Skip to content

kdeldycke/repomatic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

5,098 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

repomatic

Last release Python versions Downloads Unittests status Coverage status

A Python CLI and pyproject.toml configuration that let you release Python packages multiple times a day with only 2-clicks. Designed for uv-based Python projects, but usable for other projects too. The CLI operates through reusable GitHub Actions workflows as its CI delivery mechanism.

Maintainer-in-the-loop: nothing is done behind your back. A PR or issue is created every time a change is proposed or action is needed.

Automates:

  • Version bumping
  • Changelog management
  • Formatting autofix for: Python, Markdown, JSON, typos
  • Linting: Python types with mypy, YAML, zsh, GitHub Actions, workflow security, URLS & redirects, Awesome lists, secrets
  • Compiling of Python binaries for Linux / macOS / Windows on x86_64 & arm64
  • Building of Python packages and upload to PyPI
  • Produce attestations
  • Git version tagging and GitHub release creation
  • Synchronization of: uv.lock, .gitignore, .mailmap and Mermaid dependency graph
  • Auto-locking of inactive closed issues
  • Static image optimization
  • Sphinx documentation building & deployment, and autodoc updates
  • Label management, with file-based and content-based rules
  • Awesome list template synchronization
  • Address GitHub Actions limitations

Quick start

$ cd my-project
$ uvx -- repomatic init
$ git add . && git commit -m "Update repomatic files" && git push

This works for both new and existing repositories β€” managed files (workflows, configs, skills) are always regenerated to the latest version. The only exception is changelog.md, which is never overwritten once it exists. The workflows will start running and guide you through any remaining setup (like creating a WORKFLOW_UPDATE_GITHUB_PAT secret) via issues and PRs in your repository. After that, the autofix workflow handles ongoing sync.

Run repomatic init --help to see available components and options.

repomatic CLI

Try it

Thanks to uv, you can run it in one command, without installation or venv:

$ uvx -- repomatic
Usage: repomatic [OPTIONS] COMMAND [ARGS]...

Options:
  --time / --no-time      Measure and print elapsed execution time.  [default:
                          no-time]
  --color, --ansi / --no-color, --no-ansi
                          Strip out all colors and all ANSI codes from output.
                          [default: color]
  --config CONFIG_PATH    Location of the configuration file. Supports local
                          path with glob patterns or remote URL.  [default:
                          ~/Library/Application Support/repomatic/{*.toml,*.yam
                          l,*.yml,*.json,*.ini,pyproject.toml}]
  --no-config             Ignore all configuration files and only use command
                          line parameters and environment variables.
  --validate-config FILE  Validate the configuration file and exit.
  --show-params           Show all CLI parameters, their provenance, defaults
                          and value, then exit.
  --table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]
                          Rendering style of tables.  [default: rounded-
                          outline]
  --verbosity LEVEL       Either CRITICAL, ERROR, WARNING, INFO, DEBUG.
                          [default: WARNING]
  -v, --verbose           Increase the default WARNING verbosity by one level
                          for each additional repetition of the option.
                          [default: 0]
  --version               Show the version and exit.
  -h, --help              Show this message and exit.

Project setup:
  init                   Bootstrap a repository to use reusable workflows
  metadata               Output project metadata
  config                 Print [tool.repomatic] configuration reference
  workflow               Manage downstream workflow caller files
  update-deps-graph      Generate dependency graph from uv lockfile
  list-skills            List available Claude Code skills
  update-checksums       Update SHA-256 checksums for binary downloads
  format-images          Format images with lossless optimization

Release & versioning:
  changelog              Maintain a Markdown-formatted changelog
  release-prep           Prepare files for a release
  version-check          Check if a version bump is allowed
  git-tag                Create and push a Git tag
  prebake-version        Pre-bake __version__ with Git commit hash

Sync:
  sync-gitignore         Sync .gitignore from gitignore.io templates
  sync-github-releases   Sync GitHub release notes from changelog
  sync-dev-release       Sync rolling dev pre-release on GitHub
  sync-mailmap           Sync Git's .mailmap file with missing contributors
  sync-uv-lock           Re-lock and revert if only timestamp noise changed
  sync-bumpversion       Sync bumpversion config from bundled template
  sync-skills            Sync Claude Code skills from bundled definitions
  sync-awesome-template  Sync awesome-template boilerplate files
  sync-labels            Sync repository labels via labelmaker
  sync-renovate          Sync Renovate config from canonical reference

Linting & checks:
  test-plan              Run a test plan from a file against a binary
  verify-binary          Verify binary architecture using exiftool
  check-renovate         Check Renovate migration prerequisites
  lint-repo              Run repository consistency checks
  lint-changelog         Check changelog dates against release dates
  run                    Run an external tool with managed config

