Tool runnerยถ

repomatic run is a unified entry point for running external linters, formatters, and security scanners. It installs each tool at a pinned version, resolves configuration through a strict precedence chain, and invokes the tool: no manual setup, no dotfile sprawl.

Why not run the tools directly?ยถ

Installing a tool and running yamllint . yourself is fine for one tool on one machine. Once a project leans on a dozen, the same three chores repeat for each, and repomatic run takes care of all of them:

  • Configuration stays in pyproject.toml, one reviewed file rather than a dotfile per tool. Even tools that canโ€™t read pyproject.toml themselves get their [tool.X] table translated to a temporary native config at run time, following the precedence chain below.

  • Installation is automatic: binaries come from GitHub Releases and are checksum-verified, PyPI tools run through uvx, tools that import your code (mypy, Nuitka) run inside the project virtualenv, and npm tools install from the npm registry (Node.js required).

  • Versions are pinned and re-verified on each use, so a check behaves the same on your laptop and in CI instead of drifting with whatever each machine happens to have installed.

See also

The tools repomatic bridges have standing upstream requests to read [tool.X] from pyproject.toml natively, all still unshipped: actionlint#623, biome#9239, gitleaks#2066, Nuitka#3909, zizmor#322. The same request for shfmt (sh#1268) was declined.

Quick startยถ

Run a tool against your project:

$ repomatic run yamllint -- .

The -- separates repomaticโ€™s own options from the arguments forwarded to the tool. Everything after -- is passed through verbatim.

List all managed tools and their resolved config source:

$ repomatic run --list
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Tool            โ”‚ Version โ”‚ Config source                                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ actionlint      โ”‚ 1.7.12  โ”‚ [tool.actionlint] in pyproject.toml (replaces bundled default) โ”‚
โ”‚ autopep8        โ”‚ 2.3.2   โ”‚ (bare)                                                         โ”‚
โ”‚ awesome-lint    โ”‚ 2.3.0   โ”‚ (bare)                                                         โ”‚
โ”‚ biome           โ”‚ 2.5.7   โ”‚ (bare)                                                         โ”‚
โ”‚ bump-my-version โ”‚ 1.5.1   โ”‚ (bare)                                                         โ”‚
โ”‚ gh              โ”‚ 2.97.0  โ”‚ (bare)                                                         โ”‚
โ”‚ gitleaks        โ”‚ 8.30.1  โ”‚ (bare)                                                         โ”‚
โ”‚ labelmaker      โ”‚ 0.6.4   โ”‚ (bare)                                                         โ”‚
โ”‚ lychee          โ”‚ 0.24.2  โ”‚ [tool.lychee] in pyproject.toml                                โ”‚
โ”‚ mdformat        โ”‚ 1.0.0   โ”‚ bundled default                                                โ”‚
โ”‚ mypy            โ”‚ 2.3.0   โ”‚ [tool.mypy] in pyproject.toml                                  โ”‚
โ”‚ nuitka          โ”‚ 4.1.3   โ”‚ [tool.nuitka] in pyproject.toml                                โ”‚
โ”‚ oxipng          โ”‚ 10.2.0  โ”‚ (bare)                                                         โ”‚
โ”‚ pyproject-fmt   โ”‚ 2.27.0  โ”‚ (bare)                                                         โ”‚
โ”‚ ruff            โ”‚ 0.16.2  โ”‚ [tool.ruff] in pyproject.toml (replaces bundled default)       โ”‚
โ”‚ shfmt           โ”‚ 3.13.1  โ”‚ (bare)                                                         โ”‚
โ”‚ typos           โ”‚ 1.49.0  โ”‚ [tool.typos] in pyproject.toml                                 โ”‚
โ”‚ yamllint        โ”‚ 1.38.0  โ”‚ bundled default                                                โ”‚
โ”‚ zizmor          โ”‚ 1.29.0  โ”‚ bundled default                                                โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

Available toolsยถ

Tool

Version

Type

Config discovery

actionlint

1.7.12

Binary

.github/actionlint.yaml, .github/actionlint.yml

autopep8

2.3.2

PyPI

[tool.autopep8] in pyproject.toml

awesome-lint

2.3.0

npm

CLI flags only

Biome

2.5.7

Binary

biome.json, biome.jsonc, .biome.json, .biome.jsonc

bump-my-version

1.5.1

PyPI

.bumpversion.toml, [tool.bump-my-version] in pyproject.toml

GitHub CLI

2.97.0

Binary

CLI flags only

Gitleaks

8.30.1

Binary

.gitleaks.toml

labelmaker

0.6.4

Binary

CLI flags only

Lychee

0.24.2

Binary

lychee.toml, [tool.lychee] in pyproject.toml

mdformat

1.0.0

PyPI

.mdformat.toml, [tool.mdformat] in pyproject.toml

