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, and tools that import your code (mypy, Nuitka) run inside the project virtualenv.

  • 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  โ”‚ (bare)                          โ”‚
โ”‚ autopep8        โ”‚ 2.3.2   โ”‚ (bare)                          โ”‚
โ”‚ biome           โ”‚ 2.5.0   โ”‚ (bare)                          โ”‚
โ”‚ bump-my-version โ”‚ 1.2.7   โ”‚ (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            โ”‚ 1.19.1  โ”‚ [tool.mypy] in pyproject.toml   โ”‚
โ”‚ nuitka          โ”‚ 4.1     โ”‚ [tool.nuitka] in pyproject.toml โ”‚
โ”‚ pyproject-fmt   โ”‚ 2.25.0  โ”‚ (bare)                          โ”‚
โ”‚ ruff            โ”‚ 0.15.5  โ”‚ [tool.ruff] in pyproject.toml   โ”‚
โ”‚ shfmt           โ”‚ 3.13.1  โ”‚ (bare)                          โ”‚
โ”‚ typos           โ”‚ 1.47.2  โ”‚ [tool.typos] in pyproject.toml  โ”‚
โ”‚ yamllint        โ”‚ 1.38.0  โ”‚ bundled default                 โ”‚
โ”‚ zizmor          โ”‚ 1.23.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

Biome

2.5.0

Binary

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

bump-my-version

1.2.7

PyPI

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

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

1.19.1

PyPI (venv)

[tool.mypy] in pyproject.toml

Nuitka

4.1

PyPI (venv)

[tool.nuitka] in pyproject.toml

pyproject-fmt

2.25.0

PyPI

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

Ruff

0.15.5

PyPI

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

shfmt

3.13.1

Binary

.editorconfig

typos

1.47.2

Binary

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

yamllint

1.38.0

PyPI

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

zizmor

1.23.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.

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: 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  โ”‚ (bare)                          โ”‚
โ”‚ autopep8        โ”‚ 2.3.2   โ”‚ (bare)                          โ”‚
โ”‚ biome           โ”‚ 2.5.0   โ”‚ (bare)                          โ”‚
โ”‚ bump-my-version โ”‚ 1.2.7   โ”‚ (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            โ”‚ 1.19.1  โ”‚ [tool.mypy] in pyproject.toml   โ”‚
โ”‚ nuitka          โ”‚ 4.1     โ”‚ [tool.nuitka] in pyproject.toml โ”‚
โ”‚ pyproject-fmt   โ”‚ 2.25.0  โ”‚ (bare)                          โ”‚
โ”‚ ruff            โ”‚ 0.15.5  โ”‚ [tool.ruff] in pyproject.toml   โ”‚
โ”‚ shfmt           โ”‚ 3.13.1  โ”‚ (bare)                          โ”‚
โ”‚ typos           โ”‚ 1.47.2  โ”‚ [tool.typos] in pyproject.toml  โ”‚
โ”‚ yamllint        โ”‚ 1.38.0  โ”‚ bundled default                 โ”‚
โ”‚ zizmor          โ”‚ 1.23.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.

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.

Tool detailsยถ

actionlintยถ

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

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ยถ

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 --.

Biomeยถ

Installed version: 2.5.0

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ยถ

Installed version: 1.2.7

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.

Gitleaksยถ

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ยถ

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ยถ

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ยถ

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-ruff

  • mdformat-shfmt

  • mdformat_simple_breaks

  • mdformat-toc

  • mdformat-web

  • ruff

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ยถ

Installed version: 1.19.1

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.

Nuitkaยถ

Installed version: 4.1

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.

pyproject-fmtยถ

Installed version: 2.25.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ยถ

Installed version: 0.15.5

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ยถ

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ยถ

Installed version: 1.47.2

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.

yamllintยถ

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ยถ

Installed version: 1.23.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.

Comparisonยถ

Tool

Stars

Last release

Last commit

Commits

Dependencies

Language

License

actionlint

Stars

Last release

Last commit

Commits

Dependencies

Language

License

autopep8

Stars

Last release

Last commit

Commits

Dependencies

Language

License

Biome

Stars

Last release

Last commit

Commits

Dependencies

Language

License

bump-my-version

Stars

Last release

Last commit

Commits

Dependencies

Language

License

Gitleaks

Stars

Last release

Last commit

Commits

Dependencies

Language

License

labelmaker

Stars

Last release

Last commit

Commits

Dependencies

Language

License

Lychee

Stars

Last release

Last commit

Commits

Dependencies

Language

License

mdformat

Stars

Last release

Last commit

Commits

Dependencies

Language

License

mypy

Stars

Last release

Last commit

Commits

Dependencies

Language

License

Nuitka

Stars

Last release

Last commit

Commits

Dependencies

Language

License

pyproject-fmt

Stars

Last release

Last commit

Commits

Dependencies

Language

License

Ruff

Stars

Last release

Last commit

Commits

Dependencies

Language

License

shfmt

Stars

Last release

Last commit

Commits

Dependencies

Language

License

typos

Stars

Last release

Last commit

Commits

Dependencies

Language

License

yamllint

Stars

Last release

Last commit

Commits

Dependencies

Language

License

zizmor

Stars

Last release

Last commit

Commits

Dependencies

Language

License

repomatic.tool_runner APIยถ

        classDiagram
  Enum <|-- ArchiveFormat
  Enum <|-- NativeFormat
    

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):

  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.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) and version (package version).

