repomatic package¶
Expose package-wide elements.
Subpackages¶
repomatic.datapackagerepomatic.githubpackage- Submodules
repomatic.github.actionsmoduleNULL_SHAWorkflowEventWorkflowEvent.branch_protection_ruleWorkflowEvent.check_runWorkflowEvent.check_suiteWorkflowEvent.createWorkflowEvent.deleteWorkflowEvent.deploymentWorkflowEvent.deployment_statusWorkflowEvent.discussionWorkflowEvent.discussion_commentWorkflowEvent.forkWorkflowEvent.gollumWorkflowEvent.issue_commentWorkflowEvent.issuesWorkflowEvent.labelWorkflowEvent.merge_groupWorkflowEvent.milestoneWorkflowEvent.page_buildWorkflowEvent.projectWorkflowEvent.project_cardWorkflowEvent.project_columnWorkflowEvent.publicWorkflowEvent.pull_requestWorkflowEvent.pull_request_commentWorkflowEvent.pull_request_reviewWorkflowEvent.pull_request_review_commentWorkflowEvent.pull_request_targetWorkflowEvent.pushWorkflowEvent.registry_packageWorkflowEvent.releaseWorkflowEvent.repository_dispatchWorkflowEvent.scheduleWorkflowEvent.statusWorkflowEvent.watchWorkflowEvent.workflow_callWorkflowEvent.workflow_dispatchWorkflowEvent.workflow_run
AnnotationLevelgenerate_delimiter()format_multiline_output()emit_annotation()get_github_event()
repomatic.github.dev_releasemodulerepomatic.github.ghmodulerepomatic.github.issuemodulerepomatic.github.matrixmodulerepomatic.github.pr_bodymodulerepomatic.github.release_syncmodulerepomatic.github.releasesmodulerepomatic.github.tokenmodulecheck_pat_contents_permission()check_pat_issues_permission()check_pat_pull_requests_permission()check_pat_vulnerability_alerts_permission()check_pat_workflows_permission()check_commit_statuses_permission()PatPermissionResultsPatPermissionResults.contentsPatPermissionResults.issuesPatPermissionResults.pull_requestsPatPermissionResults.vulnerability_alertsPatPermissionResults.workflowsPatPermissionResults.commit_statusesPatPermissionResults.all_passed()PatPermissionResults.failed()PatPermissionResults.iter_results()
check_all_pat_permissions()validate_gh_token_env()validate_gh_api_access()validate_classic_pat_scope()
repomatic.github.unsubscribemoduleGRAPHQL_PAGE_SIZENOTIFICATION_PAGE_SIZENOTIFICATION_SUBJECT_TYPESItemActionDetailRowPhase1ResultPhase1Result.batch_sizePhase1Result.cutoffPhase1Result.newest_updatedPhase1Result.oldest_updatedPhase1Result.rowsPhase1Result.threads_failedPhase1Result.threads_inspectedPhase1Result.threads_skipped_openPhase1Result.threads_skipped_recentPhase1Result.threads_skipped_unknownPhase1Result.threads_totalPhase1Result.threads_unsubscribed
Phase2ResultUnsubscribeResultrender_report()unsubscribe_threads()
repomatic.github.workflow_syncmoduleWorkflowFormatDEFAULT_VERSIONWorkflowTriggerInfoLintResultextract_trigger_info()generate_thin_caller()identify_canonical_workflow()extract_extra_jobs()check_has_workflow_dispatch()check_version_pinned()check_triggers_match()check_secrets_passed()check_concurrency_match()generate_workflow_header()run_workflow_lint()generate_workflows()
repomatic.templatespackage
Submodules¶
repomatic.binary module¶
Binary build targets and verification utilities.
Defines the Nuitka compilation targets for all supported platforms and provides binary architecture verification using exiftool.
- repomatic.binary.NUITKA_BUILD_TARGETS = {'linux-arm64': {'arch': 'arm64', 'extension': 'bin', 'os': 'ubuntu-24.04-arm', 'platform_id': 'linux'}, 'linux-x64': {'arch': 'x64', 'extension': 'bin', 'os': 'ubuntu-24.04', 'platform_id': 'linux'}, 'macos-arm64': {'arch': 'arm64', 'extension': 'bin', 'os': 'macos-26', 'platform_id': 'macos'}, 'macos-x64': {'arch': 'x64', 'extension': 'bin', 'os': 'macos-26-intel', 'platform_id': 'macos'}, 'windows-arm64': {'arch': 'arm64', 'extension': 'exe', 'os': 'windows-11-arm', 'platform_id': 'windows'}, 'windows-x64': {'arch': 'x64', 'extension': 'exe', 'os': 'windows-2025', 'platform_id': 'windows'}}¶
List of GitHub-hosted runners used for Nuitka builds.
The key of the dictionary is the target name, which is used as a short name for user-friendlyness. As such, it is used to name the compiled binary.
Values are dictionaries with the following keys:
os: Operating system name, as used in `GitHub-hosted runners-
Hint
We choose to run the compilation only on the latest supported version of each OS, for each architecture. Note that macOS and Windows do not have the latest version available for each architecture.
platform_id: Platform identifier, as defined by Extra Platform.arch: Architecture identifier.Note
Architecture IDs are inspired from those specified for self-hosted runners
Note
Maybe we should just adopt target triple.
extension: File extension of the compiled binary.
- repomatic.binary.FLAT_BUILD_TARGETS = [{'arch': 'arm64', 'extension': 'bin', 'os': 'ubuntu-24.04-arm', 'platform_id': 'linux', 'target': 'linux-arm64'}, {'arch': 'x64', 'extension': 'bin', 'os': 'ubuntu-24.04', 'platform_id': 'linux', 'target': 'linux-x64'}, {'arch': 'arm64', 'extension': 'bin', 'os': 'macos-26', 'platform_id': 'macos', 'target': 'macos-arm64'}, {'arch': 'x64', 'extension': 'bin', 'os': 'macos-26-intel', 'platform_id': 'macos', 'target': 'macos-x64'}, {'arch': 'arm64', 'extension': 'exe', 'os': 'windows-11-arm', 'platform_id': 'windows', 'target': 'windows-arm64'}, {'arch': 'x64', 'extension': 'exe', 'os': 'windows-2025', 'platform_id': 'windows', 'target': 'windows-x64'}]¶
List of build targets in a flat format, suitable for matrix inclusion.
- repomatic.binary.BINARY_AFFECTING_PATHS: Final[tuple[str, ...]] = ('.github/workflows/release.yaml', 'pyproject.toml', 'tests/', 'uv.lock')¶
Path prefixes that always affect compiled binaries, regardless of the project.
Project-specific source directories (derived from
[project.scripts]inpyproject.toml) are added dynamically bybinary_affecting_paths.
- repomatic.binary.SKIP_BINARY_BUILD_BRANCHES: Final[frozenset[str]] = frozenset({'format-images', 'format-json', 'format-markdown', 'format-shell', 'sync-gitignore', 'sync-mailmap', 'update-deps-graph'})¶
Branch names for which binary builds should be skipped.
These branches contain changes that do not affect compiled binaries:
.mailmapupdates only affect contributor attributionDocumentation and image changes don’t affect code
.gitignoreand JSON config changes don’t affect binaries
This allows workflows to skip expensive Nuitka compilation jobs for PRs that cannot possibly change the binary output.
- repomatic.binary.BINARY_ARCH_MAPPINGS: Final[dict[str, tuple[str, str]]] = {'linux-arm64': ('CPUType', 'Arm 64-bits'), 'linux-x64': ('CPUType', 'AMD x86-64'), 'macos-arm64': ('CPUType', 'ARM 64-bit'), 'macos-x64': ('CPUType', 'x86 64-bit'), 'windows-arm64': ('MachineType', 'ARM64'), 'windows-x64': ('MachineType', 'AMD64')}¶
Mapping of build targets to (exiftool_field, expected_substring) tuples.
ABI signatures reported by
file(1)for each compiled binary:linux-arm64: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, strippedlinux-x64: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, strippedmacos-arm64: Mach-O 64-bit executable arm64macos-x64: Mach-O 64-bit executable x86_64windows-arm64: PE32+ executable (console) Aarch64, for MS Windowswindows-x64: PE32+ executable (console) x86-64, for MS Windows
- repomatic.binary.get_exiftool_command()[source]¶
Return the platform-appropriate exiftool command.
On Windows, exiftool is installed as
exiftool.exe.- Return type:
- repomatic.binary.run_exiftool(binary_path)[source]¶
Run exiftool on a binary and return parsed JSON output.
- Parameters:
binary_path (
Path) – Path to the binary file.- Return type:
- Returns:
Dictionary of exiftool metadata.
- Raises:
subprocess.CalledProcessError – If exiftool fails.
json.JSONDecodeError – If output is not valid JSON.
- repomatic.binary.verify_binary_arch(target, binary_path)[source]¶
Verify that a binary matches the expected architecture for a target.
- Parameters:
- Raises:
ValueError – If target is unknown.
AssertionError – If binary architecture does not match expected.
- Return type:
repomatic.broken_links module¶
Broken links detection and reporting.
Combines Lychee and Sphinx linkcheck results into a single “Broken links” GitHub issue. Sphinx linkcheck parsing detects broken auto-generated links (intersphinx, autodoc, type annotations) that Lychee cannot see because they only exist in the rendered HTML output.
Issue lifecycle management is delegated to issue.
- repomatic.broken_links.ISSUE_TITLE = 'Broken links'¶
Issue title used for the combined broken links report.
- repomatic.broken_links.LYCHEE_DEFAULT_BODY = PosixPath('lychee/out.md')¶
Default output path used by the lychee-action GitHub Action.
- repomatic.broken_links.SPHINX_DEFAULT_OUTPUT = PosixPath('docs/linkcheck/output.json')¶
Default Sphinx linkcheck output path produced by the
docs.yamlworkflow.
- class repomatic.broken_links.LinkcheckResult(filename, lineno, status, code, uri, info)[source]¶
Bases:
objectA single result entry from Sphinx linkcheck
output.json.Each line in the JSON-lines file corresponds to one checked URI.
- repomatic.broken_links.parse_output_json(output_json)[source]¶
Parse the Sphinx linkcheck
output.jsonfile.The file uses JSON-lines format: one JSON object per line. Blank lines are skipped.
- Parameters:
output_json (
Path) – Path to theoutput.jsonfile.- Return type:
- Returns:
List of parsed linkcheck results.
- repomatic.broken_links.filter_broken(results)[source]¶
Filter results to only broken and timed-out links.
- Parameters:
results (
Iterable[LinkcheckResult]) – Iterable of linkcheck results.- Return type:
- Returns:
List of results with
statusof"broken"or"timeout".
- repomatic.broken_links.generate_markdown_report(broken, source_url=None)[source]¶
Generate a Markdown report of broken links grouped by source file.
The report starts with H2 file headings, suitable for embedding as a section in the combined broken links issue body.
- Parameters:
broken (
list[LinkcheckResult]) – List of broken linkcheck results.source_url (
str|None) – Base URL for linking filenames and line numbers. When provided, file headers become clickable links and line numbers deep-link to the specific line.
- Return type:
- Returns:
Markdown-formatted report string.
- repomatic.broken_links.get_label(repo_name)[source]¶
Return the appropriate label based on repository name.
- repomatic.broken_links.manage_combined_broken_links_issue(repo_name=None, lychee_exit_code=None, lychee_body_file=None, sphinx_output_json=None, sphinx_source_url=None)[source]¶
Manage the combined broken links issue lifecycle.
Combines results from Lychee and Sphinx linkcheck into a single “Broken links” issue. Each tool’s results appear under its own heading. Tools that were not run are omitted from the report. Tools that found no broken links show a “No broken links found.” message.
When running in GitHub Actions, most parameters are auto-detected from
Metadataand well-known file paths:repo_namedefaults toMetadata.repo_name.lychee_body_filedefaults to./lychee/out.mdwhenlychee_exit_codeis provided and the file exists.sphinx_output_jsondefaults to./docs/linkcheck/output.jsonwhen the file exists.sphinx_source_urlis composed fromMetadata.repo_urlandMetadata.sha.
- Parameters:
repo_name (
str|None) – Repository name (for label selection). Defaults toMetadata.repo_name.lychee_exit_code (
int|None) – Exit code from lychee (0=no broken links, 2=broken links found).Noneif lychee was not run.lychee_body_file (
Path|None) – Path to the lychee output file. Defaults to./lychee/out.mdwhenlychee_exit_codeis provided and the file exists.sphinx_output_json (
Path|None) – Path to Sphinx linkcheckoutput.json. Defaults to./docs/linkcheck/output.jsonwhen the file exists.sphinx_source_url (
str|None) – Base URL for linking filenames and line numbers in the Sphinx report. Auto-composed fromMetadata.repo_urlandMetadata.sha.
- Raises:
ValueError – If lychee exit code is not 0, 2, or
None.ValueError – If
repo_namecannot be determined.
- Return type:
repomatic.cache module¶
Global cache for downloaded tool executables, HTTP API responses, and generated tool configurations.
Three cache subtrees under the user-level cache directory:
Binary cache (bin/): platform-specific tool executables, keyed by
{tool}/{version}/{platform}/{executable}. Each cached binary has a
.sha256 sidecar written after a verified archive download. Cache hits
verify the binary against this sidecar to detect local tampering.
HTTP response cache (http/): JSON API responses from PyPI and GitHub,
keyed by {namespace}/{key}.json. Freshness is controlled by a per-caller
TTL (seconds); stale entries remain on disk until auto-purge removes them.
Config cache (config/): generated tool configuration files, keyed by
{tool}/{filename}. Overwritten on every invocation from the current
[tool.X] section in pyproject.toml or bundled defaults. Passed to
tools via explicit --config flags so repomatic never writes to the
user’s repository.
Note
The cache module is intentionally a pure storage layer. It does not know about checksums, registries, API semantics, or tool specifications. All trust and freshness decisions belong to the caller.
- class repomatic.cache.CacheEntry(tool, version, platform, executable, size, path, mtime)[source]¶
Bases:
objectA single cached binary with its metadata.
- class repomatic.cache.HttpCacheEntry(namespace, key, size, path, mtime)[source]¶
Bases:
objectA single cached HTTP response with its metadata.
- class repomatic.cache.ConfigCacheEntry(tool, filename, size, path, mtime)[source]¶
Bases:
objectA single cached tool configuration file with its metadata.
- repomatic.cache.cache_dir()[source]¶
Resolve the cache root directory.
Precedence (highest to lowest):
REPOMATIC_CACHE_DIRenvironment variable.cache.dirin[tool.repomatic].Platform-specific default.
- Return type:
- Returns:
Absolute path to the cache root (may not exist yet).
- repomatic.cache.cached_binary_path(name, version, platform_key, executable)[source]¶
Construct the cache path for a binary (does not check existence).
- repomatic.cache.get_cached_binary(name, version, platform_key, executable)[source]¶
Return the cached binary path if it exists and is executable.
Does not verify the checksum. The caller is responsible for integrity checks since it owns the checksum value and the
skip_checksumflag.
- repomatic.cache.store_binary(name, version, platform_key, source)[source]¶
Copy an extracted binary into the cache atomically.
Writes to a temporary file in the target directory, then renames to the final name. This is atomic on POSIX (same-filesystem rename) and safe on Windows (
Path.replaceoverwrites atomically).Triggers
auto_purge()after a successful store.
- repomatic.cache.cache_info()[source]¶
List all cached binaries.
- Return type:
- Returns:
List of
CacheEntryinstances, sorted by tool name then version.
- repomatic.cache.clear_cache(tool=None, max_age_days=None)[source]¶
Remove cached binaries.
- Parameters:
- Return type:
- Returns:
Tuple of (files_deleted, bytes_freed).
- repomatic.cache.get_cached_response(namespace, key, max_age_seconds)[source]¶
Return a cached HTTP response if it exists and is fresh.
- Parameters:
- Return type:
- Returns:
Raw cached response bytes, or
Noneif not cached or stale.
- repomatic.cache.store_response(namespace, key, data)[source]¶
Store an HTTP response in the cache atomically.
Uses the same write-to-temp-then-rename pattern as
store_binary(). Triggersauto_purge()after a successful store.- Parameters:
- Return type:
- Returns:
Path to the cached response file, or
Noneif the write failed (permissions, read-only filesystem, sandbox restrictions).
- repomatic.cache.http_cache_info()[source]¶
List all cached HTTP responses.
- Return type:
- Returns:
List of
HttpCacheEntryinstances, sorted by namespace then key.
- repomatic.cache.clear_http_cache(namespace=None, max_age_days=None)[source]¶
Remove cached HTTP responses.
- Parameters:
- Return type:
- Returns:
Tuple of (files_deleted, bytes_freed).
- repomatic.cache.store_config(tool_name, filename, content)[source]¶
Store a generated tool config in the cache atomically.
Uses the same write-to-temp-then-rename pattern as
store_response(). Does not triggerauto_purge(): config files are tiny and overwritten on every invocation, so age-based pruning is unnecessary.- Parameters:
- Return type:
- Returns:
Path to the cached config file, or
Noneif the write failed (permissions, read-only filesystem, sandbox restrictions).
- repomatic.cache.config_cache_info()[source]¶
List all cached tool configurations.
- Return type:
- Returns:
List of
ConfigCacheEntryinstances, sorted by tool name.
- repomatic.cache.auto_purge()[source]¶
Remove cached entries older than the configured TTL.
Called automatically after
store_binary()andstore_response(). Purges both binary and HTTP cache entries. Resolves the TTL fromREPOMATIC_CACHE_MAX_AGEenv var, thencache.max-agein[tool.repomatic], then theCacheConfig.max_agefield default. Set to0to disable.- Return type:
repomatic.changelog module¶
Changelog parsing, updating, and release lifecycle management.
This module is the single source of truth for all changelog management decisions and operations. It handles two phases of the release cycle:
Post-release (unfreeze) — Changelog.update():
Decomposes the latest release section via Changelog.decompose_version(),
transforms the elements into an unreleased entry (date → unreleased,
comparison URL → ...main, body → development warning), renders via the
release-notes template, and prepends the result to the changelog.
Release preparation (freeze) — Changelog.freeze():
Decomposes the current unreleased section, sets the release date, freezes
the comparison URL to ...vX.Y.Z, clears the development warning,
renders via the release-notes template, and replaces the section
in place.
Both operations follow the same decompose → modify → render → replace
pattern, with the release-notes.md template as the single source of
truth for section layout. Both are idempotent: re-running them produces
the same result. This is critical for CI workflows that may be retried.
Note
This is a custom implementation. After evaluating all major alternatives — towncrier, commitizen, python-semantic-release, generate-changelog, release-please, scriv, and git-changelog (see issue #94) — none were found to cover even half of the requirements.
Why not use an off-the-shelf tool?¶
Existing tools fall into two camps, neither of which fits:
Commit-driven tools (python-semantic-release, commitizen, generate-changelog, release-please) auto-generate changelogs from Git history. This conflicts with the project’s philosophy of hand-curated changelogs: entries are written for users, consolidated by hand, and summarize only changes worth knowing about. Auto-generated logs from developer commits are too noisy and don’t account for back-and-forth during development.
Fragment-driven tools (towncrier, scriv) avoid merge conflicts by using per-change files, but handle none of the release orchestration: comparison URL management, GFM warning lifecycle, workflow action reference freezing, or the two-commit freeze/unfreeze release cycle. The multiplication of files across the repo adds complexity, and there is no 1:1 mapping between fragments and changelog entries.
Specific gaps across all evaluated tools:
No comparison URL management. None generate GitHub
v1.0.0...v1.1.0diff links, or update them from...mainto...vX.Y.Zat release time.No unreleased section lifecycle. None manage the
[!WARNING]GFM alert warning that the version is under active development, inserting it post-release and removing it at release time.No workflow action reference freezing. None handle the freeze/unfreeze cycle for
@main↔@vX.Y.Zreferences in workflow files.No two-commit release workflow. None support the freeze commit (
[changelog] Release vX.Y.Z) plus unfreeze commit ([changelog] Post-release bump) pattern thatchangelog.yamluses.No citation file integration. None update
citation.cffrelease dates.No version bump eligibility checks. None prevent double version increments by comparing the current version against the latest Git tag with a commit-message fallback.
The custom implementation in this module is tightly integrated with the release workflow. Adopting any external tool would require keeping most of this code and adding a new dependency — more complexity, not less.
- repomatic.changelog.CHANGELOG_HEADER = '# Changelog\n'¶
Default changelog header for empty changelogs.
- repomatic.changelog.SECTION_START = '##'¶
Markdown heading level for changelog version sections.
- repomatic.changelog.DATE_PATTERN = re.compile('\\d{4}\\-\\d{2}\\-\\d{2}')¶
Pattern matching release dates in YYYY-MM-DD format.
- repomatic.changelog.VERSION_COMPARE_PATTERN = re.compile('v(\\d+\\.\\d+\\.\\d+)\\.\\.\\.v(\\d+\\.\\d+\\.\\d+)')¶
Pattern matching GitHub comparison URLs like
v1.0.0...v1.0.1.
- repomatic.changelog.RELEASED_VERSION_PATTERN = re.compile('^##\\s*\\[`?(\\d+\\.\\d+\\.\\d+)`?\\s+\\((\\d{4}-\\d{2}-\\d{2})\\)\\]', re.MULTILINE)¶
Pattern matching released version headings with dates.
Captures version and date from headings like
## [`5.9.1` (2026-02-14)](...). Skips unreleased versions which use(unreleased)instead of a date. Backticks around the version are optional.
- repomatic.changelog.HEADING_PARTS_PATTERN = re.compile('^##\\s*\\[`?(?P<version>\\d+\\.\\d+\\.\\d+(?:\\.\\w+)?)`?\\s+\\((?P<date>[^)]+)\\)\\]\\((?P<url>[^)]+)\\)', re.MULTILINE)¶
Pattern extracting version, date/label, and URL from a heading.
Used by
Changelog.decompose_version()to populate the heading fields ofVersionElements.
- repomatic.changelog.AVAILABLE_VERB = 'is available on'¶
Verb phrase for versions present on a platform.
- repomatic.changelog.FIRST_AVAILABLE_VERB = 'is the *first version* available on'¶
Verb phrase for the inaugural release on a platform.
- repomatic.changelog.GITHUB_LABEL = '🐙 GitHub'¶
Display label for GitHub releases in admonitions.
- repomatic.changelog.GITHUB_RELEASE_URL = '{repo_url}/releases/tag/v{version}'¶
GitHub release page URL for a specific version.
- repomatic.changelog.NOT_AVAILABLE_VERB = 'is **not available** on'¶
Verb phrase for versions missing from a platform.
- repomatic.changelog.YANKED_DEDUP_MARKER = 'yanked from PyPI'¶
Dedup marker for the yanked admonition to prevent duplicate insertion.
- class repomatic.changelog.VersionElements(compare_url='', date='', version='', availability_admonition='', changes='', development_warning='', editorial_admonition='', yanked_admonition='')[source]¶
Bases:
objectDiscrete building blocks of a changelog version section.
Each field is a pre-formatted markdown block (or empty string when absent). Templates compose these elements into the final section layout. Empty variables produce empty strings, which
render_template’s 3+ newline collapsing handles gracefully.Heading fields (
compare_url,date,version) are populated byChangelog.decompose_version()and used by therelease-notestemplate to render the##heading line. Body fields are unchanged.
- class repomatic.changelog.Changelog(initial_changelog=None, current_version=None)[source]¶
Bases:
objectHelpers to manipulate changelog files written in Markdown.
- update()[source]¶
Add a new unreleased entry at the top of the changelog.
Decomposes the current version section, transforms it into an unreleased entry (date set to
unreleased, comparison URL retargeted tomain, body replaced with the development warning), and prepends it to the changelog.Idempotent: returns the current content unchanged if an unreleased entry already exists.
- Return type:
- freeze(release_date=None, default_branch='main')[source]¶
Freeze the current unreleased section for release.
Decomposes the current version section, sets the release date, freezes the comparison URL to the release tag, clears the development warning, and re-renders via the
release-notestemplate.
- classmethod freeze_file(path, version, release_date=None, default_branch='main')[source]¶
Freeze a changelog file in place.
Reads the file, applies all freeze operations via
freeze(), and writes the result back.
- extract_repo_url()[source]¶
Extract the repository URL from changelog comparison links.
Parses the first
## [...](<repo_url>/compare/...)heading and returns the base repository URL (e.g.https://github.com/user/repo).- Return type:
- Returns:
The repository URL, or empty string if not found.
- extract_all_releases()[source]¶
Extract all released versions and their dates from the changelog.
Scans for headings matching
## [X.Y.Z (YYYY-MM-DD)](...). Unreleased versions (with(unreleased)) are skipped.
- extract_all_version_headings()[source]¶
Extract all version strings from
##headings.Includes both released and unreleased versions, so the caller can avoid false-positive orphan detection for the current development version.
- insert_version_section(version, date, repo_url, all_versions)[source]¶
Insert a placeholder section for a missing version.
The section is placed at the correct position in descending version order. The comparison URL points from the next-lower version to this one. After insertion, the next-higher version’s comparison URL base is updated to reference this version, keeping the timeline coherent.
Idempotent: returns False if the version heading already exists.
- update_comparison_base(version, new_base)[source]¶
Replace the base version in a version heading’s comparison URL.
Changes
compare/vOLD...vX.Y.Ztocompare/vNEW...vX.Y.Zin the heading for the given version.
- decompose_version(version)[source]¶
Decompose a version section into discrete elements.
Parses both the heading (version, date, URL) and the body (admonitions, changes).
Classifies each GFM alert block (consecutive
>lines) as one of the auto-generated element types. Everything not classified as auto-generated is preserved aschanges.- Parameters:
version (
str) – Version string (e.g.1.2.3).- Return type:
- Returns:
A
VersionElementswith each field populated.
- repomatic.changelog.build_release_admonition(version, *, pypi_url='', github_url='', first_on_all=False)[source]¶
Build a GFM release admonition with available distribution links.
- Parameters:
version (
str) – Version string (e.g.1.2.3).pypi_url (
str) – PyPI project URL, or empty if not on PyPI.github_url (
str) – GitHub release URL, or empty if no release exists.first_on_all (
bool) – Whether every listed platform is a first appearance. WhenTrue, uses “is the first version available on” wording.
- Return type:
- Returns:
A
> [!NOTE]admonition block, or empty string if neither URL is provided.
Build a GFM warning admonition for platforms missing a version.
- repomatic.changelog.lint_changelog_dates(changelog_path, package=None, *, fix=False, pypi_package_history=())[source]¶
Verify that changelog release dates match canonical release dates.
Uses PyPI upload dates as the canonical reference when the project is published to PyPI. Falls back to git tag dates for projects not on PyPI.
Versions older than the first PyPI release are expected to be absent and logged at info level. Versions newer than the first PyPI release but missing from PyPI are unexpected and logged as warnings.
Also detects orphaned versions: versions that exist as git tags, GitHub releases, or PyPI packages but have no corresponding changelog entry. Orphans are logged as warnings and cause a non-zero exit code.
When
fixis enabled, date mismatches are corrected in-place and admonitions are added to the changelog:A
[!NOTE]admonition listing available distribution links (PyPI, GitHub) for each version. Links are conditional: only sources where the version exists are included.A
[!WARNING]admonition listing platforms where the version is not available (missing from PyPI, GitHub, or both).A
[!CAUTION]admonition for yanked releases.
Caution
The
fix-changelogworkflow job skips this function during the release cycle (whenrelease_commits_matrixis non-empty). At that point the release pipeline hasn’t published to PyPI or created a GitHub release yet, so this function would incorrectly add “not available” admonitions to the freshly-released version.Placeholder sections for orphaned versions, with comparison URLs linking to adjacent versions.
- Parameters:
changelog_path (
Path) – Path to the changelog file.package (
str|None) – PyPI package name. IfNone, auto-detected frompyproject.toml. If detection fails, falls back to git tags.fix (
bool) – If True, fix dates and add admonitions to the file.pypi_package_history (
Sequence[str]) – Former PyPI package names for renamed projects. Releases from each former name are merged into the lookup table so versions published under old names are recognized. The current package name wins on version collisions.
- Return type:
- Returns:
0if all dates match or references are missing,1if any date mismatch or orphan is found.
repomatic.checksums module¶
Update SHA-256 checksums for binary downloads.
Two update modes:
Workflow files — scans for GitHub release download URLs paired with
sha256sum --checkverification lines. Replaces stale hashes in-place.Tool registry — iterates
TOOL_REGISTRYentries withbinaryspecs, downloads each URL, and replaces stale hashes intool_runner.py.
Designed to be called by Renovate postUpgradeTasks after version bumps,
but also works standalone for manual checksum updates.
- repomatic.checksums.update_checksums(file_path)[source]¶
Update SHA-256 checksums in a workflow file.
repomatic.cli module¶
- repomatic.cli.is_stdout(filepath)[source]¶
Check if a file path is set to stdout.
Prevents the creation of a
-file in the current directory.- Return type:
- repomatic.cli.prep_path(filepath)[source]¶
Prepare the output file parameter for Click’s echo function.
Always returns a UTF-8 encoded file object, including for stdout. This avoids
UnicodeEncodeErroron Windows where the default stdout encoding iscp1252.For non-stdout paths, parent directories are created automatically if they don’t exist. This absorbs the
mkdir -pstep that workflows previously had to do.- Return type:
- repomatic.cli.generate_header(ctx)[source]¶
Generate metadata to be left as comments to the top of a file generated by this CLI.
- Return type:
- repomatic.cli.remove_header(content)[source]¶
Return content without blank lines and header metadata from above.
- Return type:
- class repomatic.cli.ComponentSelector[source]¶
Bases:
ParamTypeAccepts bare component names or qualified
component/fileselectors.Bare names (e.g.,
skills) select an entire component. Qualified entries (e.g.,skills/repomatic-topics) select a single file within a component. The same syntax is used by theexcludeconfig option in[tool.repomatic].- get_metavar(param, ctx=None)[source]¶
Returns the metavar default for this param if it provides one.
- convert(value, param, ctx)[source]¶
Convert the value to the correct type. This is not called if the value is
None(the missing value).This must accept string values from the command line, as well as values that are already the correct type. It may also convert other compatible types.
The
paramandctxarguments may beNonein certain situations, such as when converting prompt input.If the value cannot be converted, call
fail()with a descriptive message.- Parameters:
value – The value to convert.
param – The parameter that is using this type to convert its value. May be
None.ctx – The current context that arrived at this value. May be
None.
- shell_complete(ctx, param, incomplete)[source]¶
Return a list of
CompletionItemobjects for the incomplete value. Most types do not provide completions, but some do, and this allows custom types to provide custom completions as well.- Parameters:
ctx – Invocation context for this command.
param – The parameter that is requesting completion.
incomplete – Value being completed. May be empty.
Added in version 8.0.
- repomatic.cli.GITIGNORE_BASE_CATEGORIES: tuple[str, ...] = ('certificates', 'emacs', 'git', 'gpg', 'linux', 'macos', 'node', 'nohup', 'python', 'rust', 'ssh', 'vim', 'virtualenv', 'visualstudiocode', 'windows')¶
Base gitignore.io template categories included in every generated
.gitignore.These cover common development environments, operating systems, and tools. Downstream projects can add more via
gitignore-extra-categoriesin[tool.repomatic].
- repomatic.cli.GITIGNORE_IO_URL = 'https://www.toptal.com/developers/gitignore/api'¶
gitignore.io API endpoint for fetching
.gitignoretemplates.
repomatic.config module¶
Configuration schema and loading for [tool.repomatic] in pyproject.toml.
Defines the Config dataclass, its TOML serialization helpers, and the
load_repomatic_config function that reads, validates, and returns a typed
Config instance.
- class repomatic.config.CacheConfig(dir='', github_release_ttl=604800, github_releases_ttl=86400, max_age=30, pypi_ttl=86400)[source]¶
Bases:
objectNested schema for
[tool.repomatic.cache].- dir: str = ''¶
Override the binary cache directory path.
When empty (the default), the cache uses the platform convention:
~/Library/Caches/repomaticon macOS,$XDG_CACHE_HOME/repomaticor~/.cache/repomaticon Linux,%LOCALAPPDATA%\repomatic\Cacheon Windows. TheREPOMATIC_CACHE_DIRenvironment variable takes precedence over this setting.
- github_release_ttl: int = 604800¶
Freshness TTL for cached single-release bodies (seconds).
GitHub release bodies are immutable once published, so a long TTL (7 days) is safe. Set to
0to disable caching for single-release lookups.
- github_releases_ttl: int = 86400¶
Freshness TTL for cached all-releases responses (seconds).
New releases can appear at any time, so a shorter TTL (24 hours) balances freshness with API savings.
- class repomatic.config.DependencyGraphConfig(all_extras=True, all_groups=True, level=None, no_extras=<factory>, no_groups=<factory>, output='./docs/assets/dependencies.mmd')[source]¶
Bases:
objectNested schema for
[tool.repomatic.dependency-graph].- all_extras: bool = True¶
Whether to include all optional extras in the graph.
When
True, theupdate-deps-graphcommand behaves as if--all-extraswas passed.
- all_groups: bool = True¶
Whether to include all dependency groups in the graph.
When
True, theupdate-deps-graphcommand behaves as if--all-groupswas passed. Projects that want to exclude development dependency groups (docs, test, typing) from their published graph can set this tofalse.
- level: int | None = None¶
Maximum depth of the dependency graph.
Nonemeans unlimited.1= primary deps only,2= primary + their deps, etc. Equivalent to--level.
- no_extras: list[str]¶
Optional extras to exclude from the graph.
Equivalent to passing
--no-extrafor each entry. Takes precedence overdependency-graph.all-extras.
- class repomatic.config.DocsConfig(apidoc_exclude=<factory>, apidoc_extra_args=<factory>, update_script='./docs/docs_update.py')[source]¶
Bases:
objectNested schema for
[tool.repomatic.docs].- apidoc_exclude: list[str]¶
Glob patterns for modules to exclude from
sphinx-apidoc.Passed as positional exclude arguments after the source directory (e.g.,
["setup.py", "tests"]).
- class repomatic.config.GitignoreConfig(extra_categories=<factory>, extra_content=<factory>, location='./.gitignore', sync=True)[source]¶
Bases:
objectNested schema for
[tool.repomatic.gitignore].- extra_categories: list[str]¶
Additional gitignore template categories to fetch from gitignore.io.
List of template names (e.g.,
["Python", "Node", "Terraform"]) to combine with the generated.gitignorecontent.
- class repomatic.config.LabelsConfig(extra_content_rules='', extra_file_rules='', extra_files=<factory>, sync=True)[source]¶
Bases:
objectNested schema for
[tool.repomatic.labels].- extra_content_rules: str = ''¶
Additional YAML rules appended to the content-based labeller configuration.
Appended to the bundled
labeller-content-based.yamlduring export.
- extra_file_rules: str = ''¶
Additional YAML rules appended to the file-based labeller configuration.
Appended to the bundled
labeller-file-based.yamlduring export.
- class repomatic.config.TestMatrixConfig(exclude=<factory>, include=<factory>, remove=<factory>, replace=<factory>, variations=<factory>)[source]¶
Bases:
objectNested schema for
[tool.repomatic.test-matrix].Keys inside
replaceandvariationsare GitHub Actions matrix identifiers (e.g.,os,python-version) and must not be normalized to snake_case. Click Extra’sclick_extra.normalize_keys = Falsemetadata on the parent field prevents this.- exclude: list[dict[str, str]]¶
Extra exclude rules applied to both full and PR test matrices.
Each entry is a dict of GitHub Actions matrix keys (e.g.,
{"os": "windows-11-arm"}) that removes matching combinations. Additive to the upstream default excludes.
- include: list[dict[str, str]]¶
Extra include directives applied to both full and PR test matrices.
Each entry is a dict of GitHub Actions matrix keys that adds or augments matrix combinations. Additive to the upstream default includes.
- remove: dict[str, list[str]]¶
Per-axis value removals applied to both full and PR test matrices.
Outer key is the variation/axis ID (e.g.,
os,python-version). Inner list contains values to drop from that axis. Applied after replacements but before excludes, includes, and variations.
- replace: dict[str, dict[str, str]]¶
Per-axis value replacements applied to both full and PR test matrices.
Outer key is the variation/axis ID (e.g.,
os,python-version). Inner dict maps old values to new values. Applied before removals, excludes, includes, and variations.
- variations: dict[str, list[str]]¶
Extra matrix dimension values added to the full test matrix only.
Each key is a dimension ID (e.g.,
os,click-version) and its value is a list of additional entries. For existing dimensions, values are merged with the upstream defaults. For new dimension IDs, a new axis is created. Only affects the full matrix; the PR matrix stays a curated reduced set.
- class repomatic.config.TestPlanConfig(file='./tests/cli-test-plan.yaml', inline=None, timeout=None)[source]¶
Bases:
objectNested schema for
[tool.repomatic.test-plan].- file: str = './tests/cli-test-plan.yaml'¶
Path to the YAML test plan file for binary testing.
The test plan file defines a list of test cases to run against compiled binaries. Each test case specifies command-line arguments and expected output patterns.
- class repomatic.config.WorkflowConfig(source_paths=None, sync=True)[source]¶
Bases:
objectNested schema for
[tool.repomatic.workflow].- source_paths: list[str] | None = None¶
Source code directory names for workflow trigger
paths:filters.When set, thin-caller and header-only workflows include
paths:filters using these directory names (asname/**globs) alongside universal paths likepyproject.tomlanduv.lock.When
None(default), source paths are auto-derived from[project.name]inpyproject.tomlby replacing hyphens with underscores — the universal Python convention. For example,name = "extra-platforms"automatically uses["extra_platforms"].
- class repomatic.config.Config(awesome_template_sync=True, bumpversion_sync=True, cache=<factory>, changelog_location='./changelog.md', dependency_graph=<factory>, dev_release_sync=True, docs=<factory>, exclude=<factory>, gitignore=<factory>, include=<factory>, labels=<factory>, mailmap_sync=True, notification_unsubscribe=False, nuitka_enabled=True, nuitka_entry_points=<factory>, nuitka_extra_args=<factory>, nuitka_unstable_targets=<factory>, pypi_package_history=<factory>, setup_guide=True, skills_location='./.claude/skills/', test_matrix=<factory>, test_plan=<factory>, uv_lock_sync=True, workflow=<factory>)[source]¶
Bases:
objectConfiguration schema for
[tool.repomatic]inpyproject.toml.This dataclass defines the structure and default values for repomatic configuration. Each field has a docstring explaining its purpose.
- awesome_template_sync: bool = True¶
Whether awesome-template sync is enabled for this project.
Repositories whose name starts with
awesome-get their boilerplate synced from files bundled inrepomatic. Set tofalseto opt out.
- bumpversion_sync: bool = True¶
Whether bumpversion config sync is enabled for this project.
Projects that manage their own
[tool.bumpversion]section and do not want the autofix job to overwrite it can set this tofalse.
- cache: CacheConfig¶
Binary cache configuration.
- changelog_location: str = './changelog.md'¶
File path of the changelog, relative to the root of the repository.
- dependency_graph: DependencyGraphConfig¶
Dependency graph generation configuration.
- dev_release_sync: bool = True¶
Whether dev pre-release sync is enabled for this project.
Projects that do not want a rolling draft pre-release maintained on GitHub can set this to
false.
- docs: DocsConfig¶
Sphinx documentation generation configuration.
- exclude: list[str]¶
Additional components and files to exclude from repomatic operations.
Additive to the default exclusions (
labels,skills). Bare names exclude an entire component (e.g.,"workflows"). Qualifiedcomponent/identifierentries exclude a specific file within a component (e.g.,"workflows/debug.yaml","skills/repomatic-audit","labels/labeller-content-based.yaml").Affects
repomatic init,workflow sync, andworkflow create. Explicit CLI positional arguments override this list.
- gitignore: GitignoreConfig¶
.gitignoresync configuration.
- include: list[str]¶
Components and files to force-include, overriding default exclusions.
Use this to opt into components that are excluded by default (
labels,skills). Each entry is subtracted from the effective exclude set (defaults + userexclude) and bypassesRepoScopefiltering, so scope-restricted files (like awesome-only skills) are included regardless of repository type. Qualified entries (component/file) implicitly select the parent component. Same syntax asexclude.
- labels: LabelsConfig¶
Repository label sync configuration.
- mailmap_sync: bool = True¶
Whether
.mailmapsync is enabled for this project.Projects that manage their own
.mailmapand do not want the autofix job to overwrite it can set this tofalse.
- notification_unsubscribe: bool = False¶
Whether the unsubscribe-threads workflow is enabled.
Notifications are per-user across all repos. Enable on the single repo where you want scheduled cleanup of closed notification threads. Requires a classic PAT with
notificationsscope stored asREPOMATIC_NOTIFICATIONS_PAT.
- nuitka_enabled: bool = True¶
Whether Nuitka binary compilation is enabled for this project.
Projects with
[project.scripts]entries that are not intended to produce standalone binaries (e.g., libraries with convenience CLI wrappers) can set this tofalseto opt out of Nuitka compilation.
- nuitka_entry_points: list[str]¶
Which
[project.scripts]entry points produce Nuitka binaries.List of CLI IDs (e.g.,
["mpm"]) to compile. When empty (the default), deduplicates by callable target: keeps the first entry point for each uniquemodule:callablepair. This avoids building duplicate binaries when a project declares alias entry points (like bothmpmandmeta-package-managerpointing to the same function).
- nuitka_extra_args: list[str]¶
Extra Nuitka CLI arguments for binary compilation.
Project-specific flags (e.g.,
--include-data-files,--include-package-data) that are passed to the Nuitka build command.
- nuitka_unstable_targets: list[str]¶
Nuitka build targets allowed to fail without blocking the release.
List of target names (e.g.,
["linux-arm64", "windows-x64"]) that are marked as unstable. Jobs for these targets will be allowed to fail without preventing the release workflow from succeeding.
- pypi_package_history: list[str]¶
Former PyPI package names for projects that were renamed.
When a project changes its PyPI name, older versions remain published under the previous name. List former names here so
lint-changelogcan fetch release metadata from all names and generate correct PyPI URLs.
- setup_guide: bool = True¶
Whether the setup guide issue is enabled for this project.
Projects that do not need
REPOMATIC_PATor manage their own PAT setup can set this tofalseto suppress the setup guide issue.
- skills_location: str = './.claude/skills/'¶
Directory prefix for Claude Code skill files, relative to the repository root.
Skill files are written as
{skills_location}/{skill-id}/SKILL.md. Useful for repositories where.claude/is not at the root (e.g., dotfiles repos that store configs under a subdirectory).
- test_matrix: TestMatrixConfig¶
Per-project customizations for the GitHub Actions CI test matrix.
Keys inside this section are GitHub Actions matrix identifiers (e.g.,
os,python-version) and must not be normalized to snake_case.
- test_plan: TestPlanConfig¶
Binary test plan configuration.
- uv_lock_sync: bool = True¶
Whether
uv.locksync is enabled for this project.Projects that manage their own lock file strategy and do not want the
sync-uv-lockjob to runuv lock --upgradecan set this tofalse.
- workflow: WorkflowConfig¶
Workflow sync configuration.
- repomatic.config.SUBCOMMAND_CONFIG_FIELDS: Final[frozenset[str]] = frozenset({'awesome_template_sync', 'bumpversion_sync', 'cache', 'changelog_location', 'dependency_graph', 'dev_release_sync', 'docs', 'exclude', 'gitignore', 'include', 'labels', 'mailmap_sync', 'notification_unsubscribe', 'pypi_package_history', 'setup_guide', 'skills_location', 'test_matrix', 'test_plan', 'uv_lock_sync', 'workflow'})¶
Config fields consumed directly by subcommands, not needed as metadata outputs.
The
test-plananddeps-graphsubcommands now read these values directly from[tool.repomatic]inpyproject.toml, so they no longer need to be passed through workflow metadata outputs.
- repomatic.config.CONFIG_REFERENCE_HEADERS = ('Option', 'Type', 'Default', 'Description')¶
Column headers for the
[tool.repomatic]configuration reference table.
- repomatic.config.config_reference()[source]¶
Build the
[tool.repomatic]configuration reference as table rows.Introspects the
Configdataclass fields, their type annotations, defaults, and attribute docstrings. Nested dataclass fields are expanded into individual rows with dotted keys. Returns a list of(option, type, default, description)tuples suitable forclick_extra.table.print_table.
- repomatic.config.load_repomatic_config(pyproject_data=None)[source]¶
Load
[tool.repomatic]config merged withConfigdefaults.Delegates to click-extra’s schema-aware dataclass instantiation, which handles normalization, flattening, nested dataclasses, and opaque field extraction automatically based on field metadata and type hints.
repomatic.deps_graph module¶
Generate Mermaid dependency graphs from uv lockfiles.
Note
Uses uv export --format cyclonedx1.5 which provides structured JSON
with dependency relationships, replacing the need for pipdeptree.
Warning
The generated Mermaid syntax targets the version bundled with
sphinxcontrib-mermaid, currently 11.12.1. See the hard-coded
MERMAID_VERSION constant in sphinxcontrib-mermaid’s source.
Avoid using Mermaid features introduced after that version.
- repomatic.deps_graph.STYLE_PRIMARY_DEPS_SUBGRAPH: str = 'fill:#1565C020,stroke:#42A5F5'¶
Mermaid style for the primary dependencies subgraph box.
Uses semi-transparent fill (8-digit hex) so the tint adapts to both light and dark page backgrounds.
- repomatic.deps_graph.STYLE_EXTRA_SUBGRAPH: str = 'fill:#7B1FA220,stroke:#BA68C8'¶
Mermaid style for extra dependency subgraph boxes.
Uses semi-transparent fill (8-digit hex) so the tint adapts to both light and dark page backgrounds.
- repomatic.deps_graph.STYLE_GROUP_SUBGRAPH: str = 'fill:#546E7A20,stroke:#90A4AE'¶
Mermaid style for group dependency subgraph boxes.
Uses semi-transparent fill (8-digit hex) so the tint adapts to both light and dark page backgrounds.
- repomatic.deps_graph.STYLE_PRIMARY_NODE: str = 'stroke-width:3px'¶
Mermaid style for root and primary dependency nodes (thick border).
- repomatic.deps_graph.MERMAID_RESERVED_KEYWORDS: frozenset[str] = frozenset({'C4Component', 'C4Container', 'C4Deployment', 'C4Dynamic', '_blank', '_parent', '_self', '_top', 'call', 'class', 'classDef', 'click', 'end', 'flowchart', 'flowchart-v2', 'graph', 'interpolate', 'linkStyle', 'style', 'subgraph'})¶
Mermaid keywords that cannot be used as node IDs.
- repomatic.deps_graph.normalize_package_name(name)[source]¶
Normalize package name for use as Mermaid node ID.
Converts to lowercase and replaces non-alphanumeric characters with underscores. Appends
_0suffix to avoid conflicts with Mermaid reserved keywords.- Return type:
- repomatic.deps_graph.parse_bom_ref(bom_ref)[source]¶
Parse a CycloneDX bom-ref into package name and version.
The format is typically
name-index@version(e.g.,click-extra-11@7.4.0).
- repomatic.deps_graph.get_available_groups(pyproject_path=None)[source]¶
Discover available dependency groups from pyproject.toml.
- repomatic.deps_graph.get_available_extras(pyproject_path=None)[source]¶
Discover available optional extras from pyproject.toml.
- repomatic.deps_graph.get_cyclonedx_sbom(package=None, groups=None, extras=None, frozen=True)[source]¶
Run uv export and return the CycloneDX SBOM as a dictionary.
Results are cached to avoid redundant subprocess calls within the same process.
- Parameters:
package (
str|None) – Optional package name to focus the export on.groups (
tuple[str,...] |None) – Optional dependency groups to include (e.g., “test”, “typing”).extras (
tuple[str,...] |None) – Optional extras to include (e.g., “xml”, “json5”).frozen (
bool) – If True, use –frozen to skip lock file updates.
- Return type:
- Returns:
Parsed CycloneDX SBOM dictionary.
- Raises:
subprocess.CalledProcessError – If uv command fails.
json.JSONDecodeError – If output is not valid JSON.
- repomatic.deps_graph.get_package_names_from_sbom(sbom)[source]¶
Extract all package names from a CycloneDX SBOM.
- repomatic.deps_graph.build_dependency_graph(sbom, root_package=None)[source]¶
Build a dependency graph from CycloneDX SBOM data.
- repomatic.deps_graph.filter_graph_to_package(root_name, nodes, edges, package)[source]¶
Filter the graph to only include dependencies of a specific package.
- Parameters:
- Return type:
- Returns:
Filtered (nodes, edges) tuple.
- repomatic.deps_graph.trim_graph_to_depth(root_name, nodes, edges, depth)[source]¶
Trim the graph to only include nodes within a given depth from the root.
Performs a breadth-first traversal from the root, keeping only nodes reachable within
depthhops and edges between those nodes.- Parameters:
- Return type:
- Returns:
Filtered (nodes, edges) tuple.
- repomatic.deps_graph.render_mermaid(root_name, nodes, edges, group_packages=None, extra_packages=None, lock_specs=None)[source]¶
Render the dependency graph as a Mermaid flowchart.
Warning
Output must stay compatible with the Mermaid version bundled in
sphinxcontrib-mermaid. See module docstring for details.- Parameters:
root_name (
str) – The root package name (used to highlight it).nodes (
dict[str,tuple[str,str]]) – Dictionary mapping bom-ref to (name, version) tuples.edges (
list[tuple[str,str]]) – List of (from_name, to_name) edge tuples.group_packages (
dict[str,set[str]] |None) – Optional dict mapping group names to sets of package names that are unique to that group. These will be rendered in subgraphs with--groupprefix.extra_packages (
dict[str,set[str]] |None) – Optional dict mapping extra names to sets of package names that are unique to that extra. These will be rendered in subgraphs with--extraprefix.lock_specs (
LockSpecifiers|None) – Optional specifiers extracted fromuv.lock. Provides edge labels (by_package) and subgraph node labels (by_subgraph).
- Return type:
- Returns:
Mermaid flowchart string.
- repomatic.deps_graph.generate_dependency_graph(package=None, groups=None, extras=None, frozen=True, depth=None, exclude_base=False)[source]¶
Generate a Mermaid dependency graph.
- Parameters:
package (
str|None) – Optional package name to focus on. If None, shows the entire project dependency tree.groups (
tuple[str,...] |None) – Optional dependency groups to include (e.g., “test”, “typing”).extras (
tuple[str,...] |None) – Optional extras to include (e.g., “xml”, “json5”).frozen (
bool) – If True, use –frozen to skip lock file updates.depth (
int|None) – Optional maximum depth from root. If None, shows the full tree.exclude_base (
bool) – If True, exclude main (base) dependencies from the graph, showing only packages unique to the requested groups/extras. Used by--only-groupand--only-extra.
- Return type:
- Returns:
The graph in Mermaid format.
repomatic.git_ops module¶
Git operations for GitHub Actions workflows.
This module provides utilities for common Git operations in CI/CD contexts, with idempotent behavior to allow safe re-runs of failed workflows.
All operations follow a “belt-and-suspenders” approach: combine workflow
timing guarantees (e.g. workflow_run ensures tags exist) with idempotent
guards (e.g. skip_existing on tag creation). This ensures correctness
in the face of race conditions, API eventual consistency, and partial failures
that are common in GitHub Actions.
Warning
Tag push requires REPOMATIC_PAT
Tags pushed with the default GITHUB_TOKEN do not trigger downstream
on.push.tags workflows. The custom PAT is required so that tagging
a release commit actually fires the publish and release creation jobs.
- repomatic.git_ops.SHORT_SHA_LENGTH = 7¶
Default SHA length hard-coded to
7.Caution
The default is subject to change and depends on the size of the repository.
- repomatic.git_ops.GITHUB_REMOTE_PATTERN = re.compile('github\\.com[:/](?P<slug>[^/]+/[^/]+?)(?:\\.git)?$')¶
Extracts an
owner/reposlug from a GitHub remote URL.Handles both HTTPS (
https://github.com/owner/repo.git) and SSH (git@github.com:owner/repo.git) formats.
- repomatic.git_ops.RELEASE_COMMIT_PATTERN = re.compile('^\\[changelog\\] Release v(?P<version>[0-9]+\\.[0-9]+\\.[0-9]+)$')¶
Pre-compiled regex for release commit messages.
Matches the full message and captures the version number. Use
fullmatchto validate a commit is a release commit, ormatch/searchwith.group("version")to extract the version string.A rebase merge preserves the original commit messages, so release commits match this pattern. A squash merge replaces them with the PR title (e.g.
Release `v1.2.3` (#42)), which does not match. This mismatch is the mechanism by which squash merges are safely skipped: thecreate-tagjob only processes commits matching this pattern, so no tag, PyPI publish, or GitHub release is created from a squash merge. Thedetect-squash-mergejob inrelease.yamldetects this and opens an issue to notify the maintainer.
- repomatic.git_ops.get_repo_slug_from_remote(remote='origin')[source]¶
Extract the
owner/reposlug from a git remote URL.Parses both HTTPS and SSH GitHub remote formats. Returns
Noneif the remote is not set, not a GitHub URL, or git is unavailable.
- repomatic.git_ops.get_latest_tag_version()[source]¶
Returns the latest release version from Git tags.
Looks for tags matching the pattern
vX.Y.Zand returns the highest version. ReturnsNoneif no matching tags are found.- Return type:
Version|None
- repomatic.git_ops.get_release_version_from_commits(max_count=10)[source]¶
Extract release version from recent commit messages.
Searches recent commits for messages matching the pattern
[changelog] Release vX.Y.Zand returns the version from the most recent match.This provides a fallback when tags haven’t been pushed yet due to race conditions between workflows. The release commit message contains the version information before the tag is created.
- repomatic.git_ops.get_tag_date(tag)[source]¶
Get the date of a Git tag in
YYYY-MM-DDformat.Uses
creatordatewhich resolves to the tagger date for annotated tags and the commit date for lightweight tags.
- repomatic.git_ops.get_all_version_tags()[source]¶
Get all version tags and their dates.
Runs a single
git tagcommand to list all tags matching thevX.Y.Zpattern and extracts their dates.
- repomatic.git_ops.create_tag(tag, commit=None)[source]¶
Create a local Git tag.
- Parameters:
- Raises:
subprocess.CalledProcessError – If tag creation fails.
- Return type:
- repomatic.git_ops.push_tag(tag, remote='origin')[source]¶
Push a Git tag to a remote repository.
- Parameters:
- Raises:
subprocess.CalledProcessError – If push fails.
- Return type:
- repomatic.git_ops.create_and_push_tag(tag, commit=None, push=True, skip_existing=True)[source]¶
Create and optionally push a Git tag.
This function is idempotent: if the tag already exists and
skip_existingis True, it returns False without failing. This allows safe re-runs of workflows that were interrupted after tag creation but before other steps.- Parameters:
- Return type:
- Returns:
True if the tag was created, False if it already existed.
- Raises:
ValueError – If tag exists and skip_existing is False.
subprocess.CalledProcessError – If Git operations fail.
repomatic.images module¶
Image optimization using external CLI tools.
Replaces the Docker-based calibreapp/image-actions GitHub Action with direct
invocations of lightweight CLI tools, removing the Docker dependency and enabling
ubuntu-slim runners.
Tools used per format:
PNG:
oxipng(lossless, multithreaded Rust optimizer).JPEG/JPG:
jpegoptim(lossless Huffman optimization + metadata stripping).
Note
Both tools are strictly lossless: oxipng finds optimal PNG encoding
parameters without altering pixel data, and jpegoptim (without -m)
rewrites Huffman tables only. This means optimization is idempotent — a
second run produces no further changes, so the workflow never creates noisy
PRs for negligible savings.
Warning
WebP and AVIF are intentionally not optimized. The only available tools
(cwebp, avifenc) work by lossy re-encoding: decode → re-compress at
a target quality. This is not idempotent — each pass re-compresses the
previous output, producing progressively smaller (and worse) files. The
earlier calibreapp/image-actions suffered from this: it required multiple
workflow runs to stabilize below the savings threshold, generating repeated
PRs with diminishing returns and cumulative quality loss. Lossless WebP/AVIF
modes exist but typically increase file size when applied to already
lossy-encoded images, making them counterproductive. Since WebP and AVIF are
modern formats chosen specifically for their compression efficiency, files in
these formats are almost always already well-optimized at creation time.
- class repomatic.images.OptimizationResult(path, before_bytes, after_bytes)[source]¶
Bases:
objectResult of optimizing a single image file.
- repomatic.images.format_file_size(size_bytes)[source]¶
Format a byte count as a human-readable string.
Uses KB/MB/GB with one decimal place, matching the format produced by
calibreapp/image-actions.- Return type:
- repomatic.images.optimize_image(path, min_savings_pct, min_savings_bytes=1024)[source]¶
Optimize a single image file in-place.
- Parameters:
path (
Path) – Path to the image file.min_savings_pct (
float) – Minimum percentage savings to keep the result. If savings are below this threshold, the original file is restored.min_savings_bytes (
int) – Minimum absolute byte savings to keep the result. Prevents noisy diffs for tiny files where even a high percentage represents negligible absolute savings.
- Return type:
- Returns:
An
OptimizationResultif the file was optimized, orNoneif the format is unsupported, the required tool is missing, or savings were below the threshold.
repomatic.init_project module¶
Bundled data files, configuration templates, and repository initialization.
Provides a unified interface for accessing bundled data files from
repomatic/data/ and orchestrates repository bootstrapping via
repomatic init.
Available components (repomatic init <component>):
workflows- Thin-caller workflow fileslabels- Label definitions (labels.toml + labeller rules)renovate- Renovate dependency update configuration (renovate.json5)changelog- Minimal changelog.mdruff- Merges[tool.ruff]into pyproject.tomlpytest- Merges[tool.pytest]into pyproject.tomlmypy- Merges[tool.mypy]into pyproject.tomlbumpversion- Merges[tool.bumpversion]into pyproject.tomlskills- Claude Code skill definitions (.claude/skills/)awesome-template- Boilerplate forawesome-*repositories
Selectors use the same component[/file] syntax as the exclude
config option in [tool.repomatic]. Qualified entries like
skills/repomatic-topics select a single file within a component.
- repomatic.init_project.EXPORTABLE_FILES: dict[str, str | None] = {'autofix.yaml': '.github/workflows/autofix.yaml', 'autolock.yaml': '.github/workflows/autolock.yaml', 'bumpversion.toml': None, 'cancel-runs.yaml': '.github/workflows/cancel-runs.yaml', 'changelog.yaml': '.github/workflows/changelog.yaml', 'codecov.yaml': '.github/codecov.yaml', 'debug.yaml': '.github/workflows/debug.yaml', 'docs.yaml': '.github/workflows/docs.yaml', 'labeller-content-based.yaml': '.github/labeller-content-based.yaml', 'labeller-file-based.yaml': '.github/labeller-file-based.yaml', 'labels.toml': 'labels.toml', 'labels.yaml': '.github/workflows/labels.yaml', 'lint.yaml': '.github/workflows/lint.yaml', 'lychee.toml': None, 'mdformat.toml': None, 'mypy.toml': None, 'pytest.toml': None, 'release.yaml': '.github/workflows/release.yaml', 'renovate.json5': 'renovate.json5', 'renovate.yaml': '.github/workflows/renovate.yaml', 'ruff.toml': None, 'skill-av-false-positive.md': '.claude/skills/av-false-positive/SKILL.md', 'skill-awesome-triage.md': '.claude/skills/awesome-triage/SKILL.md', 'skill-babysit-ci.md': '.claude/skills/babysit-ci/SKILL.md', 'skill-benchmark-update.md': '.claude/skills/benchmark-update/SKILL.md', 'skill-brand-assets.md': '.claude/skills/brand-assets/SKILL.md', 'skill-file-bug-report.md': '.claude/skills/file-bug-report/SKILL.md', 'skill-repomatic-audit.md': '.claude/skills/repomatic-audit/SKILL.md', 'skill-repomatic-changelog.md': '.claude/skills/repomatic-changelog/SKILL.md', 'skill-repomatic-deps.md': '.claude/skills/repomatic-deps/SKILL.md', 'skill-repomatic-init.md': '.claude/skills/repomatic-init/SKILL.md', 'skill-repomatic-lint.md': '.claude/skills/repomatic-lint/SKILL.md', 'skill-repomatic-release.md': '.claude/skills/repomatic-release/SKILL.md', 'skill-repomatic-sync.md': '.claude/skills/repomatic-sync/SKILL.md', 'skill-repomatic-test.md': '.claude/skills/repomatic-test/SKILL.md', 'skill-repomatic-topics.md': '.claude/skills/repomatic-topics/SKILL.md', 'skill-sphinx-docs-sync.md': '.claude/skills/sphinx-docs-sync/SKILL.md', 'skill-translation-sync.md': '.claude/skills/translation-sync/SKILL.md', 'skill-upstream-audit.md': '.claude/skills/upstream-audit/SKILL.md', 'tests.yaml': '.github/workflows/tests.yaml', 'typos.toml': None, 'unsubscribe.yaml': '.github/workflows/unsubscribe.yaml', 'yamllint.yaml': None, 'zizmor.yaml': None}¶
Registry of all exportable files: maps filename to default output path.
Nonemeans stdout (for pyproject.toml templates that need merging).
- repomatic.init_project.get_data_content(filename)[source]¶
Get the content of a bundled data file.
This is the low-level function for reading any file from
repomatic/data/.- Parameters:
filename (
str) – Name of the file to retrieve (e.g., “labels.toml”).- Return type:
- Returns:
Content of the file as a string.
- Raises:
FileNotFoundError – If the file doesn’t exist.
- repomatic.init_project.export_content(filename)[source]¶
Get the content of any exportable bundled file.
- Parameters:
filename (
str) – The filename (e.g., “ruff.toml”, “labels.toml”, “release.yaml”).- Return type:
- Returns:
Content of the file as a string.
- Raises:
ValueError – If the file is not in the registry.
FileNotFoundError – If the file doesn’t exist.
- repomatic.init_project.init_config(config_type, pyproject_path=None)[source]¶
Initialize a configuration by merging it into pyproject.toml.
Reads the pyproject.toml file, checks if the tool section already exists, and if not, inserts the bundled template at the appropriate location.
The template is stored in native format (without
[tool.X]prefix) and is parsed by tomlkit and added under the[tool]table.- Parameters:
- Return type:
- Returns:
The modified pyproject.toml content, or
Noneif no changes needed.- Raises:
ValueError – If the config type is not supported.
- repomatic.init_project.default_version_pin()[source]¶
Derive the default version pin from
__version__.Strips any
.dev0suffix and prefixes withv. For example,"5.10.0.dev0"becomes"v5.10.0".- Return type:
- class repomatic.init_project.InitResult(created=<factory>, updated=<factory>, skipped=<factory>, excluded=<factory>, excluded_existing=<factory>, unmodified_configs=<factory>, warnings=<factory>)[source]¶
Bases:
objectResult of a repository initialization run.
- repomatic.init_project.run_init(output_dir, components=(), version=None, repo='kdeldycke/repomatic', repo_slug=None, config=None)[source]¶
Bootstrap a repository for use with
kdeldycke/repomatic.Creates thin-caller workflow files, exports configuration files, and generates a minimal
changelog.mdif missing. Managed files (workflows, configs, skills) are always overwritten. User-owned files (changelog.md,zizmor.yaml) are created once and never overwritten.For
awesome-*repositories, theawesome-templatecomponent is auto-included when no explicit component selection is made.Note
Scope exclusions (
RepoScope.NON_AWESOME,AWESOME_ONLY) and user-config exclusions ([tool.repomatic] exclude) only apply during barerepomatic init. When components are explicitly named on the CLI, scope is bypassed: the caller knows what they asked for. This allows workflows to materialize out-of-scope configs at runtime (e.g.,repomatic init renovatein an awesome repo).- Parameters:
output_dir (
Path) – Root directory of the target repository.components (
Sequence[str]) – Components to initialize. Empty means all defaults. When non-empty, scope and user-config exclusions are bypassed.version (
str|None) – Version pin for upstream workflows (e.g.,v5.10.0).repo (
str) – Upstream repository containing reusable workflows.repo_slug (
str|None) – Repositoryowner/nameslug for awesome-template URL rewriting. Auto-detected viaMetadataif not provided.
- Return type:
- Returns:
Summary of created, updated, skipped, and warned items.
- repomatic.init_project.AWESOME_TEMPLATE_SLUG = 'kdeldycke/awesome-template'¶
Source slug embedded in bundled awesome-template files, rewritten at sync time.
- repomatic.init_project.init_awesome_template(output_dir, repo_slug, result)[source]¶
Copy bundled awesome-template files and rewrite URLs.
Copies all files from the
repomatic/data/awesome_template/bundle into output_dir and rewriteskdeldycke/awesome-templateURLs in.github/markdown and YAML files to match repo_slug.- Parameters:
output_dir (
Path) – Root directory of the target repository.repo_slug (
str) – Targetowner/nameslug for URL rewriting.result (
InitResult) –InitResultaccumulator for created/updated files.
- Return type:
- repomatic.init_project.find_unmodified_init_files()[source]¶
Find init-managed config files identical to their bundled defaults.
Checks bundled components without
keep_unmodifiedfor files on disk whose content matches the bundled template (viaexport_content()) after trailing-whitespace normalization (.rstrip() + "\n").Mirrors the API of
tool_runner.find_unmodified_configs(), returning(component_name, relative_path)tuples.
- repomatic.init_project.find_all_unmodified_configs()[source]¶
Find all config files identical to their bundled defaults.
Combines tool configs (yamllint, zizmor, etc.) from
tool_runner.find_unmodified_configs()and init-managed configs (labels, renovate) fromfind_unmodified_init_files().
repomatic.lint_repo module¶
Repository linting for GitHub Actions workflows.
This module provides consistency checks for repository metadata, including package names, website fields, descriptions, and funding configuration.
- repomatic.lint_repo.check_package_name_vs_repo(package_name, repo_name)[source]¶
Check if package name matches repository name.
- repomatic.lint_repo.check_website_for_sphinx(repo, is_sphinx, homepage_url=None)[source]¶
Check that Sphinx projects have a website set.
- Parameters:
- Return type:
- Returns:
Tuple of (warning_message or None, info_message).
- repomatic.lint_repo.check_description_matches(repo, project_description, repo_description=None)[source]¶
Check that repository description matches project description.
- Parameters:
- Return type:
- Returns:
Tuple of (error_message or None, info_message).
- repomatic.lint_repo.check_funding_file(repo)[source]¶
Check that repos with GitHub Sponsors have a
FUNDING.yml.Skips forks (they inherit the parent’s sponsor button) and owners without a Sponsors listing. Uses the GraphQL API because the REST API does not expose
hasSponsorsListing.
- repomatic.lint_repo.check_stale_draft_releases(repo)[source]¶
Check for draft releases that are not dev pre-releases.
Draft releases whose tag does not end with
.dev0are likely leftovers from abandoned or failed release attempts. The only expected drafts are the rolling dev pre-releases managed bysync-dev-release.
- repomatic.lint_repo.check_topics_subset_of_keywords(repo, keywords=None)[source]¶
Check that GitHub repo topics are a subset of pyproject.toml keywords.
- repomatic.lint_repo.check_pat_repository_scope(repo)[source]¶
Check that the PAT is scoped to only the current repository.
Fine-grained PATs should use Only select repositories to follow the principle of least privilege. This check detects tokens configured with All repositories access.
Two strategies are tried in order:
GET /installation/repositories— returns the repos the token can access, including arepository_selectionfield.Cross-repo probe — check
permissions.pushon another repo owned by the same user. If the token can push to a repo it should not have access to, it is over-scoped.
- repomatic.lint_repo.check_fork_pr_approval_policy(repo)[source]¶
Check that fork PR workflows require approval for first-time contributors.
GitHub Actions has a per-repository policy that controls when workflows from fork pull requests must be approved by a maintainer before they run. The three values, from weakest to strongest, are
first_time_contributors_new_to_github,first_time_contributors, andall_external_contributors.The default (
first_time_contributors_new_to_github) only catches brand-new GitHub accounts, which is trivial to bypass with a slightly aged account. The minimum acceptable setting isfirst_time_contributors, which requires approval for any first-time contributor to this repository. This is one of the mitigations recommended in Astral’s open-source security post: see https://astral.sh/blog/open-source-security-at-astral.Queries
GET /repos/{repo}/actions/permissions/fork-pr-contributor-approvaland returnsFalsewhen the policy is weaker thanfirst_time_contributors.Note
This endpoint requires the
Actions: readpermission. When theREPOMATIC_PATlacks it (or the API call fails for any other reason), the check returnsNoneto signal that the result is indeterminate rather than negative.
- repomatic.lint_repo.check_tag_protection_rules(repo)[source]¶
Check that no tag rulesets could block the
create-tagworkflow job.Tag rulesets that restrict creation or require status checks can prevent
REPOMATIC_PAT(orGITHUB_TOKEN) from pushing release tags. This check queries the repository rulesets API and warns when any ruleset targets tags.
- repomatic.lint_repo.check_branch_ruleset_on_default(repo)[source]¶
Check that at least one active branch ruleset exists.
Queries the same
GET /repos/{repo}/rulesetsendpoint ascheck_tag_protection_rules()and looks for active rulesets withtarget == "branch". The presence of any such ruleset is taken as evidence that the default branch is protected (restrict deletions and block force pushes).Note
This is a heuristic: it does not verify the ruleset targets the default branch specifically, nor that it enables the exact rules recommended by the setup guide. A deeper check would require fetching each ruleset’s conditions via
GET /repos/{repo}/rulesets/{id}, adding N+1 API calls.
- repomatic.lint_repo.check_immutable_releases(repo)[source]¶
Check that immutable releases are enabled for the repository.
Queries
GET /repos/{repo}/immutable-releasesand inspects theenabledfield in the response.Note
This endpoint requires the “Administration: Read-only” permission on fine-grained PATs. The
REPOMATIC_PATdoes not include this scope (too broad), so the check returnsNonewhen the API call fails, signaling that the result is indeterminate rather than negative.
- repomatic.lint_repo.check_pages_deployment_source(repo)[source]¶
Check that GitHub Pages is deployed via GitHub Actions, not a branch.
The
docs.yamlworkflow usesactions/upload-pages-artifactandactions/deploy-pages, which require the Pages source to be set to GitHub Actions in the repository settings. Branch-based deployment (legacy) is incompatible.Queries
GET /repos/{repo}/pagesand inspects thebuild_typefield in the response.Note
A 404 means Pages is not configured at all. This is treated as indeterminate (
None) rather than a failure, because the repo may not have deployed docs yet.
- repomatic.lint_repo.check_stale_gh_pages_branch(repo)[source]¶
Check for a leftover
gh-pagesbranch after switching to GitHub Actions.When Pages is deployed via GitHub Actions, the
gh-pagesbranch is no longer needed and should be deleted to avoid confusion.
- repomatic.lint_repo.check_workflow_permissions()[source]¶
Check that workflows with custom jobs declare
permissions: {}.Thin-caller workflows (all jobs use
uses:to call a reusable workflow) inherit permissions from the called workflow and do not need a top-levelpermissionskey. Workflows that define their ownsteps:should declarepermissions: {}to follow the principle of least privilege.
- repomatic.lint_repo.run_repo_lint(package_name=None, repo_name=None, is_sphinx=False, project_description=None, keywords=None, repo=None, has_pat=False, has_virustotal_key=False, nuitka_active=False, sha=None)[source]¶
Run all repository lint checks.
Emits GitHub Actions annotations for each check result.
- Parameters:
is_sphinx (
bool) – Whether the project uses Sphinx documentation.project_description (
str|None) – Description from pyproject.toml.keywords (
list[str] |None) – Keywords list from pyproject.toml.has_pat (
bool) – WhetherGH_TOKENcontainsREPOMATIC_PAT.has_virustotal_key (
bool) – WhetherVIRUSTOTAL_API_KEYis configured.
- Return type:
- Returns:
Exit code (0 for success, 1 for errors).
repomatic.mailmap module¶
- repomatic.mailmap.MAILMAP_PATH = PosixPath('.mailmap')¶
Canonical path to the
.mailmapfile in the repository root.
- class repomatic.mailmap.Record(canonical='', aliases=<factory>, pre_comment='')[source]¶
Bases:
objectA mailmap identity mapping entry.
- class repomatic.mailmap.Mailmap[source]¶
Bases:
objectHelpers to manipulate
.mailmapfiles..mailmapfile format is documented on Git website.Initialize the mailmap with an empty list of records.
- parse(content)[source]¶
Parse mailmap content and add it to the current list of records.
Each non-empty, non-comment line is considered a mapping entry.
The preceding lines of a mapping entry are kept attached to it as pre-comments, so the layout will be preserved on rendering, during which records are sorted.
- Return type:
- property git_contributors: set[str][source]¶
Returns the set of all contributors found in the Git commit history.
No normalization happens: all variations of authors and committers strings attached to all commits are considered.
For format output syntax, see: https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emaNem
repomatic.metadata module¶
Extract metadata from repository and Python projects to be used by GitHub workflows.
This module solves a fundamental limitation of GitHub Actions: a workflow run is
triggered by a singular event, which might encapsulate multiple commits. GitHub only
exposes github.event.head_commit (the most recent commit), but workflows often need
to process all commits in the push event.
This is critical for releases, where two commits are pushed together:
[changelog] Release vX.Y.Z— the release commit to be tagged and published[changelog] Post-release bump vX.Y.Z → vX.Y.Z— bumps version for the next dev cycle
Since github.event.head_commit only sees the post-release bump, this module extracts
the full commit range from the push event and identifies release commits that need
special handling (tagging, PyPI publishing, GitHub release creation).
The following variables are printed to the environment file:
```text is_bot=false new_commits=346ce664f055fbd042a25ee0b7e96702e95 6f27db47612aaee06fdf08744b09a9f5f6c2 release_commits=6f27db47612aaee06fdf08744b09a9f5f6c2 mailmap_exists=true gitignore_exists=true python_files=”.github/update_mailmap.py” “.github/metadata.py” “setup.py” json_files= yaml_files=”config.yaml” “.github/workflows/lint.yaml” “.github/workflows/test.yaml” workflow_files=”.github/workflows/lint.yaml” “.github/workflows/test.yaml” doc_files=”changelog.md” “readme.md” “docs/license.md” markdown_files=”changelog.md” “readme.md” “docs/license.md” image_files= zsh_files= is_python_project=true package_name=click-extra project_description=📦 Extra colorful clickable helpers for the CLI. mypy_params=–python-version 3.7 current_version=2.0.1 released_version=2.0.0 is_sphinx=true active_autodoc=true release_notes=[🐍 Available on PyPI](https://pypi.org/project/click-extra/2.21.3). new_commits_matrix={
- “commit”: [
“346ce664f055fbd042a25ee0b7e96702e95”, “6f27db47612aaee06fdf08744b09a9f5f6c2”
], “include”: [
- {
“commit”: “346ce664f055fbd042a25ee0b7e96702e95”, “short_sha”: “346ce66”, “current_version”: “2.0.1”
}, {
“commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “short_sha”: “6f27db4”, “current_version”: “2.0.0”
}
]
} release_commits_matrix={
“commit”: [“6f27db47612aaee06fdf08744b09a9f5f6c2”], “include”: [
- {
“commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “short_sha”: “6f27db4”, “current_version”: “2.0.0”
}
]
} build_targets=[
- {
“target”: “linux-arm64”, “os”: “ubuntu-24.04-arm”, “platform_id”: “linux”, “arch”: “arm64”, “extension”: “bin”
}, {
“target”: “linux-x64”, “os”: “ubuntu-24.04”, “platform_id”: “linux”, “arch”: “x64”, “extension”: “bin”
}, {
“target”: “macos-arm64”, “os”: “macos-26”, “platform_id”: “macos”, “arch”: “arm64”, “extension”: “bin”
}, {
“target”: “macos-x64”, “os”: “macos-26-intel”, “platform_id”: “macos”, “arch”: “x64”, “extension”: “bin”
}, {
“target”: “windows-arm64”, “os”: “windows-11-arm”, “platform_id”: “windows”, “arch”: “arm64”, “extension”: “exe”
}, {
“target”: “windows-x64”, “os”: “windows-2025”, “platform_id”: “windows”, “arch”: “x64”, “extension”: “exe”
}
] nuitka_matrix={
- “os”: [
“ubuntu-24.04-arm”, “ubuntu-24.04”, “macos-26”, “macos-26-intel”, “windows-11-arm”, “windows-2025”
], “entry_point”: [“mpm”], “commit”: [
“346ce664f055fbd042a25ee0b7e96702e95”, “6f27db47612aaee06fdf08744b09a9f5f6c2”
], “include”: [
- {
“target”: “linux-arm64”, “os”: “ubuntu-24.04-arm”, “platform_id”: “linux”, “arch”: “arm64”, “extension”: “bin”
}, {
“target”: “linux-x64”, “os”: “ubuntu-24.04”, “platform_id”: “linux”, “arch”: “x64”, “extension”: “bin”
}, {
“target”: “macos-arm64”, “os”: “macos-26”, “platform_id”: “macos”, “arch”: “arm64”, “extension”: “bin”
}, {
“target”: “macos-x64”, “os”: “macos-26-intel”, “platform_id”: “macos”, “arch”: “x64”, “extension”: “bin”
}, {
“target”: “windows-arm64”, “os”: “windows-11-arm”, “platform_id”: “windows”, “arch”: “arm64”, “extension”: “exe”
}, {
“target”: “windows-x64”, “os”: “windows-2025”, “platform_id”: “windows”, “arch”: “x64”, “extension”: “exe”
}, {
“entry_point”: “mpm”, “cli_id”: “mpm”, “module_id”: “meta_package_manager.__main__”, “callable_id”: “main”, “module_path”: “meta_package_manager”
}, {
“commit”: “346ce664f055fbd042a25ee0b7e96702e95”, “short_sha”: “346ce66”, “current_version”: “2.0.0”
}, {
“commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “short_sha”: “6f27db4”, “current_version”: “1.9.1”
}, {
“os”: “ubuntu-24.04-arm”, “entry_point”: “mpm”, “commit”: “346ce664f055fbd042a25ee0b7e96702e95”, “bin_name”: “mpm-linux-arm64.bin”
}, {
“os”: “ubuntu-24.04-arm”, “entry_point”: “mpm”, “commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “bin_name”: “mpm-linux-arm64.bin”
}, {
“os”: “ubuntu-24.04”, “entry_point”: “mpm”, “commit”: “346ce664f055fbd042a25ee0b7e96702e95”, “bin_name”: “mpm-linux-x64.bin”
}, {
“os”: “ubuntu-24.04”, “entry_point”: “mpm”, “commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “bin_name”: “mpm-linux-x64.bin”
}, {
“os”: “macos-26”, “entry_point”: “mpm”, “commit”: “346ce664f055fbd042a25ee0b7e96702e95”, “bin_name”: “mpm-macos-arm64.bin”
}, {
“os”: “macos-26”, “entry_point”: “mpm”, “commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “bin_name”: “mpm-macos-arm64.bin”
}, {
“os”: “macos-26-intel”, “entry_point”: “mpm”, “commit”: “346ce664f055fbd042a25ee0b7e96702e95”, “bin_name”: “mpm-macos-x64.bin”
}, {
“os”: “macos-26-intel”, “entry_point”: “mpm”, “commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “bin_name”: “mpm-macos-x64.bin”
}, {
“os”: “windows-11-arm”, “entry_point”: “mpm”, “commit”: “346ce664f055fbd042a25ee0b7e96702e95”, “bin_name”: “mpm-windows-arm64.bin”
}, {
“os”: “windows-11-arm”, “entry_point”: “mpm”, “commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “bin_name”: “mpm-windows-arm64.bin”
}, {
“os”: “windows-2025”, “entry_point”: “mpm”, “commit”: “346ce664f055fbd042a25ee0b7e96702e95”, “bin_name”: “mpm-windows-x64.exe”
}, {
“os”: “windows-2025”, “entry_point”: “mpm”, “commit”: “6f27db47612aaee06fdf08744b09a9f5f6c2”, “bin_name”: “mpm-windows-x64.exe”
}, {“state”: “stable”}
]
}¶
Warning
Fields with serialized lists and dictionaries, like new_commits_matrix,
build_targets or nuitka_matrix, are pretty-printed in the example above for
readability. They are inlined in the actual output and not formatted this way.
- class repomatic.metadata.Dialect(*values)[source]¶
Bases:
StrEnumOutput dialect for metadata serialization.
- github = 'github'¶
- github_json = 'github-json'¶
- json = 'json'¶
- repomatic.metadata.METADATA_KEYS_HEADERS = ('Key', 'Description')¶
Column headers for the metadata keys reference table.
- repomatic.metadata.metadata_keys_reference()[source]¶
Build the metadata keys reference as table rows.
Returns a list of
(key, description)tuples for all keys produced byMetadata.dump(), including[tool.repomatic]config fields that are exposed as metadata outputs.
- repomatic.metadata.HEREDOC_FIELDS: Final[frozenset[str]] = frozenset({'release_notes', 'release_notes_with_admonition'})¶
Metadata fields that should always use heredoc format in GitHub Actions output.
Some fields may contain special characters (brackets, parentheses, emojis, or potential newlines) that can break GitHub Actions parsing when using simple
key=valueformat. These fields will use the heredoc delimiter format regardless of whether they currently contain multiple lines.
- repomatic.metadata.is_version_bump_allowed(part)[source]¶
Check if a version bump of the specified part is allowed.
This prevents double version increments within a development cycle. A bump is blocked if the version has already been bumped (but not released) since the last tagged release.
For example: - Last release:
v5.0.1, current:5.0.2→ minor bump allowed - Last release:v5.0.1, current:5.1.0→ minor bump NOT allowed (bumped) - Last release:v5.0.1, current:6.0.0→ major bump NOT allowed (bumped)Note
When tags are not available (e.g., due to race conditions between workflows), this function falls back to parsing version from recent commit messages.
- class repomatic.metadata.JSONMetadata(*, skipkeys=False, ensure_ascii=True, check_circular=True, allow_nan=True, sort_keys=False, indent=None, separators=None, default=None)[source]¶
Bases:
JSONEncoderCustom JSON encoder for metadata serialization.
Constructor for JSONEncoder, with sensible defaults.
If skipkeys is false, then it is a TypeError to attempt encoding of keys that are not str, int, float, bool or None. If skipkeys is True, such items are simply skipped.
If ensure_ascii is true, the output is guaranteed to be str objects with all incoming non-ASCII and non-printable characters escaped. If ensure_ascii is false, the output can contain non-ASCII and non-printable characters.
If check_circular is true, then lists, dicts, and custom encoded objects will be checked for circular references during encoding to prevent an infinite recursion (which would cause an RecursionError). Otherwise, no such check takes place.
If allow_nan is true, then NaN, Infinity, and -Infinity will be encoded as such. This behavior is not JSON specification compliant, but is consistent with most JavaScript based encoders and decoders. Otherwise, it will be a ValueError to encode such floats.
If sort_keys is true, then the output of dictionaries will be sorted by key; this is useful for regression tests to ensure that JSON serializations can be compared on a day-to-day basis.
If indent is a non-negative integer, then JSON array elements and object members will be pretty-printed with that indent level. An indent level of 0 will only insert newlines. None is the most compact representation.
If specified, separators should be an (item_separator, key_separator) tuple. The default is (’, ‘, ‘: ‘) if indent is
Noneand (‘,’, ‘: ‘) otherwise. To get the most compact JSON representation, you should specify (‘,’, ‘:’) to eliminate whitespace.If specified, default is a function that gets called for objects that can’t otherwise be serialized. It should return a JSON encodable version of the object or raise a
TypeError.- default(o)[source]¶
Implement this method in a subclass such that it returns a serializable object for
o, or calls the base implementation (to raise aTypeError).For example, to support arbitrary iterators, you could implement default like this:
def default(self, o): try: iterable = iter(o) except TypeError: pass else: return list(iterable) # Let the base class default method raise the TypeError return super().default(o)
- Return type:
- class repomatic.metadata.Metadata[source]¶
Bases:
objectMetadata class.
Implemented as a singleton: every
Metadata()call returns the same instance within a process. This is safe because env vars and project files do not change during a single CLI invocation. Usereset()in test teardown to discard the cached instance between tests.Initialize internal variables.
- classmethod reset()[source]¶
Discard the singleton so the next call creates a fresh instance.
Intended for test teardown only. Production code should never call this.
- Return type:
- pyproject_path = PosixPath('pyproject.toml')¶
- sphinx_conf_path = PosixPath('docs/conf.py')¶
- property github_event: dict[str, Any][source]¶
Load the GitHub event payload from
GITHUB_EVENT_PATH.GitHub Actions automatically sets
GITHUB_EVENT_PATHto a JSON file containing the complete webhook event payload.
- git_deepen(commit_hash, max_attempts=10, deepen_increment=50)[source]¶
Deepen a shallow clone until the provided
commit_hashis found.Progressively fetches more commits from the current repository until the specified commit is found or max attempts is reached.
Returns
Trueif the commit was found,Falseotherwise.- Return type:
- commit_matrix(commits)[source]¶
Pre-compute a matrix of commits.
Danger
This method temporarily modify the state of the repository to compute version metadata from the past.
To prevent any loss of uncommitted data, it stashes and unstash the local changes between checkouts.
The list of commits is augmented with long and short SHA values, as well as current version. Most recent commit is first, oldest is last.
Returns a ready-to-use matrix structure:
- property event_type: WorkflowEvent | None[source]¶
Returns the type of event that triggered the workflow run.
Caution
This property is based on a crude heuristics as it only looks at the value of the
GITHUB_BASE_REFenvironment variable. Which is only set when the event that triggers a workflow run is either pull_request or pull_request_target.Todo
Add detection of all workflow trigger events.
- property event_actor: str | None[source]¶
Returns the GitHub login of the user that triggered the workflow run.
- property event_sender_type: str | None[source]¶
Returns the type of the user that triggered the workflow run.
- property is_bot: bool[source]¶
Returns
Trueif the workflow was triggered by a bot or automated process.This is useful to only run some jobs on human-triggered events. Or skip jobs triggered by bots to avoid infinite loops.
Also detects Renovate PRs by branch name pattern (
renovate/*), which handles cases where Renovate runs as a user account rather than therenovate[bot]app.
- property head_branch: str | None[source]¶
Returns the head branch name for pull request events.
For pull request events, this is the source branch name (e.g.,
update-mailmap). For push events, returnsNonesince there’s no head branch concept.The branch name is extracted from the
GITHUB_HEAD_REFenvironment variable, which is only set for pull request events.
- property event_name: str | None[source]¶
Returns the name of the event that triggered the workflow.
Reads
GITHUB_EVENT_NAME. This is the raw event name (e.g.,"push","pull_request","workflow_run"), as opposed toevent_typewhich returns aWorkflowEventenum based on heuristics.
- property job_name: str | None[source]¶
Returns the ID of the current job in the workflow.
Reads
GITHUB_JOB.
- property ref_name: str | None[source]¶
Returns the short ref name of the branch or tag.
Reads
GITHUB_REF_NAME.
- property repo_name: str | None[source]¶
Returns the repository name without owner prefix.
Derived from
repo_slugby splitting on/.
- property is_awesome: bool[source]¶
Whether this is an awesome-list repository.
Detected by the
awesome-prefix on the repository name.
- property repo_owner: str | None[source]¶
Returns the repository owner.
Reads
GITHUB_REPOSITORY_OWNER, falling back to the owner component ofrepo_slug.
- property repo_slug: str | None[source]¶
Returns the
owner/nameslug for the current repository.Resolution order:
GITHUB_REPOSITORYenv var (CI),gh repo view(authenticated local), git remote URL parsing (offline fallback).
- property repo_url: str | None[source]¶
Returns the full URL to the repository.
Derived from
server_urlandrepo_slug.
- property run_id: str | None[source]¶
Returns the unique ID of the current workflow run.
Reads
GITHUB_RUN_ID.
- property run_number: str | None[source]¶
Returns the run number for the current workflow.
Reads
GITHUB_RUN_NUMBER.
- property server_url: str[source]¶
Returns the GitHub server URL.
Reads
GITHUB_SERVER_URL, defaulting tohttps://github.com.
- property sha: str | None[source]¶
Returns the commit SHA that triggered the workflow.
Reads
GITHUB_SHA.
- property triggering_actor: str | None[source]¶
Returns the login of the user that initiated the workflow run.
Reads
GITHUB_TRIGGERING_ACTOR. This differs fromevent_actor(GITHUB_ACTOR) when a workflow is re-run by a different user.
- property workflow_ref: str | None[source]¶
Returns the full workflow reference.
Reads
GITHUB_WORKFLOW_REF. The format isowner/repo/.github/workflows/name.yaml@refs/heads/branch.
- property changed_files: tuple[str, ...] | None[source]¶
Returns the list of files changed in the current event’s commit range.
Uses
git diff --name-onlybetween the start and end of the commit range. ReturnsNoneif no commit range is available (e.g., outside CI).
- property binary_affecting_paths: tuple[str, ...][source]¶
Path prefixes that affect compiled binaries for this project.
Combines the static
BINARY_AFFECTING_PATHS(common files likepyproject.toml,uv.lock,tests/) with project-specific source directories derived from[project.scripts]inpyproject.toml.For example, a project with
mpm = "meta_package_manager.__main__:main"addsmeta_package_manager/as an affecting path. This makes the check reusable across downstream repositories without hardcoding source directories.
- property skip_binary_build: bool[source]¶
Returns
Trueif binary builds should be skipped for this event.Binary builds are expensive and time-consuming. This property identifies contexts where the changes cannot possibly affect compiled binaries, allowing workflows to skip Nuitka compilation jobs.
Two mechanisms are checked:
Branch name — PRs from known non-code branches (documentation,
.mailmap,.gitignore, etc.) are skipped.Changed files — Push events where all changed files fall outside
binary_affecting_pathsare skipped. This avoids ~2h of Nuitka builds for documentation-only commits tomain.
- property commit_range: tuple[str | None, str] | None[source]¶
Range of commits bundled within the triggering event.
A workflow run is triggered by a singular event, which might encapsulate one or more commits. This means the workflow will only run once on the last commit, even if multiple new commits were pushed.
This is critical for releases where two commits are pushed together:
[changelog] Release vX.Y.Z— the release commit[changelog] Post-release bump vX.Y.Z → vX.Y.Z— the post-release bump
Without extracting the full commit range, the release commit would be missed since
github.event.head_commitonly exposes the post-release bump.This property also enables processing each commit individually when we want to keep a carefully constructed commit history. The typical example is a pull request that is merged upstream but we’d like to produce artifacts (builds, packages, etc.) for each individual commit.
The default
GITHUB_SHAenvironment variable is not enough as it only points to the last commit. We need to inspect the commit history to find all new ones. New commits need to be fetched differently inpushandpull_requestevents.See also
See also
Pull request events on GitHub are a bit complex, see: The Many SHAs of a GitHub Pull Request.
- property current_commit_matrix: Matrix | None[source]¶
Pre-computed matrix with long and short SHA values of the current commit.
- property new_commits: tuple[Commit, ...] | None[source]¶
Returns list of all
Commitobjects bundled within the triggering event.This extracts all commits from the push event, not just
head_commit. For releases, this typically includes both the release commit and the post-release bump commit, allowing downstream jobs to process each one.Commits are returned in chronological order (oldest first, most recent last).
- property new_commits_matrix: Matrix | None[source]¶
Pre-computed matrix with long and short SHA values of new commits.
- property release_commits: tuple[Commit, ...] | None[source]¶
Returns list of
Commitobjects to be tagged within the triggering event.This filters
new_commitsto find release commits that need special handling: tagging, PyPI publishing, and GitHub release creation.This is essential because when a release is pushed,
github.event.head_commitonly exposes the post-release bump commit, not the release commit. By extracting all commits from the event (vianew_commits) and filtering for release commits here, we ensure the release workflow can properly identify and process the[changelog] Release vX.Y.Zcommit.We cannot identify a release commit based on the presence of a
vX.Y.Ztag alone. That’s because the tag is not present in theprepare-releasepull request produced by thechangelog.yamlworkflow. The tag is created later by therelease.yamlworkflow, when the pull request is merged tomain.Our best option is to identify a release based on the full commit message, using the template from the
changelog.yamlworkflow.
- property release_commits_matrix: Matrix | None[source]¶
Pre-computed matrix with long and short SHA values of release commits.
- property gitignore_parser: Parser | None[source]¶
Returns a parser for the
.gitignorefile, if it exists.
- glob_files(*patterns)[source]¶
Return all file path matching the
patterns.Patterns are glob patterns supporting
**for recursive search, and!for negation.All directories are traversed, whether they are hidden (i.e. starting with a dot
.) or not, including symlinks.Skips:
files which does not exists
directories
broken symlinks
files matching patterns specified by
.gitignorefile
Returns both hidden and non-hidden files.
All files are normalized to their absolute path, so that duplicates produced by symlinks are ignored.
File path are returned as relative to the current working directory if possible, or as absolute path otherwise.
The resulting list of file paths is sorted.
- property json_files: list[Path][source]¶
Returns a list of JSON files.
Note
JSON5 files are excluded because Biome doesn’t support them.
- property image_files: list[Path][source]¶
Returns a list of image files.
Covers the formats handled by
repomatic format-images: JPEG, PNG, WebP, and AVIF. Seerepomatic.imagesfor the optimization tools.
- property shfmt_files: list[Path][source]¶
Returns a list of shell files that
shfmtcan reliably format.shfmtsupports the following dialects (-lnflag):bash: GNU Bourne Again Shell.
posix: POSIX Shell (
/bin/sh).mksh: MirBSD Korn Shell.
bats: Bash Automated Testing System.
Zsh is excluded.
shfmtadded experimental Zsh support in v3.13.0 but it fails on common constructs:for var (list)short-form loops andfor ... { }brace-delimited loops. See mvdan/sh#1203 for upstream tracking.Files are excluded by extension (
.zsh,.zshrc, etc.) and by shebang (any.shfile whose first line referenceszsh).
- property is_python_project[source]¶
Returns
Trueif repository is a Python project.Presence of a
pyproject.tomlfile that respects the standards is enough to consider the project as a Python one.
- property pyproject_toml: dict[str, Any][source]¶
Returns the raw parsed content of
pyproject.toml.Returns an empty dict if the file does not exist.
- property pyproject: StandardMetadata | None[source]¶
Returns metadata stored in the
pyproject.tomlfile.Returns
Noneif thepyproject.tomldoes not exists or does not respects the PEP standards.Warning
Some third-party apps have their configuration saved into
pyproject.tomlfile, but that does not means the project is a Python one. For that, thepyproject.tomlneeds to respect the PEPs.
- property config: Config[source]¶
Returns the
[tool.repomatic]section frompyproject.toml.Merges user configuration with defaults from
Config.
- property nuitka_entry_points: list[str][source]¶
Entry points selected for Nuitka binary compilation.
Reads
[tool.repomatic].nuitka.entry-pointsfrompyproject.toml. When empty (the default), deduplicates by callable target: keeps the first entry point for each uniquemodule:callablepair, so alias entry points (like bothmpmandmeta-package-managerpointing to the same function) don’t produce duplicate binaries. Unrecognized CLI IDs are logged as warnings and discarded.
- property unstable_targets: set[str][source]¶
Nuitka build targets allowed to fail without blocking the release.
Reads
[tool.repomatic].nuitka.unstable-targetsfrompyproject.toml. Defaults to an empty set.Unrecognized target names are logged as warnings and discarded.
- property script_entries: list[tuple[str, str, str]][source]¶
Returns a list of tuples containing the script name, its module and callable.
Results are derived from the script entries of
pyproject.toml. So that:Will yields the following list:
- property mypy_params: list[str] | None[source]¶
Generates
mypyparameters.Mypy needs to be fed with this parameter:
--python-version 3.x.Extracts the minimum Python version from the project’s
requires-pythonspecifier. Only takesmajor.minorinto account.
- static get_current_version()[source]¶
Returns the current version as managed by bump-my-version.
Same as calling the CLI:
- property current_version: str | None[source]¶
Returns the current version.
Current version is fetched from the
bump-my-versionconfiguration file.During a release, two commits are bundled into a single push event:
[changelog] Release vX.Y.Z— freezes the version to the release number[changelog] Post-release bump vX.Y.Z → vX.Y.Z— bumps to the next dev version
In this situation, the current version returned is the one from the most recent commit (the post-release bump), which represents the next development version. Use
released_versionto get the version from the release commit.
- property released_version: str | None[source]¶
Returns the version of the release commit.
During a release push event, this extracts the version from the
[changelog] Release vX.Y.Zcommit, which is distinct fromcurrent_version(the post-release bump version). This is used for tagging, PyPI publishing, and GitHub release creation.Returns
Noneif no release commit is found in the current event.
- property minor_bump_allowed: bool[source]¶
Check if a minor version bump is allowed.
This prevents double version increments within a development cycle.
- property major_bump_allowed: bool[source]¶
Check if a major version bump is allowed.
This prevents double version increments within a development cycle.
- property nuitka_matrix: Matrix | None[source]¶
Pre-compute a matrix for Nuitka compilation workflows.
Combine the variations of: - release commits only (during releases) or all new commits (otherwise) - all entry points - for the 3 main OSes - for a set of architectures
Returns a ready-to-use matrix structure, where each variation is augmented with specific extra parameters by the way of matching parameters in the include directive.
- property test_matrix: Matrix[source]¶
Full test matrix for non-PR events.
Combines all runner OS images and Python versions, excluding known incompatible combinations. Marks development Python versions as unstable so CI can use
continue-on-error. Per-project config from[tool.repomatic.test-matrix]is applied last.
- property test_matrix_pr: Matrix[source]¶
Reduced test matrix for pull requests.
Skips experimental Python versions and redundant architecture variants to reduce CI load on PRs. Per-project config excludes and includes from
[tool.repomatic.test-matrix]are applied, but variations are not (to keep the PR matrix small).
- property release_notes: str | None[source]¶
Generate notes to be attached to the GitHub release.
Renders the
github-releasestemplate with changelog content for the version. The template is the single place that defines the release body layout.
- property release_notes_with_admonition: str | None[source]¶
Generate release notes with a pre-computed availability admonition.
Builds the same body as
release_notes, but injects a> [!NOTE]admonition linking to PyPI and GitHub even beforefix-changeloghas a chance to updatechangelog.md. This lets thecreate-releaseworkflow step include the admonition at creation time whenpublish-pypisucceeds.Returns
Nonewhen the project is not on PyPI, has no changelog, or has no version to release.
- static format_github_value(value)[source]¶
Transform Python value to GitHub-friendly, JSON-like, console string.
Renders: - str as-is - None into empty string - bool into lower-cased string - Matrix into JSON string - Iterable of mixed strings and Path into a serialized space-separated
string, where Path items are double-quoted
other Iterable into a JSON string
- Return type:
repomatic.pypi module¶
PyPI API client for package metadata lookups.
Provides a shared HTTP client and domain-specific query functions used by
repomatic.changelog (release dates, yanked status) and
repomatic.renovate (source repository discovery).
- repomatic.pypi.PYPI_API_URL = 'https://pypi.org/pypi/{package}/json'¶
PyPI JSON API URL for fetching all release metadata for a package.
- repomatic.pypi.PYPI_PROJECT_URL = 'https://pypi.org/project/{package}/{version}/'¶
PyPI project page URL for a specific version.
- repomatic.pypi.PYPI_LABEL = '🐍 PyPI'¶
Display label for PyPI releases in admonitions.
- class repomatic.pypi.PyPIRelease(date: str, yanked: bool, package: str)[source]¶
Bases:
NamedTupleRelease metadata for a single version from PyPI.
Create new instance of PyPIRelease(date, yanked, package)
- repomatic.pypi.get_release_dates(package)[source]¶
Get upload dates and yanked status for all versions from PyPI.
Fetches the package metadata in a single API call. For each version, selects the earliest upload time across all distribution files as the canonical release date. A version is considered yanked only if all of its files are yanked.
- Parameters:
package (
str) – The PyPI package name.- Return type:
- Returns:
Dict mapping version strings to
PyPIReleasetuples. Empty dict if the package is not found or the request fails.
- repomatic.pypi.get_source_url(package)[source]¶
Discover the GitHub repository URL for a PyPI package.
Queries the PyPI JSON API and scans
project_urlsfor keys that typically point to a source repository on GitHub.
repomatic.pyproject module¶
Utilities for reading and interpreting pyproject.toml metadata.
Provides standalone functions for extracting project name and source paths
from pyproject.toml. These functions have no dependency on the
Metadata singleton and can be used independently.
- repomatic.pyproject.derive_source_paths(pyproject_data=None)[source]¶
Derive source code directory name from
[project.name].Converts the project name to its importable form by replacing hyphens with underscores — the universal Python convention that all build backends (setuptools, hatchling, flit, uv) follow by default. For example,
name = "extra-platforms"yields["extra_platforms"].
repomatic.registry module¶
Declarative registry of all components managed by the init subcommand.
Every resource the init subcommand can create, sync, or merge is declared
here as a Component subclass instance in the COMPONENTS tuple.
Each component carries all its metadata: what kind it is, whether it is
selected by default, which files it manages, and any per-file properties like
repo-scope gating or config keys.
All derived constants (ALL_COMPONENTS, COMPONENT_FILES,
REUSABLE_WORKFLOWS, SKILL_PHASES, etc.) are computed from this single
registry in repomatic.init_project.
- class repomatic.registry.InitDefault(*values)[source]¶
Bases:
EnumHow
inittreats the component when no explicit CLI args are given.- INCLUDE = 1¶
Included by default (e.g., changelog, renovate, workflows).
- EXCLUDE = 2¶
In default set but excluded unless explicitly included (e.g., labels, skills).
- AUTO = 3¶
Auto-included only for matching repos (e.g., awesome-template).
- EXPLICIT = 4¶
Only included when explicitly requested (e.g., tool configs).
- class repomatic.registry.SyncMode(*values)[source]¶
Bases:
EnumHow a
ToolConfigComponentbehaves when the section already exists.- BOOTSTRAP = 1¶
Insert once, skip if section already exists (e.g., ruff, pytest).
- ONGOING = 2¶
Replace template content on every sync, preserving local additions (e.g., bumpversion).
- class repomatic.registry.RepoScope(*values)[source]¶
Bases:
EnumWhich repository types a file entry applies to.
Scope restrictions are defaults: they apply during bare
repomatic initbut are bypassed when components are explicitly named on the CLI or covered by[tool.repomatic] include.- ALL = 1¶
Included in all repository types.
- AWESOME_ONLY = 2¶
Only for awesome-* repositories.
- NON_AWESOME = 3¶
Only for non-awesome repositories.
- class repomatic.registry.FileEntry(source, target='', file_id='', scope=RepoScope.ALL, config_key='', config_default=False, reusable=True, phase='')[source]¶
Bases:
objectA single file managed within a component.
- target: str = ''¶
Relative output path in the target repository. Defaults to
source(root-level file).
- file_id: str = ''¶
Identifier for file-level
--include/--exclude. Defaults to the filename portion oftarget.
- class repomatic.registry.Component(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False)[source]¶
Bases:
objectBase class for all init components.
- init_default: InitDefault = 1¶
How
inittreats this component when no explicit CLI selection is made.
- scope: RepoScope = 1¶
Which repository types get this component. Checked at the component level during auto-exclusion, complementing the file-level
FileEntry.scope.
- config_default: bool = True¶
Value assumed when
config_keyis absent from config.Truemeans opt-out (included unless disabled).
- class repomatic.registry.BundledComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False)[source]¶
Bases:
ComponentFiles copied from
repomatic/data/to a target path.
- class repomatic.registry.WorkflowComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False)[source]¶
Bases:
ComponentThin-caller generation and header sync.
- class repomatic.registry.ToolConfigComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False, source_file='', tool_section='', insert_after=(), insert_before=(), sync_mode=SyncMode.BOOTSTRAP, preserved_keys=())[source]¶
Bases:
ComponentMerged into
pyproject.toml.- insert_before: tuple[str, ...] = ()¶
Sections to insert before in
pyproject.toml(ifinsert_afternot found).
- class repomatic.registry.TemplateComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False)[source]¶
Bases:
ComponentDirectory tree (awesome-template).
- class repomatic.registry.GeneratedComponent(name, description, init_default=InitDefault.INCLUDE, scope=RepoScope.ALL, files=(), config_key='', config_default=True, keep_unmodified=False, target='')[source]¶
Bases:
ComponentProduced from code (changelog).
Unlike bundled components, generated components have no
filestuple. Thetargetfield records the output path so the auto-exclusion logic can detect stale copies on disk.
- repomatic.registry.COMPONENTS: tuple[Component, ...] = (BundledComponent(name='labels', description='Label config files (labels.toml + labeller rules)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='labeller-content-based.yaml', target='.github/labeller-content-based.yaml', file_id='labeller-content-based.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='labeller-file-based.yaml', target='.github/labeller-file-based.yaml', file_id='labeller-file-based.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='labels.toml', target='labels.toml', file_id='labels.toml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='')), config_key='', config_default=True, keep_unmodified=False), BundledComponent(name='codecov', description='Codecov PR comment config (.github/codecov.yaml)', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.NON_AWESOME: 3>, files=(FileEntry(source='codecov.yaml', target='.github/codecov.yaml', file_id='codecov.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''),), config_key='', config_default=True, keep_unmodified=True), BundledComponent(name='renovate', description='Renovate config (renovate.json5)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.NON_AWESOME: 3>, files=(FileEntry(source='renovate.json5', target='renovate.json5', file_id='renovate.json5', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''),), config_key='', config_default=True, keep_unmodified=False), BundledComponent(name='skills', description='Claude Code skill definitions (.claude/skills/)', init_default=<InitDefault.EXCLUDE: 2>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='skill-av-false-positive.md', target='.claude/skills/av-false-positive/SKILL.md', file_id='av-false-positive', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release'), FileEntry(source='skill-awesome-triage.md', target='.claude/skills/awesome-triage/SKILL.md', file_id='awesome-triage', scope=<RepoScope.AWESOME_ONLY: 2>, config_key='', config_default=False, reusable=True, phase='Maintenance'), FileEntry(source='skill-babysit-ci.md', target='.claude/skills/babysit-ci/SKILL.md', file_id='babysit-ci', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Quality'), FileEntry(source='skill-benchmark-update.md', target='.claude/skills/benchmark-update/SKILL.md', file_id='benchmark-update', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development'), FileEntry(source='skill-brand-assets.md', target='.claude/skills/brand-assets/SKILL.md', file_id='brand-assets', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development'), FileEntry(source='skill-file-bug-report.md', target='.claude/skills/file-bug-report/SKILL.md', file_id='file-bug-report', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance'), FileEntry(source='skill-repomatic-audit.md', target='.claude/skills/repomatic-audit/SKILL.md', file_id='repomatic-audit', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance'), FileEntry(source='skill-repomatic-changelog.md', target='.claude/skills/repomatic-changelog/SKILL.md', file_id='repomatic-changelog', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release'), FileEntry(source='skill-repomatic-deps.md', target='.claude/skills/repomatic-deps/SKILL.md', file_id='repomatic-deps', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development'), FileEntry(source='skill-repomatic-init.md', target='.claude/skills/repomatic-init/SKILL.md', file_id='repomatic-init', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Setup'), FileEntry(source='skill-repomatic-lint.md', target='.claude/skills/repomatic-lint/SKILL.md', file_id='repomatic-lint', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Quality'), FileEntry(source='skill-repomatic-release.md', target='.claude/skills/repomatic-release/SKILL.md', file_id='repomatic-release', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Release'), FileEntry(source='skill-repomatic-sync.md', target='.claude/skills/repomatic-sync/SKILL.md', file_id='repomatic-sync', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Setup'), FileEntry(source='skill-repomatic-test.md', target='.claude/skills/repomatic-test/SKILL.md', file_id='repomatic-test', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Quality'), FileEntry(source='skill-repomatic-topics.md', target='.claude/skills/repomatic-topics/SKILL.md', file_id='repomatic-topics', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Development'), FileEntry(source='skill-sphinx-docs-sync.md', target='.claude/skills/sphinx-docs-sync/SKILL.md', file_id='sphinx-docs-sync', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance'), FileEntry(source='skill-translation-sync.md', target='.claude/skills/translation-sync/SKILL.md', file_id='translation-sync', scope=<RepoScope.AWESOME_ONLY: 2>, config_key='', config_default=False, reusable=True, phase='Maintenance'), FileEntry(source='skill-upstream-audit.md', target='.claude/skills/upstream-audit/SKILL.md', file_id='upstream-audit', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase='Maintenance')), config_key='', config_default=True, keep_unmodified=True), WorkflowComponent(name='workflows', description='Thin-caller workflow files', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.ALL: 1>, files=(FileEntry(source='autofix.yaml', target='.github/workflows/autofix.yaml', file_id='autofix.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='autolock.yaml', target='.github/workflows/autolock.yaml', file_id='autolock.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='cancel-runs.yaml', target='.github/workflows/cancel-runs.yaml', file_id='cancel-runs.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='changelog.yaml', target='.github/workflows/changelog.yaml', file_id='changelog.yaml', scope=<RepoScope.NON_AWESOME: 3>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='debug.yaml', target='.github/workflows/debug.yaml', file_id='debug.yaml', scope=<RepoScope.NON_AWESOME: 3>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='docs.yaml', target='.github/workflows/docs.yaml', file_id='docs.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='labels.yaml', target='.github/workflows/labels.yaml', file_id='labels.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='lint.yaml', target='.github/workflows/lint.yaml', file_id='lint.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='release.yaml', target='.github/workflows/release.yaml', file_id='release.yaml', scope=<RepoScope.NON_AWESOME: 3>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='renovate.yaml', target='.github/workflows/renovate.yaml', file_id='renovate.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=True, phase=''), FileEntry(source='tests.yaml', target='.github/workflows/tests.yaml', file_id='tests.yaml', scope=<RepoScope.ALL: 1>, config_key='', config_default=False, reusable=False, phase=''), FileEntry(source='unsubscribe.yaml', target='.github/workflows/unsubscribe.yaml', file_id='unsubscribe.yaml', scope=<RepoScope.ALL: 1>, config_key='notification.unsubscribe', config_default=False, reusable=True, phase='')), config_key='', config_default=True, keep_unmodified=False), TemplateComponent(name='awesome-template', description='Boilerplate for awesome-* repositories', init_default=<InitDefault.AUTO: 3>, scope=<RepoScope.ALL: 1>, files=(), config_key='awesome-template.sync', config_default=True, keep_unmodified=False), GeneratedComponent(name='changelog', description='Minimal changelog.md', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.NON_AWESOME: 3>, files=(), config_key='', config_default=True, keep_unmodified=False, target='changelog.md'), ToolConfigComponent(name='lychee', description='Lychee link checker configuration', init_default=<InitDefault.INCLUDE: 1>, scope=<RepoScope.AWESOME_ONLY: 2>, files=(), config_key='', config_default=True, keep_unmodified=False, source_file='lychee.toml', tool_section='tool.lychee', insert_after=(), insert_before=(), sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=()), ToolConfigComponent(name='ruff', description='Ruff linter/formatter configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, source_file='ruff.toml', tool_section='tool.ruff', insert_after=('tool.uv', 'tool.uv.build-backend'), insert_before=('tool.pytest',), sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=()), ToolConfigComponent(name='pytest', description='Pytest test configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, source_file='pytest.toml', tool_section='tool.pytest', insert_after=('tool.ruff', 'tool.ruff.format'), insert_before=('tool.mypy',), sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=()), ToolConfigComponent(name='mypy', description='Mypy type checking configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, source_file='mypy.toml', tool_section='tool.mypy', insert_after=('tool.pytest',), insert_before=('tool.nuitka', 'tool.bumpversion'), sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=()), ToolConfigComponent(name='mdformat', description='mdformat Markdown formatter configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, source_file='mdformat.toml', tool_section='tool.mdformat', insert_after=('tool.coverage',), insert_before=('tool.bumpversion',), sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=()), ToolConfigComponent(name='bumpversion', description='bump-my-version configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, source_file='bumpversion.toml', tool_section='tool.bumpversion', insert_after=('tool.mdformat', 'tool.nuitka', 'tool.mypy'), insert_before=('tool.typos',), sync_mode=<SyncMode.ONGOING: 2>, preserved_keys=('current_version',)), ToolConfigComponent(name='typos', description='Typos spell checker configuration', init_default=<InitDefault.EXPLICIT: 4>, scope=<RepoScope.ALL: 1>, files=(), config_key='', config_default=True, keep_unmodified=False, source_file='typos.toml', tool_section='tool.typos', insert_after=('tool.bumpversion',), insert_before=('tool.pytest',), sync_mode=<SyncMode.BOOTSTRAP: 1>, preserved_keys=()))¶
The component registry.
Single source of truth for all resources managed by the
initsubcommand. Every component declares its kind, selection default, file entries, and behavioral flags. All derived constants are computed from this tuple.
- repomatic.registry.DEFAULT_REPO: str = 'kdeldycke/repomatic'¶
Default upstream repository for reusable workflows.
- repomatic.registry.UPSTREAM_SOURCE_GLOB: str = 'repomatic/**'¶
Path glob for the upstream source directory in canonical workflows.
Canonical workflow
paths:filters use this glob to match source code changes. In downstream repos, this is replaced with the project’s own source directory.
- repomatic.registry.UPSTREAM_SOURCE_PREFIX: str = 'repomatic/'¶
Path prefix for upstream-specific files in canonical workflows.
Paths starting with this prefix (but not matching
UPSTREAM_SOURCE_GLOB) are dropped in downstream thin callers because they reference files that only exist in the upstream repository (e.g.,repomatic/data/renovate.json5).
- repomatic.registry.SKILL_PHASE_ORDER: tuple[str, ...] = ('Setup', 'Development', 'Quality', 'Maintenance', 'Release')¶
Canonical display order for lifecycle phases in
list-skillsoutput.
- repomatic.registry.ALL_COMPONENTS: dict[str, str] = {'awesome-template': 'Boilerplate for awesome-* repositories', 'bumpversion': 'bump-my-version configuration', 'changelog': 'Minimal changelog.md', 'codecov': 'Codecov PR comment config (.github/codecov.yaml)', 'labels': 'Label config files (labels.toml + labeller rules)', 'lychee': 'Lychee link checker configuration', 'mdformat': 'mdformat Markdown formatter configuration', 'mypy': 'Mypy type checking configuration', 'pytest': 'Pytest test configuration', 'renovate': 'Renovate config (renovate.json5)', 'ruff': 'Ruff linter/formatter configuration', 'skills': 'Claude Code skill definitions (.claude/skills/)', 'typos': 'Typos spell checker configuration', 'workflows': 'Thin-caller workflow files'}¶
All available init components.
- repomatic.registry.REUSABLE_WORKFLOWS: tuple[str, ...] = ('autofix.yaml', 'autolock.yaml', 'cancel-runs.yaml', 'changelog.yaml', 'debug.yaml', 'docs.yaml', 'labels.yaml', 'lint.yaml', 'release.yaml', 'renovate.yaml', 'unsubscribe.yaml')¶
Workflow filenames that support
workflow_calltriggers.
- repomatic.registry.NON_REUSABLE_WORKFLOWS: frozenset[str] = frozenset({'tests.yaml'})¶
Workflows without
workflow_callthat cannot be used as thin callers.
- repomatic.registry.ALL_WORKFLOW_FILES: tuple[str, ...] = ('autofix.yaml', 'autolock.yaml', 'cancel-runs.yaml', 'changelog.yaml', 'debug.yaml', 'docs.yaml', 'labels.yaml', 'lint.yaml', 'release.yaml', 'renovate.yaml', 'tests.yaml', 'unsubscribe.yaml')¶
All workflow filenames (reusable and non-reusable).
- repomatic.registry.SKILL_PHASES: dict[str, str] = {'av-false-positive': 'Release', 'awesome-triage': 'Maintenance', 'babysit-ci': 'Quality', 'benchmark-update': 'Development', 'brand-assets': 'Development', 'file-bug-report': 'Maintenance', 'repomatic-audit': 'Maintenance', 'repomatic-changelog': 'Release', 'repomatic-deps': 'Development', 'repomatic-init': 'Setup', 'repomatic-lint': 'Quality', 'repomatic-release': 'Release', 'repomatic-sync': 'Setup', 'repomatic-test': 'Quality', 'repomatic-topics': 'Development', 'sphinx-docs-sync': 'Maintenance', 'translation-sync': 'Maintenance', 'upstream-audit': 'Maintenance'}¶
Maps skill names to lifecycle phases for display grouping.
- repomatic.registry.FILE_SELECTOR_COMPONENTS: tuple[str, ...] = ('labels', 'codecov', 'renovate', 'skills', 'workflows')¶
Components that support file-level
component/fileselectors.
- repomatic.registry.COMPONENT_HELP_TABLE: str = ' labels Label config files (labels.toml + labeller rules)\n codecov Codecov PR comment config (.github/codecov.yaml)\n renovate Renovate config (renovate.json5)\n skills Claude Code skill definitions (.claude/skills/)\n workflows Thin-caller workflow files\n awesome-template Boilerplate for awesome-* repositories\n changelog Minimal changelog.md\n lychee Lychee link checker configuration\n ruff Ruff linter/formatter configuration\n pytest Pytest test configuration\n mypy Mypy type checking configuration\n mdformat mdformat Markdown formatter configuration\n bumpversion bump-my-version configuration\n typos Typos spell checker configuration'¶
Formatted component table for CLI help text.
- repomatic.registry.valid_file_ids(component)[source]¶
Return valid file identifiers for a component.
Components with file entries report their declared
file_idvalues. Returns an empty set for components without file-level selection (e.g., changelog, tool configs).
- repomatic.registry.excluded_rel_path(component, file_id)[source]¶
Map a component and file identifier to its relative output path.
Returns
Nonewhen the identifier cannot be resolved (e.g., for tool config components that have no file-level exclusion support).
- repomatic.registry.parse_component_entries(entries, *, context='entry')[source]¶
Parse component entries into full-component and file-level sets.
Bare names (no
/) must be component names fromALL_COMPONENTS. Qualifiedcomponent/identifierentries target individual files. RaisesValueErroron unknown entries.Used by both the
excludeconfig path and the CLI positional selection, with context controlling error message wording.
repomatic.release_prep module¶
Prepare a release by updating changelog, citation, readme, and workflow files.
A release cycle produces exactly two commits that must be merged via “Rebase and merge” (never squash):
Freeze commit (
[changelog] Release vX.Y.Z):Strips the
.dev0suffix from the version.Finalizes the changelog date and comparison URL.
Freezes workflow action references:
@main→@vX.Y.Z.Freezes CLI invocations:
--from . repomatic→'repomatic==X.Y.Z'.Freezes readme binary download URLs to versioned release paths.
Sets the release date in
citation.cff.
Unfreeze commit (
[changelog] Post-release bump vX.Y.Z → vX.Y.(Z+1)):Reverts action references:
@vX.Y.Z→@main.Reverts CLI invocations back to local source for dogfooding.
Bumps the version with a
.dev0suffix.Adds a new unreleased changelog section.
The auto-tagging job in release.yaml depends on these being separate
commits — it uses release_commits_matrix to identify and tag only the
freeze commit. Squash-merging would collapse both into one, breaking the
tagging logic. See the detect-squash-merge job for the safeguard.
Both operations are idempotent: re-running on an already-frozen or already-unfrozen tree is a no-op.
- class repomatic.release_prep.ReleasePrep(changelog_path=None, citation_path=None, workflow_dir=None, readme_path=None, default_branch='main')[source]¶
Bases:
objectPrepare files for a release by updating dates, URLs, and removing warnings.
- property current_version: str[source]¶
Extract current version from bump-my-version config in pyproject.toml.
- set_citation_release_date()[source]¶
Update the
date-releasedfield in citation.cff.- Return type:
- Returns:
True if the file was modified.
- freeze_workflow_urls()[source]¶
Replace workflow URLs from default branch to versioned tag.
This is part of the freeze step: it freezes workflow references to the release tag so released versions reference immutable URLs.
Replaces
/repomatic/{default_branch}/with/repomatic/v{version}/and/repomatic/.github/actions/...@{default_branch}with/repomatic/.github/actions/...@v{version}in all workflow YAML files.- Return type:
- Returns:
Number of files modified.
- freeze_readme_download_urls(version)[source]¶
Replace binary download URLs in readme with versioned release paths.
This is part of the freeze step: it freezes readme download links to a specific GitHub release so users get explicit, versioned URLs instead of the
/releases/latest/download/redirect.Handles two input forms:
Initial (never frozen):
/releases/latest/download/repomatic-linux-arm64.binPreviously frozen:
/releases/download/v6.0.0/repomatic-6.0.0-linux-arm64.bin
Both are transformed to:
/releases/download/v{version}/repomatic-{version}-linux-arm64.binNote
No unfreeze method is needed. Unlike workflow URLs (which toggle
@main↔@vX.Y.Z), readme download URLs ratchet forward — they always point to a specific release. After unfreeze, the readme still shows the last release’s URLs, which is correct for users wanting stable binaries.
- freeze_cli_version(version)[source]¶
Replace local source CLI invocations with a frozen PyPI version.
This is part of the freeze step: it freezes
repomaticinvocations to a specific PyPI version so the released workflow files reference a published package. Downstream repos that check out a tagged release will install from PyPI rather than expecting a local source tree.Replaces
--from . repomaticwith'repomatic=={version}'in all workflow YAML files, and withrepomatic=={version}(unquoted) inrenovate.json5files (where the command lives insidebash -c '...'and single quotes would break the outer quoting).Comment lines in YAML (starting with
#) and JSON5 (starting with//) are skipped to avoid corrupting explanatory comments.
- unfreeze_cli_version()[source]¶
Replace frozen PyPI CLI invocations with local source.
This is part of the unfreeze step: it reverts
repomaticinvocations back to local source (--from . repomatic) for the next development cycle onmain.Replaces
'repomatic==X.Y.Z'(quoted, in YAML) andrepomatic==X.Y.Z(unquoted, inrenovate.json5) with--from . repomatic.Comment lines are skipped (see
freeze_cli_version()).- Return type:
- Returns:
Number of files modified.
- unfreeze_workflow_urls()[source]¶
Replace workflow URLs from versioned tag back to default branch.
This is part of the unfreeze step: it reverts workflow references back to the default branch for the next development cycle.
Replaces
/repomatic/v{version}/with/repomatic/{default_branch}/and/repomatic/.github/actions/...@v{version}with/repomatic/.github/actions/...@{default_branch}in all workflow YAML files.- Return type:
- Returns:
Number of files modified.
- prepare_release(update_workflows=False)[source]¶
Run all freeze steps to prepare the release commit.
repomatic.renovate module¶
Renovate-related utilities for GitHub Actions workflows.
This module provides utilities for managing Renovate prerequisites and
migrating from Dependabot to Renovate. uv lock file operations (version
parsing, noise detection, vulnerability auditing) live in repomatic.uv.
- repomatic.renovate.RENOVATE_CONFIG_PATH = PosixPath('renovate.json5')¶
Canonical path to the Renovate configuration file.
- class repomatic.renovate.CheckFormat(*values)[source]¶
Bases:
StrEnumOutput format for Renovate prerequisite checks.
- github = 'github'¶
- json = 'json'¶
- text = 'text'¶
- class repomatic.renovate.RenovateCheckResult(renovate_config_exists, dependabot_config_path, dependabot_security_disabled, commit_statuses_permission, contents_permission=True, issues_permission=True, pull_requests_permission=True, vulnerability_alerts_permission=True, workflows_permission=True, repo='')[source]¶
Bases:
objectResult of all Renovate prerequisite checks.
This dataclass holds the results of each check, allowing workflows to consume the data and build dynamic PR bodies or conditional logic.
- to_github_output()[source]¶
Format results for GitHub Actions output.
- Return type:
- Returns:
Multi-line string in key=value format for $GITHUB_OUTPUT.
- repomatic.renovate.get_dependabot_config_path()[source]¶
Get the path to the Dependabot configuration file if it exists.
- repomatic.renovate.check_dependabot_config_absent()[source]¶
Check that no Dependabot version updates config file exists.
Renovate handles dependency updates, so Dependabot should be disabled.
- repomatic.renovate.check_dependabot_security_disabled(repo)[source]¶
Check that Dependabot security updates are disabled.
Renovate creates security PRs instead.
- repomatic.renovate.check_renovate_config_exists()[source]¶
Check if renovate.json5 configuration file exists.
- repomatic.renovate.collect_check_results(repo, sha)[source]¶
Collect all Renovate prerequisite check results.
Runs all checks and returns structured results that can be formatted as JSON or GitHub Actions output.
- Parameters:
- Return type:
- Returns:
RenovateCheckResult with all check outcomes.
- repomatic.renovate.run_migration_checks(repo, sha)[source]¶
Run Renovate migration prerequisite checks with console output.
Checks for: - Missing renovate.json5 configuration - Existing Dependabot configuration - Dependabot security updates enabled - PAT permissions: commit statuses, contents, issues, pull requests,
vulnerability alerts, workflows
repomatic.rst_to_myst module¶
Convert sphinx-apidoc RST output to MyST markdown.
Note
The converter handles only the narrow RST subset that sphinx-apidoc
generates: section headings (title + underline), automodule directives
with indented options, and structural headers like Submodules.
Autodoc directives cannot be used as native MyST directives because they
perform internal rST nested parsing that requires an rST parser context
only {eval-rst} provides. See MyST-Parser #587.
- repomatic.rst_to_myst.convert_apidoc_rst_to_myst(content)[source]¶
Convert
sphinx-apidocRST to MyST markdown with{eval-rst}blocks.
- repomatic.rst_to_myst.convert_rst_files_in_directory(directory)[source]¶
Convert
sphinx-apidocRST files to MyST markdown in the given directory.For each
.rstfile containing.. automodule::directives:If a
.mdfile with the same stem exists, delete the.rst(the existing markdown takes precedence).Otherwise, convert the RST content to MyST and write a
.mdfile, then delete the.rst.
repomatic.sponsor module¶
Check if a GitHub user is a sponsor of another user or organization.
Uses the GitHub GraphQL API via the gh CLI to query sponsorship data.
Supports both user and organization owners, with pagination for accounts
that have more than 100 sponsors.
When run in GitHub Actions, defaults are read from
Metadata for owner and repository, and from
GITHUB_EVENT_PATH for the author and issue/PR number.
- repomatic.sponsor.get_default_owner()[source]¶
Get the repository owner from CI context.
Delegates to
Metadata.repo_owner.
- repomatic.sponsor.get_default_repo()[source]¶
Get the repository slug from CI context.
Delegates to
Metadata.repo_slug.
- repomatic.sponsor.get_default_author()[source]¶
Get the issue/PR author from the GitHub event payload.
- repomatic.sponsor.get_default_number()[source]¶
Get the issue/PR number from the GitHub event payload.
- repomatic.sponsor.is_pull_request()[source]¶
Check if the current event is a pull request.
- Return type:
- repomatic.sponsor.get_sponsors(owner: str) frozenset[str][source]¶
Get all sponsors for a user or organization.
Tries the user query first, then falls back to organization query.
Results are cached to avoid redundant API calls within the same process.
repomatic.test_matrix module¶
Test matrix constants for CI workflows.
Defines the GitHub-hosted runner images and Python versions used to build
test matrices. Separating these from
repomatic.metadata makes the CI matrix configuration self-contained
and easier to update when runner images or Python releases change.
- repomatic.test_matrix.TEST_RUNNERS_FULL = ('ubuntu-24.04-arm', 'ubuntu-slim', 'macos-26', 'macos-26-intel', 'windows-11-arm', 'windows-2025')¶
GitHub-hosted runners for the full test matrix.
Two variants per platform (one per architecture) to keep the matrix small. See available images.
- repomatic.test_matrix.TEST_RUNNERS_PR = ('ubuntu-slim', 'macos-26', 'windows-2025')¶
Reduced runner set for pull request test matrices.
One runner per platform, skipping redundant architecture variants.
- repomatic.test_matrix.TEST_PYTHON_FULL = ('3.10', '3.14', '3.14t', '3.15')¶
Python versions for the full test matrix.
Intermediate versions (3.11, 3.12, 3.13) are skipped to reduce CI load.
- repomatic.test_matrix.TEST_PYTHON_PR = ('3.10', '3.14')¶
Reduced Python version set for pull request test matrices.
Skips experimental versions (free-threaded, development) to reduce CI load.
- repomatic.test_matrix.UNSTABLE_PYTHON_VERSIONS: Final[frozenset[str]] = frozenset({'3.15'})¶
Python versions still in development.
Jobs using these versions run with
continue-on-errorin CI.
repomatic.test_plan module¶
- exception repomatic.test_plan.SkippedTest[source]¶
Bases:
ExceptionRaised when a test case should be skipped.
- class repomatic.test_plan.CLITestCase(cli_parameters=<factory>, skip_platforms=<factory>, only_platforms=<factory>, timeout=None, exit_code=None, strip_ansi=False, output_contains=<factory>, stdout_contains=<factory>, stderr_contains=<factory>, output_regex_matches=<factory>, stdout_regex_matches=<factory>, stderr_regex_matches=<factory>, output_regex_fullmatch=None, stdout_regex_fullmatch=None, stderr_regex_fullmatch=None, execution_trace=None)[source]¶
Bases:
object- skip_platforms: Trait | Group | str | None | Iterable[Trait | Group | str | None | Iterable[_TNestedReferences]]¶
- only_platforms: Trait | Group | str | None | Iterable[Trait | Group | str | None | Iterable[_TNestedReferences]]¶
- execution_trace: str | None = None¶
User-friendly rendering of the CLI command execution and its output.
- run_cli_test(command, additional_skip_platforms, default_timeout)[source]¶
Run a CLI command and check its output against the test case.
The provided
commandcan be either:a path to a binary or script to execute;
a command name to be searched in the
PATH,a command line with arguments to be parsed and executed by the shell.
Todo
Add support for environment variables.
Todo
Add support for proper mixed <stdout>/<stderr> stream as a single, intertwined output.
repomatic.tool_runner module¶
Unified tool runner with managed config resolution.
Provides repomatic run <tool> — a single entry point that installs an
external tool at a pinned version, resolves its configuration through a strict
4-level precedence chain, translates [tool.X] sections from
pyproject.toml into the tool’s native format, and invokes the tool with
the resolved config.
Important
Config resolution precedence (first match wins, no merging):
Native config file — tool’s own config file in the repo.
``[tool.X]`` in ``pyproject.toml`` — translated to native format.
Bundled default — from
repomatic/data/.Bare invocation — no config at all.
- repomatic.tool_runner.GENERATED_HEADER_TEMPLATE = 'Generated by {command} v{version} - https://github.com/kdeldycke/repomatic'¶
Template for the first line of generated-file headers.
Used by both CLI commands (e.g.
sync-mailmap) and the tool runner (e.g.run shfmt) to stamp files with provenance. Format fields:command(full command path) andversion(package version).
- repomatic.tool_runner.generated_header(command, comment_prefix='# ')[source]¶
Return a generated-by header block with timestamp.
- class repomatic.tool_runner.ArchiveFormat(*values)[source]¶
Bases:
EnumArchive format for binary tool downloads.
- RAW = 'raw'¶
- TAR_GZ = 'tar.gz'¶
- TAR_XZ = 'tar.xz'¶
- ZIP = 'zip'¶
- tarfile_mode()[source]¶
Return the
tarfile.openmode string for this format.- Raises:
ValueError – If called on a non-tar format.
- Return type:
Literal['r:gz','r:xz']
- class repomatic.tool_runner.NativeFormat(*values)[source]¶
Bases:
EnumTarget format for
[tool.X]translation.- YAML = 'yaml'¶
- TOML = 'toml'¶
- JSON = 'json'¶
- EDITORCONFIG = 'editorconfig'¶
- repomatic.tool_runner.PlatformKey¶
A
(platform_or_group, architecture)pair used as binary lookup key.The platform element can be a single
Platform(likeMACOS) or aGroup(likeLINUX, which matches any Linux distribution). The architecture is always a concreteArchitecture.Resolution order in
BinarySpec.resolve_platform():Exact Platform match (
current_platform() == key_platform).Group membership (
current_platform() in key_group), preferring the group with fewest members (most specific).
alias of
tuple[Platform|Group,Architecture]
- class repomatic.tool_runner.BinarySpec(urls, checksums, archive_format, archive_executable=None, strip_components=0)[source]¶
Bases:
objectPlatform-specific binary download specification.
Keys are
PlatformKeytuples pairing an extra-platformsPlatformorGroupwith anArchitecture. This lets callers use broad groups (LINUXmatches any distro) or specific platforms (DEBIAN) with full detection heuristics from extra-platforms.Hint
Structural integrity checks (key types, checksum format, URL placeholders, strip_components consistency) are enforced in
test_tool_spec_integrity. If the registry becomes user-configurable in the future, move these checks to__post_init__.- urls: dict[tuple[Platform | Group, Architecture], str]¶
Platform key to URL template mapping. URLs use
{version}placeholders.
- checksums: dict[tuple[Platform | Group, Architecture], str]¶
Platform key to SHA-256 hex digest mapping.
- archive_format: ArchiveFormat | dict[tuple[Platform | Group, Architecture] | Platform | Group, ArchiveFormat]¶
Archive format of the downloaded file.
A single
ArchiveFormatapplies to every platform. A dict maps platform specifiers to formats, allowing mixed archives in one spec:archive_format={ALL_PLATFORMS: ArchiveFormat.TAR_GZ, WINDOWS: ArchiveFormat.ZIP}
Dict keys follow the same resolution as
resolve_platform(): exactPlatformKeytuple first, then barePlatformequality, thenGroupmembership (smallest group wins).
- archive_executable: str | None = None¶
Path of the executable inside the archive.
Nonedefaults to the tool name. ForRAWformat, used as the final filename.
- resolve_platform()[source]¶
Match the current environment against registered platform keys.
Uses
current_platform()andcurrent_architecture()from extra-platforms, inheriting its full detection heuristics.- Return type:
tuple[Platform|Group,Architecture]- Returns:
The matching
PlatformKey.- Raises:
RuntimeError – If no key matches the current environment.
- get_archive_format(key)[source]¶
Return the archive format for the given platform key.
When
archive_formatis a singleArchiveFormat, returns it directly. When it is a dict, resolves in order: exactPlatformKeytuple, bare Platform equality, then Group membership (smallest group wins).- Return type:
- class repomatic.tool_runner.ToolSpec(name, display_name=None, version='', package=None, executable=None, native_config_files=(), config_flag=None, native_format=NativeFormat.YAML, default_config=None, reads_pyproject=False, default_flags=(), ci_flags=(), with_packages=(), needs_venv=False, computed_params=None, config_after_subcommand=False, post_process=None, binary=None, source_url=None, config_docs_url=None, cli_docs_url=None)[source]¶
Bases:
objectSpecification for an external tool managed by repomatic.
Hint
Structural integrity checks (name format, version format, flag conventions, field consistency) are enforced in
test_tool_spec_integrity. If the registry becomes user-configurable in the future, move these checks to__post_init__.Hint
CLI parser quirks for
config_after_subcommandTools that use subcommands (
tool <subcmd> [flags] [files]) may requireconfig_flagto appear after the subcommand name, depending on the CLI parser framework:clap (Rust): global flags accepted before or after the subcommand. No special handling needed. Used by: ruff, labelmaker.
cobra (Go): root-level flags inherited by all subcommands, accepted in both positions. No special handling needed. Used by: gitleaks.
click (Python): global flags accepted before or after the subcommand. No special handling needed. Used by: bump-my-version.
bpaf (Rust):
#[bpaf(external)]fields are scoped inside the subcommand variant, sotool <subcmd> --flagworks buttool --flag <subcmd>does not. Setconfig_after_subcommand=True. Used by: biome.
- name: str¶
Tool identity: CLI name for
repomatic run <name>, default PyPI package name, and default executable name.
- display_name: str | None = None¶
Human-readable name with proper casing for documentation (like
'Biome','Gitleaks').Nonedefaults toname.
- package: str | None = None¶
PyPI package name.
Nonedefaults toname. Only set when the package name differs from the tool name.
- executable: str | None = None¶
Executable name if different from the tool name.
Nonedefaults to the registry key.
- native_config_files: tuple[str, ...] = ()¶
Config filenames the tool auto-discovers, checked in order.
Paths relative to repo root (e.g.,
'zizmor.yaml','.github/actionlint.yaml'). Empty for tools with no config file.
- config_flag: str | None = None¶
CLI flag to pass a config file path (e.g.,
'--config','--config-file').Noneif the tool only reads from fixed paths.
- native_format: NativeFormat = 'yaml'¶
Target format for
[tool.X]translation.
- default_config: str | None = None¶
Filename in
repomatic/data/for bundled defaults, stored innative_format.Noneif no bundled default exists.
- reads_pyproject: bool = False¶
Whether the tool natively reads
[tool.X]frompyproject.toml.When
Trueand[tool.X]exists inpyproject.toml, repomatic skips Level 2 translation (the tool reads it directly). Resolution still falls through to Level 3 (bundled default) and Level 4 (bare) when no config is found.
- with_packages: tuple[str, ...] = ()¶
Extra packages installed alongside the tool (e.g., mdformat plugins).
Passed as
--with <pkg>to uvx.
- needs_venv: bool = False¶
If
True, useuv run(project venv) instead ofuvx(isolated).Required when the tool imports project code (mypy, pytest).
- computed_params: Callable[[Metadata], list[str]] | None = None¶
Callable that receives a
Metadatainstance and returns extra CLI args derived from project metadata (e.g., mypy’s--python-versionfromrequires-python).Noneif no computed params.
- config_after_subcommand: bool = False¶
Insert
config_flagafter the first token ofextra_args.Needed for tools whose CLI parser (e.g., bpaf) scopes global options inside the subcommand, so
tool subcommand --config-path Xis valid buttool --config-path X subcommandis not. WhenTrue,config_argsare spliced after the first element ofextra_args(the subcommand name).
- post_process: Callable[[Sequence[str]], None] | None = None¶
Callback invoked on
extra_argsafter the tool exits successfully.Intended for temporary workarounds that fix known upstream formatting bugs in-place. Remove the callback once upstream ships the fix.
- binary: BinarySpec | None = None¶
Platform-specific binary download spec. When set, the tool is downloaded as a binary instead of installed via
uvxoruv run.
- repomatic.tool_runner.get_data_file_path(filename)[source]¶
Yield the filesystem path of a bundled data file.
Unlike
init_project.get_data_content()which returns string content, this yields aPathsuitable for passing to external tools via--config <path>. The path is valid only within the context manager.
- repomatic.tool_runner.load_pyproject_tool_section(tool_name)[source]¶
Load
[tool.<tool_name>]frompyproject.tomlin the current directory.
- repomatic.tool_runner.resolve_config(spec, tool_config=None)[source]¶
Resolve config for a tool using the 4-level precedence chain.
- Parameters:
- Return type:
- Returns:
Tuple of (extra CLI args for config, path to clean up). The path is
Nonewhen no cleanup is needed (cache-based configs persist across runs). Non-Nonepaths are CWD files written for tools that have no--configflag.
- repomatic.tool_runner.binary_tool_context(name, no_cache=False)[source]¶
Download a binary tool and yield its executable path.
For tools invoked indirectly by repomatic commands (e.g., labelmaker called by
sync-labels) rather than viarun_tool(). Downloads once; the binary stays valid for the context’s duration. On a cache hit the yielded path points to the cache and the staging directory is empty.
- repomatic.tool_runner.run_tool(name, extra_args=(), version=None, checksum=None, skip_checksum=False, no_cache=False)[source]¶
Run an external tool with managed config resolution.
- Parameters:
name (
str) – Tool name (must be inTOOL_REGISTRY).extra_args (
Sequence[str]) – Extra arguments passed through to the tool.checksum (
str|None) – Override the SHA-256 checksum for the current platform.skip_checksum (
bool) – Skip SHA-256 verification entirely.no_cache (
bool) – Bypass the binary cache whenTrue.
- Return type:
- Returns:
The tool’s exit code.
- repomatic.tool_runner.resolve_config_source(spec)[source]¶
Return a human-readable description of the active config source.
Used by
repomatic run --listto show which precedence level is active for each tool in the current repo.- Return type:
- repomatic.tool_runner.find_unmodified_configs()[source]¶
Find native config files identical to their bundled defaults.
Iterates over every tool in
TOOL_REGISTRYthat has adefault_config. For each, checks whether any of itsnative_config_filesexists on disk and is content-identical to the bundled default after trailing-whitespace normalization.The normalization (
rstrip() + "\n") matches the convention used by_init_config_fileswhen writing files duringinit.
repomatic.uv module¶
uv lock file operations and vulnerability auditing.
This module provides utilities for managing uv.lock files: parsing versions,
detecting timestamp noise, computing diff tables, auditing for vulnerabilities,
and fetching release notes from GitHub.
- repomatic.uv.uv_cmd(subcommand, *, frozen=False)[source]¶
Build a
uv <subcommand>command prefix with standard flags.Always includes
--no-progress. Adds--frozenwhen requested (appropriate forrun,export,sync— not forlock).
- repomatic.uv.GITHUB_API_RELEASE_BY_TAG_URL = 'https://api.github.com/repos/{owner}/{repo}/releases/tags/{tag}'¶
GitHub API URL for fetching a single release by tag name.
- repomatic.uv.RELEASE_NOTES_MAX_LENGTH = 2000¶
Maximum characters per package release body before truncation.
- class repomatic.uv.VulnerablePackage(name, current_version, advisory_id, advisory_title, fixed_version, advisory_url)[source]¶
Bases:
objectA single vulnerability advisory for a Python package.
- repomatic.uv.parse_uv_audit_output(output)[source]¶
Parse the text output of
uv auditinto structured vulnerability data.Handles multiple advisories per package and packages without a known fix version. Unrecognized lines are silently skipped.
- Parameters:
output (
str) – Combined stdout/stderr fromuv audit.- Return type:
- Returns:
A list of
VulnerablePackageentries.
- repomatic.uv.format_vulnerability_table(vulns)[source]¶
Format vulnerability data as a markdown table.
- Parameters:
vulns (
list[VulnerablePackage]) – List ofVulnerablePackageentries.- Return type:
- Returns:
A markdown string with a
### Vulnerabilitiesheading and table, or an empty string if no vulnerabilities are provided.
- repomatic.uv.is_lock_diff_only_timestamp_noise(lock_path)[source]¶
Check whether the only changes in a lock file are timestamp noise.
Note
This is a workaround for uv writing a new resolved timestamp on every
uv lockrun even when no packages changed. See uv#18155.Runs
git diffon the given path and inspects every added/removed content line. ReturnsTrueonly when all changed lines match theexclude-newer-packagetimestamp pattern (timestamp =/span =).
- repomatic.uv.revert_lock_if_noise(lock_path)[source]¶
Revert a lock file if its only changes are timestamp noise.
Calls
is_lock_diff_only_timestamp_noise()and, ifTrue, runsgit checkoutto discard the noise changes.Note
In Renovate’s
postUpgradeTaskscontext, the revert is ineffective because Renovate captures file content after its ownuv lock --upgrademanager step beforepostUpgradeTasksrun, and commits its cached content regardless of working tree changes.
- repomatic.uv.add_exclude_newer_packages(pyproject_path, packages)[source]¶
Add packages to
[tool.uv].exclude-newer-packageinpyproject.toml.Persists
"0 day"exemptions for the given packages so that subsequentuv lock --upgraderuns (e.g. from thesync-uv-lockjob) do not downgrade security-fixed packages back to versions within theexclude-newercooldown window.Skips packages that already have an entry. Returns
Trueif the file was modified.
- repomatic.uv.prune_stale_exclude_newer_packages(pyproject_path, lock_path)[source]¶
Remove stale entries from
[tool.uv].exclude-newer-package.Note
This is a workaround until uv supports native pruning. See uv#18792.
An entry is stale when its locked version’s upload time falls before the
exclude-newercutoff, meaninguv lock --upgradewould resolve to the same (or newer) version without the"0 day"override.Packages without an upload time in the lock file (git or path sources) are treated as permanent exemptions and never pruned.
- repomatic.uv.parse_lock_versions(lock_path)[source]¶
Parse a
uv.lockfile and return a mapping of package names to versions.
- repomatic.uv.parse_lock_upload_times(lock_path)[source]¶
Parse a
uv.lockfile and return a mapping of package names to upload times.Extracts the
upload-timefield from each package’ssdistentry.
- repomatic.uv.parse_lock_exclude_newer(lock_path)[source]¶
Parse the
exclude-newertimestamp from auv.lockfile.
- class repomatic.uv.LockSpecifiers(by_package, by_subgraph)[source]¶
Bases:
objectDependency specifiers extracted from a
uv.lockfile.Two views of the same data, built in a single pass over the lock packages:
by_package{package_name: {dep_name: specifier}}. Every dependency declared by a package (main and dev) keyed by the declaring package name. Used for edge labels in dependency graphs.by_subgraph{subgraph_name: {dep_name: specifier}}. Primary dependencies keyed by dev-group name or extra name. Used for node labels inside subgraphs.
- repomatic.uv.parse_lock_specifiers(lock_path=None, *, lock_data=None)[source]¶
Parse
uv.lockand extract dependency specifiers.A single pass builds two complementary indexes from
[package.metadata].requires-distand[package.metadata.requires-dev]. SeeLockSpecifiersfor the two views returned.- Parameters:
- Return type:
- repomatic.uv.diff_lock_versions(before, after)[source]¶
Compare two version mappings and return the list of changes.
- Parameters:
- Return type:
- Returns:
A sorted list of
(name, old_version, new_version)tuples.old_versionis empty for added packages;new_versionis empty for removed packages.
- repomatic.uv.format_diff_table(changes, upload_times=None, exclude_newer='', comparison_urls=None)[source]¶
Format version changes as a markdown table with heading.
When
upload_timesis provided, a “Released” column is added so reviewers can visually verify that all updated packages respect theexclude-newercutoff. The cutoff itself is shown above the table whenexclude_neweris non-empty.- Parameters:
changes (
list[tuple[str,str,str]]) – List of(name, old_version, new_version)tuples as returned bydiff_lock_versions().upload_times (
dict[str,str] |None) – Optional mapping of package names to ISO 8601 upload-time strings, as returned byparse_lock_upload_times().exclude_newer (
str) – Optionalexclude-newerISO 8601 datetime from the lock file, as returned byparse_lock_exclude_newer().comparison_urls (
dict[str,str] |None) – Optional mapping of package names to GitHub comparison URLs, as returned bybuild_comparison_urls().
- Return type:
- Returns:
A markdown string with a
### Updated packagesheading and table, or an empty string if there are no changes.
- repomatic.uv.get_github_release_body(repo_url, version)[source]¶
Fetch the release notes body for a specific version from GitHub.
Tries
v{version}first (most common for Python packages), then the bare{version}tag.
- repomatic.uv.fetch_release_notes(changes)[source]¶
Fetch release notes for all updated packages.
For each package with a new version, discovers the GitHub repository via PyPI and fetches the release notes from GitHub Releases for all versions in the range
(old, new]. Falls back to a changelog link from PyPIproject_urlswhen no GitHub Release exists.- Parameters:
changes (
list[tuple[str,str,str]]) – List of(name, old_version, new_version)tuples.- Return type:
- Returns:
A dict mapping package names to
(repo_url, versions)tuples whereversionsis a list of(tag, body)pairs sorted ascending. Only packages with at least one non-empty body are included. When a changelog URL is used as fallback,tagis empty andbodycontains a markdown link.
- repomatic.uv.format_release_notes(notes)[source]¶
Render release notes as collapsible
<details>blocks.Follows Renovate’s visual pattern: a “Release notes” heading with one collapsible section per package. Long release bodies are truncated to
RELEASE_NOTES_MAX_LENGTHcharacters with a link to the full release.- Parameters:
notes (
dict[str,tuple[str,list[tuple[str,str]]]]) – A dict mapping package names to(repo_url, versions)tuples whereversionsis a list of(tag, body)pairs, as returned byfetch_release_notes().- Return type:
- Returns:
A markdown string with the release notes section, or an empty string if no notes are available.
- repomatic.uv.build_comparison_urls(changes, notes)[source]¶
Build GitHub comparison URLs from version changes and release notes.
Uses the tag format discovered by
fetch_release_notes()to construct comparison URLs. Only packages with both old and new versions and a known GitHub repository are included.- Parameters:
- Return type:
- Returns:
Dict mapping package names to GitHub comparison URLs.
- repomatic.uv.fix_vulnerable_deps(lock_path)[source]¶
Detect vulnerable packages and upgrade them in the lock file.
Runs
uv auditto detect vulnerabilities, then upgrades each fixable package withuv lock --upgrade-packageusing--exclude-newer-packageto bypass theexclude-newercooldown for security fixes. Also persists the exemptions inpyproject.tomlso that subsequentuv lock --upgraderuns (e.g. from thesync-uv-lockjob) do not downgrade the fixed packages back within the cooldown window.- Parameters:
lock_path (
Path) – Path to theuv.lockfile.- Return type:
- Returns:
A tuple of
(has_fixes, diff_table).has_fixesisTruewhen at least one vulnerable package was upgraded.diff_tableis a markdown-formatted string with vulnerability details and version changes, or an empty string if no fixable vulnerabilities were found.
- class repomatic.uv.SyncResult(reverted, changes, upload_times, exclude_newer)[source]¶
Bases:
objectResult of a
sync-uv-lockoperation.
- repomatic.uv.sync_uv_lock(lock_path)[source]¶
Re-lock with
--upgradeand revert if only timestamp noise changed.First prunes stale
exclude-newer-packageentries frompyproject.toml(entries whose locked version was uploaded before theexclude-newercutoff), then runsuv lock --upgradeto update transitive dependencies. If the resulting diff contains only timestamp noise, revertsuv.lockso no spurious changes are committed.- Parameters:
lock_path (
Path) – Path to theuv.lockfile.- Return type:
- Returns:
A
SyncResultwith structured version change data.
repomatic.virustotal module¶
Upload release binaries to VirusTotal and update GitHub release notes.
Submits compiled binaries (.bin, .exe) to the VirusTotal API for malware
scanning, then appends analysis links to the GitHub release body so users can
verify scan results.
Supports two-phase operation: phase 1 uploads files and writes an initial table with scan links, phase 2 polls for analysis completion and replaces the table with detection statistics.
Note
The free-tier API allows 4 requests per minute. All API calls (uploads and polls) are rate-limited with a sleep between each request.
- repomatic.virustotal.VIRUSTOTAL_GUI_URL = 'https://www.virustotal.com/gui/file/{sha256}'¶
URL template for the VirusTotal file analysis page.
- repomatic.virustotal.VIRUSTOTAL_SECTION_HEADER = '### 🛡️ VirusTotal scans'¶
Markdown header identifying the VirusTotal section in release bodies.
Used for idempotency: if this header is already present, the release body is not modified (unless
replace=Trueis passed toupdate_release_body).
- repomatic.virustotal.DETECTION_PENDING = '*pending*'¶
Placeholder text for the Detections column when analysis is not yet complete.
- class repomatic.virustotal.DetectionStats(malicious, suspicious, undetected, harmless)[source]¶
Bases:
objectDetection statistics from a completed VirusTotal analysis.
Stores only the four categories that constitute a definitive verdict.
type-unsupported,timeout, andfailurefrom the API response are excluded from the total.
- class repomatic.virustotal.ScanResult(filename, sha256, analysis_url, detection_stats=None)[source]¶
Bases:
objectResult of uploading a single file to VirusTotal.
- detection_stats: DetectionStats | None = None¶
Detection statistics, or
Noneif analysis is still pending.
- repomatic.virustotal.scan_files(api_key, file_paths, rate_limit=4)[source]¶
Upload files to VirusTotal and return scan results.
Uses the synchronous
vt.ClientAPI. Sleeps between uploads to respect the free-tier rate limit.
- repomatic.virustotal.poll_detection_stats(api_key, results, rate_limit=4, timeout=600)[source]¶
Poll VirusTotal for detection statistics of previously uploaded files.
Queries
GET /files/{sha256}for each file until analysis completes or the timeout is reached. Respects the free-tier rate limit for all API calls.- Parameters:
api_key (
str) – VirusTotal API key.results (
list[ScanResult]) – Scan results from a previous upload.rate_limit (
int) – Maximum API requests per minute (shared with uploads).timeout (
int) – Maximum seconds to wait for all analyses to complete.
- Return type:
- Returns:
Results with
detection_statspopulated (orNonefor files whose analysis did not complete before the timeout).
- repomatic.virustotal.format_virustotal_section(results, repo='', tag='')[source]¶
Format scan results as a markdown section for a GitHub release body.
When any result has
detection_stats, a three-column table is rendered with a Detections column. Otherwise the simpler two-column format is used.- Parameters:
results (
list[ScanResult]) – Scan results to format.repo (
str) – Repository inowner/repoformat. When provided along withtag, binary names are linked to their GitHub release download URLs.tag (
str) – Release tag (e.g.,v6.11.1).
- Return type:
- Returns:
Markdown string, or empty string if no results.
- repomatic.virustotal.update_release_body(repo, tag, results, replace=False)[source]¶
Append or replace VirusTotal scan links in a GitHub release body.
- Parameters:
repo (
str) – Repository inowner/repoformat.tag (
str) – Release tag (e.g.,v6.11.1).results (
list[ScanResult]) – Scan results to write.replace (
bool) – WhenTrue, replace an existing VirusTotal section instead of skipping. WhenFalse(default), skip if the section is already present.
- Return type:
- Returns:
Trueif the body was updated,Falseif skipped.