mypy

2.3.0

PyPI (venv)

[tool.mypy] in pyproject.toml

Nuitka

4.1.3

PyPI (venv)

[tool.nuitka] in pyproject.toml

Oxipng

10.2.0

Binary

CLI flags only

pyproject-fmt

2.27.0

PyPI

pyproject-fmt.toml, [tool.pyproject-fmt] in pyproject.toml

Ruff

0.16.2

PyPI

.ruff.toml, ruff.toml, [tool.ruff] in pyproject.toml

shfmt

3.13.1

Binary

.editorconfig

typos

1.49.0

Binary

typos.toml, _typos.toml, .typos.toml, [tool.typos] in pyproject.toml

yamllint

1.38.0

PyPI

.yamllint, .yamllint.yaml, .yamllint.yml

zizmor

1.29.0

PyPI

.github/zizmor.yml, .github/zizmor.yaml, zizmor.yml, zizmor.yaml

  • Binary: downloaded as platform-specific executables from GitHub Releases.

  • PyPI: installed via uvx.

  • PyPI (venv): run inside the project virtualenv via uv run because they need to import project code.

  • npm: installed from the npm registry into a throwaway prefix and run via node_modules/.bin. The one backend that needs a foreign runtime (Node.js and npm on PATH); integrity is npmโ€™s own per-tarball verification, with no repomatic-pinned checksum.

Config resolutionยถ

When repomatic run <tool> is invoked, configuration is resolved through a 4-level precedence chain. The first match wins: no merging across levels.

        flowchart TD
    run([repomatic run TOOL]) --> l1{native config file?}
    l1 -->|yes| u1[Level 1. Use the in-repo config file]
    l1 -->|no| l2{tool.X in pyproject.toml?}
    l2 -->|yes| u2[Level 2. Use tool.X, translate if needed]
    l2 -->|no| l3{bundled default?}
    l3 -->|yes| u3[Level 3. repomatic bundled baseline]
    l3 -->|no| u4[Level 4. Bare invocation, tool defaults]
    

Tip

Run repomatic --verbosity INFO run <tool> to see which config level was selected and the exact command line being executed. This is useful for debugging unexpected behavior. For full detail (config file contents, environment, caching), use --verbosity DEBUG.

Level 1: native config fileยถ

If the toolโ€™s own config file exists in the repo (like ruff.toml or .yamllint.yaml), repomatic defers to it entirely. Your repo stays in control.

$ ls ruff.toml
ruff.toml
$ repomatic run ruff -- check .
# Uses ruff.toml directly โ€” repomatic does nothing special.

Level 2: [tool.X] in pyproject.tomlยถ

If no native config file is found but your pyproject.toml has a [tool.<name>] section, repomatic uses it. For tools that read pyproject.toml natively (ruff, mypy, bump-my-version, etc.), this just works. For tools that donโ€™t, repomatic translates the section into the toolโ€™s native format and passes it via a temporary config file.

Note

When the toolโ€™s native format is also TOML (like gitleaks), the translation keeps the comments from your [tool.X] section and only drops the [tool.X] prefix. Translations to another format (YAML, JSON) carry the values only: a TOML comment has no equivalent to map onto.

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

[tool.yamllint.rules.truthy]
check-keys = false
$ repomatic run yamllint -- .
# Translates [tool.yamllint] to YAML, passes via --config-file.

All tools that support [tool.X] sections in pyproject.toml, whether natively or via repomaticโ€™s translation bridge:

Tool

Customizes

Section

Support

actionlint

Workflow linting rules

[tool.actionlint]

repomatic bridge โ†’ YAML

autopep8

Python code formatting

[tool.autopep8]

Native

biome

JSON/JS formatting and linting

[tool.biome]

repomatic bridge โ†’ JSON

bump-my-version

Version bump patterns and files

[tool.bumpversion]

Native

coverage.py

Code coverage reporting

[tool.coverage.*]

Native

gitleaks

Secret detection rules

[tool.gitleaks]

repomatic bridge โ†’ TOML

lychee

Link checking rules

[tool.lychee]

Native

mdformat

Markdown formatting options

[tool.mdformat]

Native (via mdformat-pyproject)

mypy

Static type checking

[tool.mypy]

Native

nuitka

Standalone binary compilation

[tool.nuitka]