repomatic.tool_runner.generated_header(command, comment_prefix='# ')[source]ยถ

Return a generated-by header block with timestamp.

Parameters:
  • command (str) โ€“ Full command path (e.g. repomatic sync-mailmap).

  • comment_prefix (str) โ€“ Comment prefix for the target format.

Return type:

str

class repomatic.tool_runner.ArchiveFormat(*values)[source]ยถ

Bases: Enum

Archive format for binary tool downloads.

RAW = 'raw'ยถ
TAR_GZ = 'tar.gz'ยถ
TAR_XZ = 'tar.xz'ยถ
ZIP = 'zip'ยถ
tarfile_mode()[source]ยถ

Return the tarfile.open mode 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: Enum

Target format for [tool.X] translation.

YAML = 'yaml'ยถ
TOML = 'toml'ยถ
JSON = 'json'ยถ
EDITORCONFIG = 'editorconfig'ยถ
FLAGS = 'flags'ยถ
serialize(data, tool_name='')[source]ยถ

Serialize a config dict to this formatโ€™s string representation.

When data is a live [tool.X] table parsed from pyproject.toml (a tomlrt.Table), the TOML branch keeps the userโ€™s comments by reparenting the section to the document root; see _reroot_section. A plain dict carries no trivia, so it is rendered as-is. The other formats (YAML, JSON, editorconfig) cannot carry TOML comments across the format boundary, so they serialize the values only.

Parameters:
  • data (dict) โ€“ Configuration dictionary to serialize.

  • tool_name (str) โ€“ Tool name for the generated-by header comment.

Raises:

ValueError โ€“ For FLAGS, which is not a file format.

Return type:

str

repomatic.tool_runner.PlatformKeyยถ

A (platform_or_group, architecture) pair used as binary lookup key.

The platform element can be a single Platform (like MACOS) or a Group (like LINUX, which matches any Linux distribution). The architecture is always a concrete Architecture.

Resolution order in BinarySpec.resolve_platform():

  1. Exact Platform match (current_platform() == key_platform).

  2. 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: object

Platform-specific binary download specification.