GitHub issues & PRs:
  sponsor-label          Label issues/PRs from GitHub sponsors
  broken-links           Manage broken links issue lifecycle
  setup-guide            Manage setup guide issue lifecycle
  unsubscribe-threads    Unsubscribe from closed, inactive notification threads
  pr-body                Generate PR body with workflow metadata
$ uvx -- repomatic --version
repomatic, version 5.9.1

That's the best way to get started with repomatic and experiment with it.

Executables

To ease deployment, standalone executables of repomatic's latest version are available as direct downloads for several platforms and architectures:

Platform arm64 x86_64
Linux Download repomatic-6.4.1-linux-arm64.bin Download repomatic-6.4.1-linux-x64.bin
macOS Download repomatic-6.4.1-macos-arm64.bin Download repomatic-6.4.1-macos-x64.bin
Windows Download repomatic-6.4.1-windows-arm64.exe Download repomatic-6.4.1-windows-x64.exe

That way you have a chance to try it out without installing Python or uv. Or embed it in your CI/CD pipelines running on minimal images. Or run it on old platforms without worrying about dependency hell.

[tool.repomatic] configuration

Downstream projects can customize workflow behavior by adding a [tool.repomatic] section in their pyproject.toml:

[tool.repomatic]
pypi-package-history = ["old-name", "older-name"]

awesome-template.sync = false
bumpversion.sync = false
dev-release.sync = false
gitignore.sync = false
labels.sync = false
mailmap.sync = false
renovate.sync = false
uv-lock.sync = false

dependency-graph.output = "./docs/assets/dependencies.mmd"
dependency-graph.all-groups = true
dependency-graph.all-extras = true
dependency-graph.no-groups = []
dependency-graph.no-extras = []
dependency-graph.level = 0

gitignore.location = "./.gitignore"
gitignore.extra-categories = ["terraform", "go"]
gitignore.extra-content = '''
junit.xml

# Claude Code
.claude/
'''

exclude = ["skills", "workflows/debug.yaml", "zizmor"]

labels.extra-files = ["https://example.com/my-labels.toml"]
labels.extra-file-rules = "docs:\n  - docs/**"
labels.extra-content-rules = "security:\n  - '(CVE|vulnerability)'"

nuitka.enabled = false
nuitka.extra-args = [
  "--include-data-files=my_pkg/data/*.json=my_pkg/data/",
]
nuitka.unstable-targets = ["linux-arm64", "windows-arm64"]

test-plan.file = "./tests/cli-test-plan.yaml"
test-plan.timeout = 120
test-plan.inline = "- args: --version"

workflow.sync = false
workflow.source-paths = ["extra_platforms"]
Option Type Default Description
awesome-template.sync bool true Whether awesome-template sync is enabled for this project.
bumpversion.sync bool true Whether bumpversion config sync is enabled for this project.
dependency-graph.all-extras bool true Whether to include all optional extras in the graph.
dependency-graph.all-groups bool true Whether to include all dependency groups in the graph.
dependency-graph.level int (none) Maximum depth of the dependency graph.
dependency-graph.no-extras list[str] [] Optional extras to exclude from the graph.
dependency-graph.no-groups list[str] [] Dependency groups to exclude from the graph.
dependency-graph.output str "./docs/assets/dependencies.mmd" Path where the dependency graph Mermaid diagram should be written.
dev-release.sync bool true Whether dev pre-release sync is enabled for this project.
exclude list[str] ['labels', 'skills', 'zizmor'] Components and files to exclude from repomatic operations. Bare names exclude a component; component/identifier entries exclude a specific file.
gitignore.extra-categories list[str] [] Additional gitignore template categories to fetch from gitignore.io.
gitignore.extra-content str (see example) Additional content to append at the end of the generated .gitignore file.
gitignore.location str "./.gitignore" File path of the .gitignore to update, relative to the root of the repository.
gitignore.sync bool true Whether .gitignore sync is enabled for this project.
labels.extra-content-rules str "" Additional YAML rules appended to the content-based labeller configuration.
labels.extra-file-rules str "" Additional YAML rules appended to the file-based labeller configuration.
labels.extra-files list[str] [] URLs of additional label definition files (JSON, JSON5, TOML, or YAML).
labels.sync bool true Whether label sync is enabled for this project.
mailmap.sync bool true Whether .mailmap sync is enabled for this project.
nuitka.enabled bool true Whether Nuitka binary compilation is enabled for this project.
nuitka.extra-args list[str] [] Extra Nuitka CLI arguments for binary compilation.
nuitka.unstable-targets list[str] [] Nuitka build targets allowed to fail without blocking the release.
pypi-package-history list[str] [] Former PyPI package names for projects that were renamed.
renovate.sync bool true Whether Renovate config sync is enabled for this project.
test-plan.file str "./tests/cli-test-plan.yaml" Path to the YAML test plan file for binary testing.
test-plan.inline str (none) Inline YAML test plan for binaries.
test-plan.timeout int (none) Timeout in seconds for each binary test.
uv-lock.sync bool true Whether uv.lock sync is enabled for this project.
workflow.source-paths list[str] (none) Source code directory names for workflow trigger paths: filters.
workflow.sync bool true Whether workflow sync is enabled for this project.