repomatic bridge โ†’ CLI flags (native support: Nuitka#3909)

pyproject-fmt

pyproject.toml formatting

[tool.pyproject-fmt]

Native

pytest

Test runner options

[tool.pytest]

Native

ruff

Linting and formatting rules

[tool.ruff]

Native

typos

Spell-checking exceptions

[tool.typos]

Native

uv

Package resolution and build config

[tool.uv]

Native

yamllint

YAML linting rules

[tool.yamllint]

repomatic bridge โ†’ YAML

zizmor

Workflow security scanning

[tool.zizmor]

repomatic bridge โ†’ YAML

See Click Extraโ€™s inventory of pyproject.toml-aware tools for a broader list.

Level 3: bundled defaultยถ

If the repo has no config at all, repomatic falls back to its own bundled defaults (stored in repomatic/data/). These provide sensible baseline rules so that tools produce useful results even without any project-specific configuration.

Tools with bundled defaults: actionlint, mdformat, ruff, yamllint, zizmor.

Level 4: bare invocationยถ

If none of the above applies (no config file, no [tool.X], no bundled default), the tool runs with its own built-in defaults. Tools like autopep8 work this way: all behavior is controlled through CLI flags.

Checking the active config sourceยถ

To see which precedence level is active for each tool in your repo:

$ repomatic run --list
โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
โ”‚ Tool            โ”‚ Version โ”‚ Config source                                                  โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ actionlint      โ”‚ 1.7.12  โ”‚ [tool.actionlint] in pyproject.toml (replaces bundled default) โ”‚
โ”‚ autopep8        โ”‚ 2.3.2   โ”‚ (bare)                                                         โ”‚
โ”‚ awesome-lint    โ”‚ 2.3.0   โ”‚ (bare)                                                         โ”‚
โ”‚ biome           โ”‚ 2.5.7   โ”‚ (bare)                                                         โ”‚
โ”‚ bump-my-version โ”‚ 1.5.1   โ”‚ (bare)                                                         โ”‚
โ”‚ gh              โ”‚ 2.97.0  โ”‚ (bare)                                                         โ”‚
โ”‚ gitleaks        โ”‚ 8.30.1  โ”‚ (bare)                                                         โ”‚
โ”‚ labelmaker      โ”‚ 0.6.4   โ”‚ (bare)                                                         โ”‚
โ”‚ lychee          โ”‚ 0.24.2  โ”‚ [tool.lychee] in pyproject.toml                                โ”‚
โ”‚ mdformat        โ”‚ 1.0.0   โ”‚ bundled default                                                โ”‚
โ”‚ mypy            โ”‚ 2.3.0   โ”‚ [tool.mypy] in pyproject.toml                                  โ”‚
โ”‚ nuitka          โ”‚ 4.1.3   โ”‚ [tool.nuitka] in pyproject.toml                                โ”‚
โ”‚ oxipng          โ”‚ 10.2.0  โ”‚ (bare)                                                         โ”‚
โ”‚ pyproject-fmt   โ”‚ 2.27.0  โ”‚ (bare)                                                         โ”‚
โ”‚ ruff            โ”‚ 0.16.2  โ”‚ [tool.ruff] in pyproject.toml (replaces bundled default)       โ”‚
โ”‚ shfmt           โ”‚ 3.13.1  โ”‚ (bare)                                                         โ”‚
โ”‚ typos           โ”‚ 1.49.0  โ”‚ [tool.typos] in pyproject.toml                                 โ”‚
โ”‚ yamllint        โ”‚ 1.38.0  โ”‚ bundled default                                                โ”‚
โ”‚ zizmor          โ”‚ 1.29.0  โ”‚ bundled default                                                โ”‚
โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

The โ€œConfig sourceโ€ column shows whether the tool is using a native config file (level 1), [tool.X] (level 2), a bundled default (level 3), or bare invocation (level 4).

Tutorial: adding yamllint to your projectยถ

This walkthrough covers a common scenario: running yamllint on a project that has no YAML linting configured.

Step 1: run with defaultsยถ

With no config file and no [tool.yamllint] section in pyproject.toml, repomatic uses its bundled default:

$ repomatic run yamllint -- .

The bundled config enforces strict YAML rules. If that produces too many warnings, customize it.

Step 2: customize via pyproject.tomlยถ

Instead of creating a .yamllint.yaml file, add a section to your pyproject.toml:

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

[tool.yamllint.rules.truthy]
check-keys = false

Now repomatic run yamllint -- . translates this to YAML, passes it via --config-file, and cleans up the temporary file afterward.

Step 3: graduate to a native config fileยถ

If your yamllint config grows complex, create a .yamllint.yaml directly. Once that file exists, repomatic defers to it (level 1 takes precedence) and the [tool.yamllint] section in pyproject.toml is ignored.

Cleaning up unmodified configsยถ

If you previously ran repomatic init and have a native config file that is identical to the bundled default, repomatic init --delete-unmodified removes it:

$ repomatic init --delete-unmodified

Overriding tool versionsยถ

To test a newer version of a tool before the registry is updated:

$ repomatic run shfmt --version 3.14.0 --skip-checksum -- .

--skip-checksum is required because the registry only stores checksums for the pinned version. For binary tools, --checksum lets you provide the correct SHA-256 for the new version instead of skipping verification entirely:

$ repomatic run shfmt --version 3.14.0 --checksum abc123... -- .

Binary cachingยถ

repomatic run downloads platform-specific binaries (actionlint, biome, gitleaks, labelmaker, lychee, etc.) from GitHub Releases. To avoid re-downloading on every invocation, binaries are cached under a platform-appropriate user cache directory:

Platform

Default cache path

Linux

$XDG_CACHE_HOME/repomatic or ~/.cache/repomatic

macOS

~/Library/Caches/repomatic

Windows

%LOCALAPPDATA%\repomatic\Cache

Cached binaries are re-verified against their registry SHA-256 checksum on every use. Entries older than 30 days are auto-purged.

Both settings are configurable via [tool.repomatic] (see cache.dir and cache.max-age) or environment variables. The env var takes precedence over the config.

Environment variable

Config key

Default

Description

REPOMATIC_CACHE_DIR

cache.dir

(platform-specific)

Override the cache directory path.

REPOMATIC_CACHE_MAX_AGE

cache.max-age

30

Auto-purge entries older than this many days. 0 disables.

Cache management commands:

$ repomatic cache show
$ repomatic cache clean
$ repomatic cache clean --tool ruff --max-age 7
$ repomatic cache path

Use --no-cache on repomatic run to bypass the cache entirely.

Running with no argumentsยถ

Some tools declare the arguments and target files CI would pass on their behalf, so repomatic run <tool> with nothing after it runs the same invocation:

$ repomatic run yamllint
$ repomatic run mdformat

The first resolves to repomatic run yamllint -- .; the second walks every Markdown file in the repository and formats each one in turn, matching what the format-markdown job runs. A tool with no declared defaults runs bare, exactly as before.

Note

This only fires on a bare invocation. Passing any argument after --, even one that overlaps with the toolโ€™s defaults, hands control to you entirely: nothing is injected on top of it. Splicing repomaticโ€™s defaults into a caller-driven command could otherwise build something like biome format โ€ฆ check ., which is not a command anyone meant to run.

When a toolโ€™s defaults are file-driven and the repository holds no matching file, the tool is skipped rather than invoked with no path: a formatter handed zero paths does not no-op, it walks the entire tree in write mode.

Passing extra argumentsยถ

Everything after -- is forwarded to the tool:

$ repomatic run ruff -- check --fix .
$ repomatic run zizmor -- --offline .github/workflows/
$ repomatic run biome -- format --write src/

For tools with subcommands (ruff, biome, gitleaks), the subcommand goes after -- as the first argument.

Verifying without writingยถ

--verify reports which targets a tool would rewrite, without touching the working tree:

$ repomatic run mdformat --verify -- changelog.md

It runs the write path against throwaway copies of the targets and diffs the results, rather than trusting the toolโ€™s own --check or --dry-run mode. That distinction matters for a tool whose check mode relies on a repomatic post-processing step that only runs after an actual write: for those, --check can report drift the write path would reconcile, or miss drift the write path would introduce. --verify is the authoritative answer either way.

With no arguments after the tool name, --verify resolves the same defaults a bare repomatic run <tool> would, so repomatic run mdformat --verify checks every Markdown file in the repository.

Tool detailsยถ

actionlintยถ

Stars Last release

Installed version: 1.7.12

Installation method: Binary (downloaded from GitHub Releases)

Config files: .github/actionlint.yaml, .github/actionlint.yml

[tool.actionlint] bridge: repomatic translates to YAML and passes via --config-file.

Default flags: -color

Bundled default: actionlint.yaml

Source | Config reference | CLI usage

Try it:

$ repomatic run actionlint

Minimal [tool.actionlint]:

[tool.actionlint.self-hosted-runner]
labels = ["my-linux-runner"]

With no arguments actionlint lints every workflow under .github/workflows. The [tool.actionlint] section is bridged to a temporary YAML config: declaring self-hosted runner labels stops custom runs-on: values being flagged as unknown.

autopep8ยถ

Stars Last release

Installed version: 2.3.2

Installation method: PyPI, installed via uvx

Config: [tool.autopep8] in pyproject.toml (native)

Default flags: --recursive --in-place --max-line-length 88 --select E501

Source | CLI usage

Try it:

$ repomatic run autopep8 -- .

autopep8 takes its configuration from CLI flags only. repomatic passes --recursive --in-place --max-line-length 88 --select E501 by default; append more flags after --.

awesome-lintยถ

Last release

Installed version: 2.3.0

Installation method: npm registry, run via node_modules/.bin

Config: CLI flags only

CLI usage

Biomeยถ

Stars Last release

Installed version: 2.5.7

Installation method: Binary (downloaded from GitHub Releases)

Config files: biome.json, biome.jsonc, .biome.json, .biome.jsonc

[tool.biome] bridge: repomatic translates to JSON and passes via --config-path.

Source | Config reference | CLI usage

Try it:

$ repomatic run biome -- check .

Minimal [tool.biome]:

[tool.biome.formatter]
indentStyle = "space"

biome check reports formatting and lint issues; add --write after -- to apply fixes. The [tool.biome] section is bridged to a temporary biome.json, so keys keep Biomeโ€™s camelCase spelling.

bump-my-versionยถ

Stars Last release

Installed version: 1.5.1

Installation method: PyPI, installed via uvx

Config files: .bumpversion.toml and [tool.bump-my-version] in pyproject.toml (native)

Source | Config reference | CLI usage

Try it:

$ repomatic run bump-my-version -- show-bump

Minimal [tool.bumpversion]:

[tool.bumpversion]
current_version = "1.2.3"

The configuration table is [tool.bumpversion], not [tool.bump-my-version]: the section name predates the projectโ€™s rename. show-bump previews the next versions without writing; repomatic run bump-my-version -- bump minor performs the bump.

GitHub CLIยถ

Stars Last release

Installed version: 2.97.0

Installation method: Binary (downloaded from GitHub Releases)

Config: CLI flags only

Source | CLI usage

Try it:

$ repomatic run gh -- --version

Pinned so the release lane gets the same gh everywhere. The manylinux container the Linux binaries compile in ships no gh, and the runner images that do ship one leave its version to the image. gh reads no project configuration: it authenticates from GH_TOKEN in the environment.

Gitleaksยถ

Stars Last release

Installed version: 8.30.1

Installation method: Binary (downloaded from GitHub Releases)

Config files: .gitleaks.toml

[tool.gitleaks] bridge: repomatic translates to TOML and passes via --config.

Source | Config reference | CLI usage

Try it:

$ repomatic run gitleaks -- dir .

Minimal [tool.gitleaks]:

[tool.gitleaks.extend]
useDefault = true

[tool.gitleaks.allowlist]
paths = ['''\.env\.sample$''']

gitleaks dir . scans the working tree; gitleaks git scans history instead. The [tool.gitleaks] section is bridged to a temporary .gitleaks.toml: keep extend.useDefault = true, or a custom config silently replaces the built-in rule set.

labelmakerยถ

Stars Last release

Installed version: 0.6.4

Installation method: Binary (downloaded from GitHub Releases)

Config: CLI flags only

Source | CLI usage

labelmaker syncs a repositoryโ€™s issue and PR labels from a label-definition file, so unlike the linters it needs a target repository and a GITHUB_TOKEN, not a path in the working tree. There is no [tool.labelmaker] section: the label file is the configuration. See the upstream usage docs for its flags and file schema.

Lycheeยถ

Stars Last release

Installed version: 0.24.2

Installation method: Binary (downloaded from GitHub Releases)

Config files: lychee.toml and [tool.lychee] in pyproject.toml (native)

Source | Config reference | CLI usage

Try it:

$ repomatic run lychee -- .

Minimal [tool.lychee]:

[tool.lychee]
max_redirects = 5

lychee checks links found in the given path. Since v0.24 it reads [tool.lychee] from pyproject.toml natively, so repomatic does not translate it.

mdformatยถ

Stars Last release

Installed version: 1.0.0

Installation method: PyPI, installed via uvx

Config files: .mdformat.toml and [tool.mdformat] in pyproject.toml (native)

Default flags: --strict-front-matter

Bundled default: mdformat.toml

Plugins:

  • mdformat_admon

  • mdformat-config

  • mdformat_deflist

  • mdformat_footnote

  • mdformat-front-matters

  • mdformat-gfm

  • mdformat_gfm_alerts

  • mdformat_myst

  • mdformat-pelican

  • mdformat_pyproject

  • mdformat-recover-urls

  • mdformat-shfmt

  • mdformat_simple_breaks

  • mdformat-toc

  • mdformat-web

Source | Config reference | CLI usage

Try it:

$ repomatic run mdformat -- .

Minimal [tool.mdformat]:

[tool.mdformat]
wrap = "no"

mdformat rewrites Markdown in place. repomatic bundles a plugin set (GFM, MyST, front-matter, and others) and a baseline mdformat.toml; [tool.mdformat] in your pyproject.toml overrides it.

mypyยถ

Stars Last release

Installed version: 2.3.0

Installation method: PyPI, runs in project virtualenv via uv run

Config: [tool.mypy] in pyproject.toml (native)

Default flags: --color-output

Source | Config reference | CLI usage

Try it:

$ repomatic run mypy -- .

Minimal [tool.mypy]:

[tool.mypy]
strict = true

mypy runs inside the project virtualenv (via uv run) so it can import your dependencies. repomatic derives --python-version from requires-python, so the check matches your lowest supported interpreter. In a repository without a uv.lock there is no project virtualenv to freeze, so mypy runs in an isolated environment instead and only resolves the standard library: fine for standalone scripts, but dependency imports then report import-not-found.

uv run provisions only the default dependency groups, so a module that imports a dep declared solely in a non-default group (docs, typing, โ€ฆ) sees it as missing and mypy reports import-not-found. Either move the stub/dependency somewhere mypy resolves, or silence it with an override:

[[tool.mypy.overrides]]
module = "the_docs_only_package.*"
ignore_missing_imports = true

Nuitkaยถ

Stars Last release

Installed version: 4.1.3

Installation method: PyPI, runs in project virtualenv via uv run

Config: [tool.nuitka] in pyproject.toml (translated to CLI flags)

Default flags: --mode=onefile --assume-yes-for-downloads

Source | Config reference | CLI usage

Try it:

$ repomatic run nuitka -- my_app/__main__.py

Minimal [tool.nuitka]:

[tool.nuitka]
onefile = true
output-dir = "build"

repomatic reads every key from [tool.nuitka] and forwards it as a CLI flag: true becomes a bare --flag, a string or number becomes --key=value, and a list repeats the flag once per item. Nuitka does not read [tool.nuitka] natively yet (Nuitka#3909); repomaticโ€™s bridge fills the gap until it does.

Binaries skip tkinter by default, via the nuitka.nofollow-imports setting of [tool.repomatic]: set it to [] to bundle Tcl/Tk in a GUI project.

Oxipngยถ

Stars Last release

Installed version: 10.2.0

Installation method: Binary (downloaded from GitHub Releases)

Config: CLI flags only

Source | CLI usage

Try it:

$ repomatic run oxipng -- --opt 4 --strip safe image.png

Lossless PNG optimizer. repomatic format-images reaches it through repomatic.tool_runner.ensure_binary(), so the pinned, checksum-verified build is used instead of whatever the runner image or the distro archive supplies.

pyproject-fmtยถ

Stars Last release

Installed version: 2.27.0

Installation method: PyPI, installed via uvx

Config files: pyproject-fmt.toml and [tool.pyproject-fmt] in pyproject.toml (native)

Source | Config reference | CLI usage

Try it:

$ repomatic run pyproject-fmt -- pyproject.toml

Minimal [tool.pyproject-fmt]:

[tool.pyproject-fmt]
indent = 4

pyproject-fmt normalizes and reorders pyproject.toml in place. It reads its own [tool.pyproject-fmt] section natively.

Ruffยถ

Stars Last release

Installed version: 0.16.2

Installation method: PyPI, installed via uvx

Config files: .ruff.toml, ruff.toml and [tool.ruff] in pyproject.toml (native)

Bundled default: ruff.toml

Source | Config reference | CLI usage

Try it:

$ repomatic run ruff -- check .

Minimal [tool.ruff]:

[tool.ruff]
line-length = 100

ruff check . lints; ruff format . reformats. Both read [tool.ruff] natively. With no project config, repomatic falls back to its bundled ruff.toml baseline.

shfmtยถ

Stars Last release

Installed version: 3.13.1

Installation method: Binary (downloaded from GitHub Releases)

Config files: .editorconfig

Default flags: --write

Source | Config reference | CLI usage

Try it:

$ repomatic run shfmt -- .

shfmt formats shell scripts in place. It has no [tool.shfmt] section: indentation and style come from .editorconfig (indent_size, shell_variant, and the shfmt-specific keys).

typosยถ

Stars Last release

Installed version: 1.49.0

Installation method: Binary (downloaded from GitHub Releases)

Config files: typos.toml, _typos.toml, .typos.toml and [tool.typos] in pyproject.toml (native)

Default flags: --write-changes

Source | Config reference | CLI usage

Try it:

$ repomatic run typos -- .

Minimal [tool.typos]:

[tool.typos.files]
extend-exclude = ["*.lock"]

typos scans the tree and, with repomaticโ€™s default --write-changes, fixes what it finds. It reads [tool.typos] natively; use [tool.typos.default.extend-words] to map project-specific terms to their intended spelling.

Because the fix-typos workflow job ships whatever typos rewrites as an unattended pull request, guard content where a โ€œcorrectionโ€ is a corruption with [tool.typos.default.extend-ignore-re] patterns. The two known traps are encoded hashes, whose random letter runs typos happily respells (a Guix (base32 "...") source hash losing its value to an an-to-and fix), and intentional-typo examples that docs or tests exercise on purpose:

[tool.typos.default]
extend-ignore-re = [
  'base32 "[0-9a-z]{52}"',
  "\\{query\\}",
]

yamllintยถ

Stars Last release

Installed version: 1.38.0

Installation method: PyPI, installed via uvx

Config files: .yamllint, .yamllint.yaml, .yamllint.yml

[tool.yamllint] bridge: repomatic translates to YAML and passes via --config-file.

Default flags: --strict

CI flags: --format github

Bundled default: yamllint.yaml

Source | Config reference | CLI usage

Try it:

$ repomatic run yamllint -- .

Minimal [tool.yamllint]:

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

yamllint has no native pyproject.toml support, so repomatic bridges [tool.yamllint] to a temporary YAML config passed via --config-file. With no project config it uses repomaticโ€™s strict bundled yamllint.yaml.

zizmorยถ

Stars Last release

Installed version: 1.29.0

Installation method: PyPI, installed via uvx

Config files: .github/zizmor.yml, .github/zizmor.yaml, zizmor.yml, zizmor.yaml

[tool.zizmor] bridge: repomatic translates to YAML and passes via --config.

Default flags: --offline

CI flags: --format github

Bundled default: zizmor.yaml

Source | Config reference | CLI usage

Try it:

$ repomatic run zizmor -- .

zizmor audits GitHub Actions workflows for security issues, offline by default. repomatic bridges [tool.zizmor] to a temporary YAML config (passed via --config); with none, it uses the bundled zizmor.yaml. See the configuration reference for available keys.

repomatic.tool_runner APIยถ

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. The tool catalog it drives (ToolSpec entries, pinned versions, checksums) lives in tool_registry.py.

Important

Config resolution precedence (first match wins, no merging):

  1. Native config file โ€” toolโ€™s own config file in the repo.

  2. ``[tool.X]`` in ``pyproject.toml`` โ€” translated to native format.

  3. Bundled default โ€” from repomatic/data/.

  4. Bare invocation โ€” no config at all.

repomatic.tool_runner.load_pyproject_tool_section(tool_name)[source]ยถ

Load [tool.<tool_name>] from pyproject.toml in the current directory.

Returns the live tomlrt.Table (a dict subclass) rather than a plain-dict copy, so the section keeps its comment trivia for formats that can preserve it on materialization (see NativeFormat.serialize()). Callers that only read values or test truthiness are unaffected.

Return type:

dict[str, Any]

Returns:

The toolโ€™s config table, or empty dict if not found.

repomatic.tool_runner.resolve_config(spec, tool_config=None)[source]ยถ

Resolve config for a tool using the 4-level precedence chain.

Caution

The levels do not merge. The walk stops at its first hit, so a native config file or a [tool.X] section replaces the bundled default in full rather than layering on top of it. A downstream repo overriding one rule must restate every bundled rule it wants to keep, and gains nothing when the bundled default later grows a rule. resolve_config_source() labels a shadowing config so repomatic run --list shows the loss.

Parameters:
  • spec (ToolSpec) โ€“ Tool specification.

  • tool_config (dict[str, Any] | None) โ€“ Pre-loaded [tool.X] config dict. If None, reads from pyproject.toml in the current directory.

Return type:

tuple[list[str], Path | None]

Returns:

Tuple of (extra CLI args for config, path to clean up). The path is None when no cleanup is needed (cache-based configs persist across runs). Non-None paths are CWD files written for tools that have no --config flag.

repomatic.tool_runner.DOWNLOAD_TIMEOUT = 30ยถ

Socket-level timeout for artifact downloads, in seconds.

A stall guard, not a transfer budget: urlopen applies it to each blocking socket operation, so a healthy multi-minute download is unaffected while a dead connection fails in seconds instead of hanging a CI job to the runner ceiling. Deliberately larger than repomatic.http.DEFAULT_TIMEOUT, which is sized for small JSON API responses.

repomatic.tool_runner.download_to(url, dest_path, *, label=None, progress=True)[source]ยถ

Stream url into dest_path and return its SHA-256 hex digest.

Chunked download with incremental hash computation, so large binaries never load fully into memory. Shows a progress bar on interactive terminals when the server provides a Content-Length header; pass progress=False from concurrent callers whose fan-out draws its own progress display.

The single download seam for every artifact repomatic fetches by hand: whatever consumes the digest (verification in _download_and_verify(), checksum harvesting in checksums.py) builds on this so the truncation guard below applies to all of them. A short body (proxy hiccup, dropped connection) hashes to a wrong digest, so without the guard it would surface later as a checksum mismatch: that reads as a stale pin or a tampered artifact when nothing is wrong upstream. Name the real failure instead.

Parameters:
  • url (str) โ€“ URL to download.

  • dest_path (Path) โ€“ Where to write the downloaded file.

  • label (str | None) โ€“ Progress bar label. Defaults to the destination filename.

  • progress (bool) โ€“ Draw per-download feedback on interactive terminals.

Return type:

str

Returns:

Lowercase hex SHA-256 digest of the downloaded bytes.

Raises:

OSError โ€“ If the body is shorter than the advertised Content-Length.

repomatic.tool_runner.ensure_binary(name: str) Path[source]ยถ

Install a registry binary tool and return the path to its executable.

The seam for repomatic code that shells out to a third-party binary but is not itself a run_tool() invocation. It buys the same guarantees every repomatic run binary gets: the registry-pinned version, its archive verified against the recorded SHA-256, and a shared cache so repeated calls in one run download once.

Prefer this over looking the tool up on PATH. Whatever PATH offers is whichever version the machine or CI image happens to carry, unpinned and unverified, and it differs between a developerโ€™s laptop and every runner.

Memoized per tool name: callers in a loop (format-images optimizing one PNG per call) hit the install-and-verify path once per process, not once per file. Failures are not memoized, so a transient download error can be retried.

Parameters:

name (str) โ€“ Registry key of a tool whose ToolSpec declares a binary.

Return type:

Path

Returns:

Absolute path to the ready-to-run executable.

Raises:

ClickException โ€“ If the tool is unknown, ships no binary, or cannot be downloaded and verified.

repomatic.tool_runner.resolve_default_args(spec)[source]ยถ

Build the argument batches for a bare repomatic run <tool>.

Combines default_args with the file list named by default_paths, splitting into one batch per file when per_file is set.

Parameters:

spec (ToolSpec) โ€“ The tool to resolve defaults for.

Return type:

list[list[str]] | None

Returns:

One argument list per invocation; a single empty-argument batch when the tool declares no defaults, so the caller runs it bare as before. None when the tool wants targets and the repository holds none, which means skip the tool rather than invoke it pathless.

repomatic.tool_runner.TOOL_CRASH_EXIT_CODE = 70ยถ

Exit code reported when a tool contradicts its own rewrite status.

EX_SOFTWARE from sysexits.h: an internal error in the tool being run. Deliberately outside the set a formatterโ€™s caller tolerates, so a crash cannot land on the code that means โ€œI reformatted a fileโ€. See rewrite_exit_code.

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.

With no extra_args, a tool declaring default_args or default_paths runs the invocation CI performs, resolved in-process by resolve_default_args(). Any explicit argument suppresses that entirely and is passed through as before.

Parameters:
  • name (str) โ€“ Tool name (must be in TOOL_REGISTRY).

  • extra_args (Sequence[str]) โ€“ Extra arguments passed through to the tool.

  • version (str | None) โ€“ Override the pinned version.

  • 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 when True.

Return type:

int

Returns:

The toolโ€™s exit code; the first non-zero one when the defaults resolved to several invocations, or TOOL_CRASH_EXIT_CODE when a tool declaring rewrite_exit_code reports a rewrite it did not perform.

repomatic.tool_runner.verify_via_write_path(name, extra_args=(), **run_kwargs)[source]ยถ

Check a post_process toolโ€™s formatting without touching the tree.

A tool pairing post_process with check_flags has no trustworthy check mode: the fixup only runs on the write path, so the check status can flag drift the write path would reconcile, or miss drift it would introduce (see check_flags). This runs the write path against throwaway copies instead, then compares, which is the only authoritative answer.

Important

The copies are made inside the working directory, not in the system temp area. Formatters discover their config by walking up from each file, so a copy parked outside the repository resolves a different config and silently reports drift that does not exist.

The working tree is never written to: only the copies are formatted, and they are removed before returning.

Parameters:
  • name (str) โ€“ Tool name, as in run_tool().

  • extra_args (Sequence[str]) โ€“ Arguments for the tool. Any existing path among them is copied and rewritten to its copy; check flags are dropped, since they would defeat the write path this relies on. Every other argument is passed through untouched. Empty resolves the toolโ€™s registry defaults, the same set run_tool() would have run, flattened into one batch: the copies are per-path already, so a per_file split would only cost extra invocations.

  • run_kwargs (Any) โ€“ Forwarded verbatim to run_tool().

Return type:

tuple[int, list[str]]

Returns:

(exit_code, drifted), where exit_code is 0 when every target is already formatted and 1 otherwise, and drifted names the paths the write path would have changed. A tool that fails on the copies yields its own exit code and no drift, since it measured nothing.

repomatic.tool_runner.resolve_config_source(spec)[source]ยถ

Return a human-readable description of the active config source.

Used by repomatic run --list to show which precedence level is active for each tool in the current repo.

Return type:

str

repomatic.tool_runner.find_unmodified_configs(root=None)[source]ยถ

Find native config files identical to their bundled defaults.

Iterates over every tool in TOOL_REGISTRY that has a default_config. For each, checks whether any of its native_config_files exists 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_files when writing files during init.

Parameters:

root (Path | None) โ€“ Directory the relative config paths resolve against. Defaults to the working directory; run_init passes its output_dir so the scan and the deletion the CLI derives from it (--delete-unmodified) agree on one tree.

Return type:

list[tuple[str, str]]

Returns:

List of (tool_name, relative_path) tuples for each unmodified file found.