lintsmith
Generate lean, pinned Super-Linter images that contain only the linters a repository uses, to cut the cold pull-and-unpack time in CI.
Formerly superlint-lean. The old PyPI package and repository URL keep working, but
new releases are published as lintsmith.
Status: working, early. On GitHub-hosted runners a lean image pulls 5x to 8x
faster than upstream slim:
| Stack | Upstream slim | Lean | Faster |
|---|---|---|---|
| ghwm | 70.7 s | 10.7 s | 6.6x |
| python | 67.9 s | 11.6 s | 5.9x |
| frontend-react | 68.7 s | 10.4 s | 6.6x |
| dotnet (against upstream standard) | 91.0 s | 16.9 s | 5.4x |
Cold pull, median of 3, upstream v9.0.0. All profiles, the method and the caveats are in docs/benchmarks.
Idea
- A repository declares the linters it needs, or they are discovered from the
VALIDATE_*flags in its Super-Linter workflow. - A compiler builds an overlay image: same Python/Alpine base as upstream, plus only the needed tool paths copied from the upstream release image.
- A PR check fails when a change adds file types the current image does not cover.
- A scheduled job rebuilds when upstream publishes a new release.
- The workflows are distributed through the ghwm registry; see Run the lean image with the ghwm registry workflows.
Install
The CLI is a Python package, managed with uv:
uv tool install lintsmith # or: pipx install lintsmith
uvx lintsmith --help # run without installing
The latest unreleased code is available with
uvx --from git+https://github.com/ghwfxlab/lintsmith lintsmith --help.
Commands:
| Command | Purpose |
|---|---|
lintsmith generate |
VALIDATE_* flags (env files or a workflow) -> Dockerfile, tool self-checks and plan |
lintsmith drift |
PR drift check: fails when changed files are not covered by the enabled linters |
lintsmith sync-mapping |
Compare the linter mapping with an upstream checkout and add draft entries |
lintsmith release-notes |
Release notes with before/after benchmarks of two upstream versions |
lintsmith compare, summarize |
Markdown tables from benchmark results: lean against baseline, and every variant on its own |
lintsmith repository-settings |
Print or apply the repository settings in docs/repository-settings.md (maintainers) |
lintsmith bench |
Time cold or warm pulls and container start of an image (the benchmark workflow) |
lintsmith select-upstream |
Pick the upstream release to sync to, honouring a cooldown |
lintsmith smoke |
Lint upstream's good and bad fixtures with a built image, one linter at a time |
Layout
| Path | Purpose |
|---|---|
src/lintsmith/ |
The package: cli.py, generate.py, drift.py, config.py (reads workflows and env files with upstream's VALIDATE_* semantics), mapping_sync.py, release_notes.py |
src/lintsmith/data/ |
linters.json (flag -> upstream paths, venvs, npm and apk packages), filetypes.json (file type rules transcribed from upstream), prune-node-modules.js |
tests/ |
pytest suite (Arrange/Act/Assert), using the ghwm workflow as a fixture |
base/opinionated.env |
The opinionated base: the checks almost every repository wants (EditorConfig, env, actionlint, zizmor, gitleaks, JSON, Markdown, natural language, YAML, shell, hadolint) |
toolchains/ |
Example toolchains on top of the base: python, Java, dotnet, rust, c-cpp, frontend-{vanilla,react,vue,angular,svelte}, base-only |
examples/ |
ghwm.env (a real repository's exact flags, without the base) and with-extras.env |
.github/workflows/ |
tests, profiles (benchmarks), upstream-sync, release, self-image (this repository's own lean image), and the reusable drift-blocker |
Development
make install # uv sync
make setup-precommit # pre-commit hooks (super-linter and the tests)
make test # pytest with coverage
make lint # ruff
make type-check # mypy
make lang # textlint for the docs
make super-linter # super-linter via Docker
make build # sdist and wheel
See CONTRIBUTING.md for the workflow, AGENTS.md for the guide used by coding agents, docs/ARCHITECTURE.md and docs/reference/cli.md for the internals and the command reference, docs/verifying-images.md to check a pulled image, and docs/repository-settings.md for the repository settings. Report vulnerabilities as described in SECURITY.md.
Configuration
Nothing is fixed. The base and the toolchains are plain env files with
VALIDATE_*=true lines, and the generator takes any combination of them:
- leave the base out, or write your own
- stack several toolchains (
--env toolchains/python.env --env toolchains/rust.env) - drop single linters from a combination with
--exclude NATURAL_LANGUAGE - add tools Super-Linter does not ship: Alpine packages (
--extra-apk), npm packages (--extra-npm) and pip packages (--extra-pip), plus--extra-checkcommands that must succeed in the built image. They can also be written in an env file (EXTRA_APK=,EXTRA_NPM=,EXTRA_PIP=,EXTRA_CHECK=), seeexamples/with-extras.env. Extras are installed at build time from the package registries, in their own layers (/opt/extras), and npm installs run with--ignore-scripts
The toolchains in this repository, and the numbers in docs/benchmarks, are examples of combinations, not the supported set.
The reason for an opinionated base is that a repository using one linter has no use for Super-Linter. The value is in running a broad set of checks, so the base is broad and each toolchain adds a stack on top.
Block pull requests that outgrow the image
The image only contains the linters you enabled. If a pull request adds a file
type whose linters are not enabled (a .rs file when no Rust linter is on),
Super-Linter would silently skip it. The drift blocker fails that pull request
and says which linter to enable:
jobs:
drift:
permissions:
contents: read
uses: ghwfxlab/lintsmith/.github/workflows/drift-blocker.yml@main
with:
workflow: .github/workflows/super-linter.yaml # the workflow with the super-linter step
allow_ext: "txt lock" # file types you deliberately do not lint
The configuration is read from the env: of the Super-Linter step, with the
upstream rules: if any VALIDATE_X is true, only those linters run; if only
false values are set, everything else runs. FILTER_REGEX_INCLUDE and
FILTER_REGEX_EXCLUDE are respected. The same discovery works in the compiler:
lintsmith generate --workflow .github/workflows/super-linter.yaml .... Content-based
detections (OpenAPI, CloudFormation, Kubernetes, ARM, AWS States, Ansible) are
not modelled, so a YAML file of those kinds is only checked as plain YAML.
Try it locally
lintsmith generate --env base/opinionated.env --env toolchains/python.env \
--upstream-tag v9.0.0 --upstream-dockerfile <upstream Dockerfile at that tag> --out build/python
docker buildx build build/python -t lean-python
docker run --rm -v "$PWD/build/python/checks.sh:/checks.sh:ro" --entrypoint bash lean-python /checks.sh
Profiles marked as needing the upstream standard image (dotnet, rust) are
compared against standard, not slim. Upstream has no Svelte-specific linter,
so the Svelte profile covers the JS, TS, CSS and HTML around it.
Limitations
- Only linters in the mapping (
src/lintsmith/data/linters.json, 95 flags for upstream v9.0.0) can be selected. An unknown flag stopsgeneratewith a message, and new upstream linters start as drafts that need a review (see CONTRIBUTING.md). - The generator reads upstream's published image and
Dockerfile. A release that moves tools around can break the mapping;upstream-syncreports it before the pin moves. - The image is rebuilt after a change reaches
main. A pull request that changes the enabled linters is still linted with the previous image, and the drift check only catches file types that no enabled linter covers. - The saving comes from leaving linters out. With every mapped linter enabled the image is only about 11% smaller than upstream standard.
- A runner that already holds the upstream image gains nothing: the pull is a no-op either way.
- Pulling a private image needs credentials:
packages: readfor GHCR in the same organization, otherwise a registry login. - Only
linux/amd64is tested. Images are not signed yet. - zstd layers are published and pull 0% to 15% faster than gzip; that is small next to leaving linters out.
Licence
This repository is MIT licensed (see LICENSE). It contains no third-party linters. A generated image is built by copying files out of the published Super-Linter image, so it contains Super-Linter (MIT) and the linters you select, each under its own licence. If you publish such an image you redistribute those tools and must follow their licences; check them for the linters you pick. The smoke test reads Super-Linter's test fixtures at build time and does not copy them into this repository or into an image.
Metadata
Release files for lintsmith 0.0.16
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| lintsmith-0.0.16.tar.gz | 60.8 kB | With provenance | Details for lintsmith-0.0.16.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| lintsmith-0.0.16-py3-none-any.whl | Python 3 | none | any | Details for lintsmith-0.0.16-py3-none-any.whl |
Total release size: 104.4 kB
Release files / lintsmith-0.0.16.tar.gz
| Download URL | lintsmith-0.0.16.tar.gz |
|---|---|
| Size | 60.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
600acc1054fef01f9bbd9ed27e436228813efdde7c20e6b82be84d1e6e06cf41
|
|
BLAKE2b-256 checksum How to use checksums |
b28fe319aeaf8fe2a8768640c823a83d20f29f5afbe1a54ca450d894fb6d510c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logRelease files / lintsmith-0.0.16-py3-none-any.whl
| Download URL | lintsmith-0.0.16-py3-none-any.whl |
|---|---|
| Size | 43.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
edfa335721eb14b12c16053e5a609b7e01224e1ff47968a20ccc54c031dd9841
|
|
BLAKE2b-256 checksum How to use checksums |
abf93cdd122ac1bf3f3e40795e2c01acfa2ca9856da10ba9ad7cd966faad8e97
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logSecurity for lintsmith 0.0.16
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for lintsmith-0.0.16.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 29, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.