Tip

The workflows also invoke tools that read their own [tool.*] sections from your pyproject.toml. You can customize their behavior in your project without forking or patching the workflows:

Tool Section Customizes
bump-my-version [tool.bumpversion] Version bump patterns and files
coverage.py [tool.coverage.*] Code coverage reporting
mypy [tool.mypy] Static type checking
pytest [tool.pytest.ini_options] Test runner options
ruff [tool.ruff] Linting and formatting rules
typos [tool.typos] Spell-checking exceptions
uv [tool.uv] Package resolution and build config

See click-extra's inventory of pyproject.toml-aware tools for a broader list.

[tool.X] bridge for third-party tools

Some tools (yamllint, zizmor, …) have long-standing requests to read configuration from pyproject.toml but haven't shipped native support yet. repomatic run bridges the gap: write your config in [tool.<name>] and repomatic translates it to the tool's native format at invocation time.

# pyproject.toml
[tool.yamllint.rules.line-length]
max = 120

[tool.yamllint.rules.truthy]
check-keys = false
$ uvx -- repomatic run yamllint -- .

repomatic writes a temporary YAML config file, passes it via --config-file, and cleans it up after the run. No dotfiles needed.

The same mechanism works for any registered tool that accepts a config file. If a native config file (e.g., .yamllint.yaml) is already present, repomatic defers to it β€” your repo stays in control.

Reusable workflows

The repomatic CLI operates in CI through reusable GitHub Actions workflows. You configure behavior via [tool.repomatic] in pyproject.toml; the workflows are the execution layer.

Example usage

The fastest way to adopt these workflows is with repomatic init (see Quick start). It generates all the thin-caller workflow files for you.

If you prefer to set up a single workflow manually, create a .github/workflows/lint.yaml file using the uses syntax:

name: Lint
on:
  push:
  pull_request:

jobs:
  lint:
    uses: kdeldycke/repomatic/.github/workflows/lint.yaml@v5.9.1

Important

Concurrency is already configured in the reusable workflowsβ€”you don't need to re-specify it in your calling workflow.

GitHub Actions limitations

GitHub Actions has several design limitations that the workflows work around:

Limitation Status Addressed by
No conditional step groups βœ… Addressed metadata job + repomatic metadata
Workflow inputs only accept strings βœ… Addressed String parsing in repomatic
Matrix outputs not cumulative βœ… Addressed metadata pre-computes matrices
cancel-in-progress evaluated on new run, not old βœ… Addressed SHA-based concurrency groups in release.yaml
Cross-event concurrency cancellation βœ… Addressed event_name in changelog.yaml concurrency group
PR close doesn't cancel runs βœ… Addressed cancel-runs.yaml
GITHUB_TOKEN can't modify workflow files βœ… Addressed WORKFLOW_UPDATE_GITHUB_PAT fine-grained PAT
Tag pushes from Actions don't trigger workflows βœ… Addressed Custom PAT for tag operations
Default input values not propagated across events βœ… Addressed Manual defaults in env: section
head_commit only has latest commit in multi-commit pushes βœ… Addressed repomatic metadata extracts full commit range
actions/checkout uses merge commit for PRs βœ… Addressed Explicit ref: github.event.pull_request.head.sha
Multiline output encoding fragile βœ… Addressed Random delimiters in repomatic/github.py
Branch deletion doesn't cancel runs ❌ Not addressed Same root cause as PR close; partially mitigated by cancel-runs.yaml since branch deletion typically follows PR closure
No native way to depend on all matrix jobs completing ❌ Not addressed GitHub limitation; use needs: with a summary job as workaround
actionlint false positives for runtime env vars 🚫 Not addressable Linter limitation, not GitHub's

Setup β€” guide new users through initial configuration:

  • πŸ“– Setup guide (setup-guide)

    • Detects missing WORKFLOW_UPDATE_GITHUB_PAT secret and opens an issue with step-by-step setup instructions
    • Automatically closes the issue once the secret is configured
    • Skip: upstream kdeldycke/repomatic repo, workflow_call events