Keys are PlatformKey tuples pairing an extra-platforms Platform or Group with an Architecture. This lets callers use broad groups (LINUX matches 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 ArchiveFormat applies 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(): exact PlatformKey tuple first, then bare Platform equality, then Group membership (smallest group wins).

archive_executable: str | None = Noneยถ

Path of the executable inside the archive. None defaults to the tool name. For RAW format, used as the final filename.

strip_components: int = 0ยถ

Number of leading path components to strip when extracting.

resolve_platform()[source]ยถ

Match the current environment against registered platform keys.

Uses current_platform() and current_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_format is a single ArchiveFormat, returns it directly. When it is a dict, resolves in order: exact PlatformKey tuple, bare Platform equality, then Group membership (smallest group wins).

Return type:

ArchiveFormat

static platform_cache_key(key)[source]ยถ

Derive a filesystem-safe cache path segment from a platform key.

Return type:

str

Returns:

A string like linux-aarch64 or macos-x86_64.

class repomatic.tool_runner.ToolSpec(name, display_name=None, version='', package=None, executable=None, module=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, check_flags=(), binary=None, source_url=None, config_docs_url=None, cli_docs_url=None)[source]ยถ

Bases: object

Specification 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_subcommand

Tools that use subcommands (tool <subcmd> [flags] [files]) may require config_flag to 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, so tool <subcmd> --flag works but tool --flag <subcmd> does not. Set config_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'). None defaults to name.

version: str = ''ยถ

Pinned version (e.g., '1.38.0').

package: str | None = Noneยถ

PyPI package name. None defaults to name. Only set when the package name differs from the tool name.

executable: str | None = Noneยถ

Executable name if different from the tool name. None defaults to the registry key.

module: str | None = Noneยถ

Python module name for -m module invocation, e.g. 'nuitka'.

When set, the tool is invoked as python -m <module> instead of the console script. Requires needs_venv=True. Use when the toolโ€™s script entry point is not reliably found across platforms (for example, Nuitka installs only a .cmd wrapper on Windows, which uv run -- nuitka cannot locate).

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'). None if the tool only reads from fixed paths.

native_format: NativeFormat = 'yaml'ยถ

Target format for [tool.X] translation.

NativeFormat.FLAGS translates the table to CLI flags (via pyproject_table_to_flags) instead of a config file, for tools that expose their config keys as long options but read no config file themselves. It is mutually exclusive with reads_pyproject, config_flag, and native_config_files.

default_config: str | None = Noneยถ

Filename in repomatic/data/ for bundled defaults, stored in native_format. None if no bundled default exists.

reads_pyproject: bool = Falseยถ

Whether the tool natively reads [tool.X] from pyproject.toml.

When True and [tool.X] exists in pyproject.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.

default_flags: tuple[str, ...] = ()ยถ

Flags always passed to the tool (e.g., ('--strict',)).

ci_flags: tuple[str, ...] = ()ยถ

Flags added only when $GITHUB_ACTIONS is set (e.g., output format).

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, use uv run (project venv) instead of uvx (isolated).

Required when the tool imports project code (mypy, pytest).

computed_params: Callable[[Metadata], list[str]] | None = Noneยถ

Callable that receives a Metadata instance and returns extra CLI args derived from project metadata (e.g., mypyโ€™s --python-version from requires-python). None if no computed params.

config_after_subcommand: bool = Falseยถ

Insert config_flag after the first token of extra_args.

Needed for tools whose CLI parser (e.g., bpaf) scopes global options inside the subcommand, so tool subcommand --config-path X is valid but tool --config-path X subcommand is not. When True, config_args are spliced after the first element of extra_args (the subcommand name).

post_process: Callable[[Sequence[str]], None] | None = Noneยถ

Callback invoked on extra_args after the tool exits successfully.

Intended for temporary workarounds that fix known upstream formatting bugs in-place. Remove the callback once upstream ships the fix.

Note

The callback runs only after a successful write-mode exit (return code 0) and rewrites files on disk, so it cannot apply in check/dry-run mode, which writes nothing. Pair it with check_flags so run_tool warns when a check invocation would silently bypass it. See check_bypasses_post_process().

check_flags: tuple[str, ...] = ()ยถ

Flags that put the tool in check/dry-run mode, writing no files.

Warning

Check mode bypasses post_process: that fixup rewrites files on disk, but check mode writes nothing. So when a tool defines both a post_process and check_flags, its check-mode exit status is unreliable. run_tool detects the pairing via check_bypasses_post_process() and warns. Verify formatting by running the write path, not the check flag.

binary: BinarySpec | None = Noneยถ

Platform-specific binary download spec. When set, the tool is downloaded as a binary instead of installed via uvx or uv run.

source_url: str | None = Noneยถ

GitHub repository or project homepage URL.

config_docs_url: str | None = Noneยถ

URL to the toolโ€™s configuration reference.

cli_docs_url: str | None = Noneยถ

URL to the toolโ€™s CLI usage documentation.

check_bypasses_post_process(extra_args)[source]ยถ

Return True when a check-mode flag will skip post_process.

Check/dry-run flags (check_flags) make the tool exit without writing files, so the post_process fixup never runs and the exit status cannot be trusted: it may flag drift the write path would reconcile, or miss drift the write path would introduce. run_tool warns on this. Returns False for tools with no post_process, where check mode is authoritative.

Return type:

bool

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 a Path suitable for passing to external tools via --config <path>. The path is valid only within the context manager.

Return type:

Iterator[Path]

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.pyproject_table_to_flags(table)[source]ยถ

Translate a [tool.X] table into long-form CLI flags.

For tools whose command-line options mirror their config keys but which cannot read [tool.X] from pyproject.toml themselves and accept no config file. Follows the conventional mapping:

  • key = true โ†’ --key

  • key = "value" (or a number) โ†’ --key=value

  • key = ["a", "b"] โ†’ --key=a --key=b (one flag per item)

  • key = false is skipped: there is no universal --no-<key> form.

Keys keep their hyphenated spelling so they map straight onto long options, and flags follow their declaration order in pyproject.toml.

Return type:

list[str]

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

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

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.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 via run_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.

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

  • no_cache (bool) โ€“ Bypass the binary cache when True.

Yields:

Path to the ready-to-run executable.

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 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.

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()[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.

Return type:

list[tuple[str, str]]

Returns:

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