Formatters β€” rewrite files to enforce canonical style:

  • 🐍 Format Python (format-python)

    • Auto-formats Python code using autopep8 and ruff
    • Requires:
      • Python files (**/*.{py,pyi,pyw,pyx,ipynb}) in the repository, or
      • documentation files (**/*.{markdown,mdown,mkdn,mdwn,mkd,md,mdtxt,mdtext,mdx,rst,tex})
  • πŸ“ Format pyproject.toml (format-pyproject)

    • Auto-formats pyproject.toml using pyproject-fmt
    • Requires:
      • Python package with a pyproject.toml file
  • ✍️ Format Markdown (format-markdown)

    • Auto-formats Markdown files using mdformat
    • Requires:
      • Markdown files (**/*.{markdown,mdown,mkdn,mdwn,mkd,md,mdtxt,mdtext,mdx}) in the repository
  • πŸ”§ Format JSON (format-json)

    • Auto-formats JSON, JSONC, and JSON5 files using Biome
    • Requires:
      • JSON files (**/*.{json,jsonc,json5}, **/.code-workspace, !**/package-lock.json) in the repository

Fixers β€” correct or improve existing content in-place:

  • ✏️ Fix typos (fix-typos)

    • Automatically fixes typos in the codebase using typos
  • πŸ–ΌοΈ Format images (format-images)

    • Losslessly compresses PNG and JPEG images using repomatic format-images with oxipng and jpegoptim
    • Requires:
      • Image files (**/*.{jpeg,jpg,png,webp,avif}) in the repository

Syncers β€” regenerate files from external sources or project state:

  • πŸ™ˆ Sync .gitignore (sync-gitignore)

    • Regenerates .gitignore from gitignore.io templates using repomatic sync-gitignore
    • Requires:
      • A .gitignore file in the repository
    • Skipped if:
      • gitignore.sync = false in [tool.repomatic]
  • πŸ”„ Sync bumpversion config (sync-bumpversion)

    • Syncs the [tool.bumpversion] configuration in pyproject.toml using repomatic sync-bumpversion
    • Skipped if:
      • [tool.bumpversion] section already exists in pyproject.toml
      • bumpversion.sync = false in [tool.repomatic]
  • πŸ”„ Sync renovate.json5 (sync-renovate)

    • Syncs the local renovate.json5 with the canonical reference from repomatic, stripping repo-specific settings (customManagers, assignees)
    • Skipped if:
      • Repository is kdeldycke/repomatic itself (the upstream source)
      • No renovate.json5 file in the repository root
      • renovate.sync = false in [tool.repomatic]
  • πŸͺ’ Sync workflows (sync-workflows)

  • πŸ“¬ Sync .mailmap (sync-mailmap)

    • Keeps .mailmap file up to date with contributors using repomatic sync-mailmap
    • Requires:
      • A .mailmap file in the repository root
    • Skipped if:
      • mailmap.sync = false in [tool.repomatic]
  • πŸ•ΈοΈ Update dependency graph (update-deps-graph)

    • Generates a Mermaid dependency graph of the Python project using repomatic update-deps-graph
    • Requires:
      • Python package with a uv.lock file
  • πŸ“š Update docs (update-docs)

    • Regenerates Sphinx autodoc files using sphinx-apidoc
    • Runs docs/docs_update.py if present to generate dynamic content (tables, diagrams, Sphinx directives)
    • Requires:
      • Python package with a pyproject.toml file
      • docs dependency group
      • Sphinx autodoc enabled (checks for sphinx.ext.autodoc in docs/conf.py)
  • 🌟 Sync awesome template (sync-awesome-template)

  • πŸ”’ Lock inactive threads (lock)

    • Automatically locks closed issues and PRs after 90 days of inactivity using lock-threads
  • 🩺 Dump context (dump-context)

    • Dumps GitHub Actions context and runner environment info across all build targets using ghaction-dump-context
    • Useful for debugging runner differences and CI environment issues
    • Runs on:
      • Push to main (only when debug.yaml itself changes)
      • Monthly schedule
      • Manual dispatch
      • workflow_call from downstream repositories
  • βœ‚οΈ Cancel PR runs (cancel-runs)

    • Cancels all in-progress and queued workflow runs for a PR's branch when the PR is closed
    • Prevents wasted CI resources from long-running jobs (e.g. Nuitka binary builds) that continue after a PR is closed
    • GitHub Actions does not natively cancel runs on PR close β€” the concurrency mechanism only triggers cancellation when a new run enters the same group
  • πŸ†™ Bump version (bump-version)

    • Creates PRs for minor and major version bumps using bump-my-version
    • Syncs uv.lock to include the new version in the same commit
    • Uses commit message parsing as fallback when tags aren't available yet
    • Requires:
      • bump-my-version configuration in pyproject.toml
      • A changelog.md file
    • Runs on:
      • Schedule (daily at 6:00 UTC)
      • Manual dispatch
      • After release.yaml workflow completes successfully (via workflow_run trigger, to ensure tags exist before checking bump eligibility). Checks out the latest main HEAD, not the triggering workflow's commit.
  • πŸ“‹ Fix changelog (fix-changelog)

    • Checks and fixes changelog dates, availability admonitions, and orphaned versions using repomatic lint-changelog --fix
    • Runs on:
      • Push to main (when changelog.md, pyproject.toml, or workflow files change). Skipped during release cycles.
      • After release.yaml workflow completes successfully (via workflow_run trigger), when the GitHub release is published and visible to the public API.
  • 🎬 Prepare release (prepare-release)

    • Creates a release PR with two commits: a freeze commit that freezes everything to the release version, and an unfreeze commit that reverts to development references and bumps the patch version
    • Uses bump-my-version and repomatic changelog
    • Must be merged with "Rebase and merge" (not squash) β€” the auto-tagging job needs both commits separate
    • Requires:
      • bump-my-version configuration in pyproject.toml
      • A changelog.md file
    • Runs on:
      • Push to main (when changelog.md, pyproject.toml, or workflow files change)
      • Manual dispatch
      • workflow_call from downstream repositories

These jobs require a docs dependency group in pyproject.toml so they can determine the right Sphinx version to install and its dependencies:

[dependency-groups]
docs = [
    "furo",
    "myst-parser",
    "sphinx",
    …
]
  • πŸ“– Deploy Sphinx doc (deploy-docs)

    • Builds Sphinx-based documentation and publishes it to GitHub Pages using sphinx and gh-pages
    • Requires:
      • Python package with a pyproject.toml file
      • docs dependency group
      • Sphinx configuration file at docs/conf.py
  • πŸ”— Sphinx linkcheck (check-sphinx-links)

    • Runs Sphinx's built-in linkcheck builder to detect broken auto-generated links (intersphinx, autodoc, type annotations) that Lychee cannot see
    • Creates/updates issues for broken documentation links found
    • Requires:
      • Python package with a pyproject.toml file
      • docs dependency group
      • Sphinx configuration file at docs/conf.py
    • Skipped for:
      • Pull requests
      • prepare-release branch
      • Post-release version bump commits
  • πŸ’” Check broken links (check-broken-links)

    • Checks for broken links in documentation using lychee
    • Creates/updates issues for broken links found
    • Requires:
      • Documentation files (**/*.{markdown,mdown,mkdn,mdwn,mkd,md,mdtxt,mdtext,mdx,rst,tex}) in the repository
    • Skipped for:
      • All PRs (only runs on push to main)
      • prepare-release branch
      • Post-release bump commits
  • πŸ”„ Sync labels (sync-labels)

    • Synchronizes repository labels using repomatic sync-labels and labelmaker
    • Uses labels.toml with multiple profiles:
      • default profile applied to all repositories
      • awesome profile additionally applied to awesome-* repositories
    • Skipped if:
      • labels.sync = false in [tool.repomatic]
  • πŸ“ File-based PR labeller (file-labeller)

    • Automatically labels PRs based on changed file paths using labeler
    • Skipped for:
      • prepare-release branch
      • Bot-created PRs
  • πŸ“ Content-based labeller (content-labeller)

    • Automatically labels issues and PRs based on title and body content using issue-labeler
    • Skipped for:
      • prepare-release branch
      • Bot-created PRs
  • πŸ’ Tag sponsors (sponsor-labeller)

    • Adds a πŸ’– sponsors label to issues and PRs from sponsors using the GitHub GraphQL API
    • Skipped for:
      • prepare-release branch
      • Bot-created PRs
  • 🏠 Lint repository metadata (lint-repo)

    • Validates repository metadata (package name, Sphinx docs, project description) using repomatic lint-repo. Reads pyproject.toml directly.
    • Requires:
      • Python package (with a pyproject.toml file)
  • πŸ”€ Lint types (lint-types)

    • Type-checks Python code using mypy
    • Requires:
      • Python files (**/*.{py,pyi,pyw,pyx,ipynb}) in the repository
    • Skipped for:
      • prepare-release branch
  • πŸ“„ Lint YAML (lint-yaml)

    • Lints YAML files using yamllint
    • Requires:
      • YAML files (**/*.{yaml,yml}) in the repository
    • Skipped for:
      • prepare-release branch
      • Bot-created PRs
  • 🐚 Lint Zsh (lint-zsh)

    • Syntax-checks Zsh scripts using zsh --no-exec
    • Requires:
      • Zsh files (**/*.zsh) in the repository
    • Skipped for:
      • prepare-release branch
      • Bot-created PRs
  • ⚑ Lint GitHub Actions (lint-github-actions)

    • Lints workflow files using actionlint and shellcheck
    • Requires:
      • Workflow files (.github/workflows/**/*.{yaml,yml}) in the repository
    • Skipped for:
      • prepare-release branch
      • Bot-created PRs
  • πŸ”’ Lint workflow security (lint-workflow-security)

    • Audits workflow files for security issues using zizmor (template injection, excessive permissions, supply chain risks, etc.)
    • Requires:
      • Workflow files (.github/workflows/**/*.{yaml,yml}) in the repository
    • Skipped for:
      • prepare-release branch
      • Bot-created PRs
  • 🌟 Lint Awesome list (lint-awesome)

    • Lints awesome lists using awesome-lint
    • Requires:
      • Repository name starts with awesome-
      • Repository is not awesome-template itself
    • Skipped for:
      • prepare-release branch
  • πŸ” Lint secrets (lint-secrets)

    • Scans for leaked secrets using gitleaks
    • Skipped for:
      • prepare-release branch
      • Bot-created PRs

Release Engineering is a full-time job, and full of edge-cases that nobody wants to deal with. This workflow automates most of it for Python projects.

Cross-platform binaries β€” Targets 6 platform/architecture combinations (Linux/macOS/Windows Γ— x86_64/arm64). Unstable targets use continue-on-error so builds don't fail on experimental platforms. Job names are prefixed with βœ… (stable, must pass) or ⁉️ (unstable, allowed to fail) for quick visual triage in the GitHub Actions UI.

  • 🧯 Detect squash merge (detect-squash-merge)

    • Detects squash-merged release PRs, opens a GitHub issue to notify the maintainer, and fails the workflow
    • The release is effectively skipped: create-tag only matches commits with the [changelog] Release v prefix, so no tag, PyPI publish, or GitHub release is created from a squash merge
    • The net effect of squashing freeze + unfreeze leaves main in a valid state for the next development cycle; the maintainer just releases the next version when ready
    • Runs on:
      • Push to main only
  • πŸ“¦ Build package (build-package)

    • Builds Python wheel and sdist packages using uv build
    • Requires:
      • Python package with a pyproject.toml file
  • βœ… Compile binaries (compile-binaries)

    • Compiles standalone binaries using Nuitka for Linux/macOS/Windows on x64/arm64
    • On release pushes, each binary generates an attestation and uploads itself to the GitHub release as its build completes
    • Requires:
    • Skipped if [tool.repomatic] nuitka = false is set in pyproject.toml (for projects with CLI entry points that don't need standalone binaries)
    • Skipped for branches that don't affect code:
      • format-json (JSON formatting)
      • format-markdown (documentation formatting)
      • format-images (image formatting)
      • sync-gitignore (.gitignore sync)
      • sync-mailmap (.mailmap sync)
      • update-deps-graph (dependency graph docs)
  • βœ… Test binaries (test-binaries)

    • Runs test plans against compiled binaries using repomatic test-plan
    • Requires:
      • Compiled binaries from compile-binaries job
      • Test plan file (default: ./tests/cli-test-plan.yaml)
    • Skipped for:
      • Same branches as compile-binaries
  • πŸ“Œ Create tag (create-tag)

    • Creates a Git tag for the release version
    • Requires:
  • 🐍 Publish to PyPI (publish-pypi)

    • Uploads packages to PyPI with attestations using uv publish
    • Requires:
      • PYPI_TOKEN secret
      • Built packages from build-package job
  • πŸ™ Create release draft (create-release)

    • Creates a GitHub release draft with the Python package attached using gh release create
    • Binaries are attached independently by each compile-binaries matrix entry as they complete (uploading to drafts is allowed)
    • Requires:
      • Successful create-tag job
  • πŸŽ‰ Publish release (publish-release)

    • Publishes the draft GitHub release after all assets have been uploaded
    • Supports GitHub immutable releases: once published, tags and assets are locked
    • Uses always() so it runs even when compile-binaries is skipped (non-binary projects) or partially fails (unstable platforms)
    • Requires:
      • Successful create-release job (draft must exist)
  • πŸ”„ Sync dev pre-release (sync-dev-release)

    • Maintains a rolling dev pre-release on GitHub that mirrors the unreleased changelog section
    • Attaches binaries and Python packages from build jobs via --upload-assets
    • The dev tag (e.g. v6.1.1.dev0) is force-updated to point to the latest main commit
    • Automatically cleaned up when a real release is created
    • Runs on: Non-release pushes to main only
    • Requires:
      • build-package and compile-binaries jobs (uses always() for resilience)
    • Skipped if:
      • dev-release.sync = false in [tool.repomatic]
  • πŸ”„ Sync bundled config (sync-bundled-config)

    • Keeps the bundled repomatic/data/renovate.json5 in sync with the root renovate.json5
    • Only runs in:
      • The kdeldycke/repomatic repository
  • 🚚 Migrate to Renovate (migrate-to-renovate)

    • Automatically migrates from Dependabot to Renovate by creating a PR that:
      • Exports renovate.json5 configuration file (if missing)
      • Removes .github/dependabot.yaml or .github/dependabot.yml (if present)
    • PR body includes a prerequisites status table showing:
      • What this PR fixes (config file creation, Dependabot removal)
      • What needs manual action (security updates settings, token permissions)
      • Links to relevant settings pages for easy access
    • Uses peter-evans/create-pull-request for consistent PR creation
    • Skipped if:
      • No changes needed (renovate.json5 already exists and no Dependabot config is present)
  • πŸ†• Renovate (renovate)

    • Validates prerequisites before running (fails if not met):
      • renovate.json5 configuration exists
      • No Dependabot config file present
      • Dependabot security updates disabled
    • Runs self-hosted Renovate to update dependencies
    • Creates PRs for outdated dependencies with stabilization periods
    • Handles security vulnerabilities via vulnerabilityAlerts
    • Requires:
      • WORKFLOW_UPDATE_GITHUB_PAT secret with Dependabot alerts permission
  • ⛓️ Sync uv.lock (sync-uv-lock)

    • Runs uv lock --upgrade to update transitive dependencies to their latest allowed versions using repomatic sync-uv-lock
    • Only creates a PR when the lock file contains real dependency changes (timestamp-only noise is detected and skipped)
    • Replaces Renovate's lockFileMaintenance, which cannot reliably revert noise-only changes
    • Requires:
      • Python package with a pyproject.toml file
    • Skipped if:
      • uv-lock.sync = false in [tool.repomatic]
  • πŸ“¦ Package install (test-package-install)

    • Verifies the package can be installed and all CLI entry points run correctly via every install method: uvx, uvx --from, uv run --with, module invocation (-m), uv tool install, and pipx run
    • Tests both the latest PyPI release and the current main branch from GitHub
    • Runs once on a single stable OS/Python β€” install correctness does not vary by platform
    • Requires:
      • cli_scripts from metadata job (skipped if no [project.scripts] entries)
  • πŸ”¬ Run tests (tests)

    • Runs the test suite across a matrix of OS (Linux/macOS/Windows Γ— x86_64/arm64) and Python versions (3.10, 3.14, 3.14t, 3.15, 3.15t)
    • Installs all optional extras (--all-extras) to catch incompatibilities between optional dependency groups
    • Runs pytest with coverage reporting to Codecov
    • Runs self-tests against the CLI test plan
    • Job names prefixed with βœ… (stable) or ⁉️ (unstable, e.g., unreleased Python versions)
  • πŸ–₯️ Validate architecture (validate-arch)

    • Checks that the detected CPU architecture matches what the runner image advertises
    • Ensures runners are not silently using emulation (e.g., x86_64 on aarch64)
    • Requires:
      • Build targets from metadata job

🧬 What is this metadata job?

Most jobs in this repository depend on a shared parent job called metadata. It runs first to extract contextual information, reconcile and combine it, and expose it for downstream jobs to consume.

This expands the capabilities of GitHub Actions, since it allows to:

  • Share complex data across jobs (like build matrix)
  • Remove limitations of conditional jobs
  • Allow for runner introspection
  • Fix quirks (like missing environment variables, events/commits mismatch, merge commits, etc.)

This job relies on the repomatic metadata command to gather data from multiple sources:

  • Git: current branch, latest tag, commit messages, changed files
  • GitHub: event type, actor, PR labels
  • Environment: OS, architecture
  • pyproject.toml: project name, version, entry points

Important

This flexibility comes at the cost of:

  • Making the whole workflow a bit more computationally intensive
  • Introducing a small delay at the beginning of the run
  • Preventing child jobs to run in parallel before its completion

But is worth it given how GitHub Actions can be frustrating.

How does it work?

uv everywhere

All Python dependencies and CLIs are installed via uv for speed and reproducibility.

Smart job skipping

Jobs are guarded by conditions to skip unnecessary steps: file type detection (only lint Python if .py files exist), branch filtering (prepare-release skipped for most linting), and bot detection.

Maintainer-in-the-loop

Workflows never commit directly or act silently. Every proposed change creates a PR; every action needed opens an issue. You review and decide β€” nothing lands without your approval.

Configurable with sensible defaults

Downstream projects customize behavior via [tool.repomatic] in pyproject.toml. Workflows also accept inputs for fine-tuning, but the configuration file is the primary interface.

Idempotent operations

Safe to re-run: tag creation skips if already exists, version bumps have eligibility checks, PRs update existing branches.

Graceful degradation

Fallback tokens (secrets.WORKFLOW_UPDATE_GITHUB_PAT || secrets.GITHUB_TOKEN) and continue-on-error for unstable targets. Job names use emoji prefixes for at-a-glance status: βœ… for stable jobs that must pass, ⁉️ for unstable jobs (e.g., experimental Python versions, unreleased platforms) that are expected to fail and won't block the workflow.

Dogfooding

This repository uses these workflows for itself.

Dependency strategy

All dependencies are pinned to specific versions for stability, reproducibility, and security.

Pinning mechanisms

Mechanism What it pins How it's updated
uv.lock Project dependencies Renovate PRs
Hard-coded versions in YAML GitHub Actions, npm, Python Renovate PRs
uv --exclude-newer option Transitive dependencies Time-based window
Tagged workflow URLs Remote workflow references Release process
--from . repomatic CLI from local source Release freeze

Hard-coded versions in workflows

GitHub Actions and npm packages are pinned directly in YAML files:

  - uses: actions/checkout@v6.0.1        # Pinned action
  - run: npm install eslint@9.39.1       # Pinned npm package

Renovate's github-actions manager handles action updates, and a custom regex manager handles npm packages pinned inline in workflow files.

Renovate cooldowns

To avoid update fatigue, and mitigate supply chain attacks, renovate.json5 uses stabilization periods (with prime numbers to stagger updates).

This ensures major updates get more scrutiny while patches flow through faster.

uv.lock and --exclude-newer

The uv.lock file pins all project dependencies, and Renovate keeps it in sync.

The --exclude-newer flag ignores packages released in the last 7 days, providing a buffer against freshly-published broken releases.

Tagged workflow URLs

Workflows in this repository are self-referential. The prepare-release job's freeze commit rewrites workflow URL references from main to the release tag, ensuring released versions reference immutable URLs. The unfreeze commit reverts them back to main for development.

Permissions and token

Several workflows need a WORKFLOW_UPDATE_GITHUB_PAT secret to create PRs that modify files in .github/workflows/ and to trigger downstream workflows. Without it, those jobs silently fall back to the default GITHUB_TOKEN, which lacks the required permissions.

After your first push, the setup-guide job automatically opens an issue with step-by-step instructions to create and configure the token.

Concurrency and cancellation

All workflows use a concurrency directive to prevent redundant runs and save CI resources. When a new commit is pushed, any in-progress workflow runs for the same branch or PR are automatically cancelled.

Workflows are grouped by:

  • Pull requests: {workflow-name}-{pr-number} β€” Multiple commits to the same PR cancel previous runs
  • Branch pushes: {workflow-name}-{branch-ref} β€” Multiple pushes to the same branch cancel previous runs

release.yaml uses a stronger protection: release commits get a unique concurrency group based on the commit SHA, so they can never be cancelled. This ensures tagging, PyPI publishing, and GitHub release creation complete successfully.

Additionally, cancel-runs.yaml actively cancels in-progress and queued runs when a PR is closed. This complements passive concurrency groups, which only trigger cancellation when a new run enters the same group β€” closing a PR doesn't produce such an event.

Tip

For implementation details on how concurrency groups are computed and why release.yaml needs special handling, see claude.md Β§ Concurrency implementation.

Claude Code integration

This repository includes Claude Code skills that wrap repomatic CLI commands as slash commands. Downstream repositories can install them with:

$ uvx -- repomatic init skills

To keep skills in sync with the latest version:

$ uvx -- repomatic sync-skills

To list all available skills with descriptions:

$ uvx -- repomatic list-skills

Available skills

Phase Skill Description
Setup /repomatic-init Bootstrap a repository with reusable workflows
Setup /repomatic-sync Sync workflow caller files with upstream
Development /repomatic-deps Generate and analyze dependency graphs from uv lockfiles
Development /repomatic-metadata Extract and explain project metadata
Quality /repomatic-lint Lint workflows and repository metadata
Quality /repomatic-test Run and write YAML test plans for compiled binaries
Release /repomatic-changelog Draft, validate, and fix changelog entries
Release /repomatic-release Pre-checks, release preparation, and post-release steps

Recommended workflow

The typical lifecycle for maintaining a downstream repository follows this sequence. Each skill suggests next steps after completing, creating a guided flow:

  1. /repomatic-init β€” One-time setup: bootstrap workflows, labels, and configs
  2. /repomatic-sync β€” Periodic: pull latest upstream workflow changes
  3. /repomatic-lint β€” Before merging: validate workflows and metadata
  4. /repomatic-deps β€” As needed: visualize the dependency tree
  5. /repomatic-changelog β€” Before release: draft and validate changelog entries
  6. /repomatic-release β€” Release time: pre-flight checks and release preparation
Walkthrough: setup to first release
# In Claude Code, bootstrap your repository
/repomatic-init

# After making changes, sync with latest upstream workflows
/repomatic-sync

# Validate everything
/repomatic-lint all

# Add changelog entries for your changes
/repomatic-changelog add

# Validate the changelog
/repomatic-changelog check

# Pre-flight checks before release
/repomatic-release check

# Prepare the release PR
/repomatic-release prep

Used in

Check these projects to get real-life examples of usage and inspiration:

Feel free to send a PR to add your project in this list if you are relying on these scripts.

Development

See claude.md for development commands, code style, testing guidelines, and design principles.