Linter runner built for speed, consistency, and low setup friction:
- Fast — native execution (no Docker), parallel, diff-aware (changed files only), opt-in (undeclared tools don't run), small binary cached by mise
- Local == CI — one binary, one config, identical behavior
- Sensible defaults —
flint initscaffolds a working setup quickly, and most repos can stick with the generated defaults - Opinionated config — Flint chooses canonical config filenames per linter,
while still letting you keep them in a directory such as
.github/config - AI-friendly — fix silently, surface only what needs review
- Separated ownership — dedicated linters and formatters own their file types to avoid overlapping rules and editor-config conflicts
- Predictable and updatable linter versions — lint behavior stays stable
until the repo intentionally updates pinned linter versions, for example via
Renovate updates to
mise.toml - Cross-platform — Linux, macOS, Windows
- Autofix —
--fixfixes what's fixable; reports what still needs review
Read the background and principles and alternatives/comparisons.
Tip
Legacy v1 (bash task scripts): see README-V1.md.
Add flint to your repo's mise.toml:
[tools]
"aqua:grafana/flint" = "0.21.0"Bootstrap a repo with flint init (scaffolds config). Install a
pre-commit hook with flint hook install.
This is appropriate even if the repo already has an existing mise.toml;
flint init is not just for greenfield repos. You can choose which linters to
enable during the prompt, or trim the generated tool list afterward if you run
flint init --yes.
Flint reads your [tools] section to discover which linters to run — declaring
a tool is the opt-in. No separate configuration needed to activate a check: if
ShellCheck's Flint-managed tool key is present in [tools], flint runs
shellcheck; otherwise that check is skipped. mise install puts all declared
tools on PATH; flint picks up whatever is there.
Add the linting tools your project needs alongside the flint binary itself:
[tools]
"aqua:grafana/flint" = "0.21.0"
# Add whichever linters apply to your repo:
shellcheck = "0.11.0"
shfmt = "v3.13.1"
actionlint = "1.7.10"Then wire up lint tasks:
[tasks.lint]
description = "Run all lints"
run = "flint run"
[tasks."lint:fix"]
description = "Auto-fix lint issues"
run = "flint run --fix"- name: Checkout code
uses: actions/checkout@...
with:
fetch-depth: 0 # needed for merge-base detection
- name: Setup mise
uses: jdx/mise-action@...
- name: Lint
env:
GITHUB_REPOSITORY: ${{ github.repository }}
GITHUB_BASE_REF: ${{ github.base_ref }}
GITHUB_HEAD_REF: ${{ github.head_ref }}
PR_HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name || github.repository }}
GITHUB_TOKEN: ${{ github.token }}
run: mise run lintfetch-depth: 0 is required for merge-base detection. GITHUB_TOKEN is needed
by some checks that query GitHub, but not every check. If lychee link checks
are enabled, see lychee for PR remap environment
requirements.
See the CLI reference for commands and flags.
Optional. Place in the repo root (or in FLINT_CONFIG_DIR — see below).
All settings have defaults.
[settings]
# base_branch = "dev" # branch to diff against; defaults to "main"
exclude = ["CHANGELOG.md", "vendor/**"] # glob patterns — exclude matching filesSee linters for per-linter configuration.
Set this env var to consolidate config files in one directory (e.g. .github/config):
# mise.toml
[env]
FLINT_CONFIG_DIR = ".github/config"When set, flint.toml is loaded from that directory, and each linter that
supports an explicit config path via a CLI flag will have it injected
automatically when the corresponding canonical Flint-managed file exists there
(see the "Config file" column in the table below).
Files that are absent are silently skipped. Some tools still rely on project-root
discovery semantics, and some alternate upstream config locations are rejected to
avoid config drift. In practice, Flint is opinionated about which config filename
each linter should use, but flexible about the directory those files live in.
Note
editorconfig-checker's config file (.editorconfig-checker.json) controls
its own settings, not .editorconfig itself. Editorconfig discovery always
walks up from the file being linted and cannot be redirected via a flag.
When a formatter explicitly owns line length for a file type, Flint writes that
carve-out into the shared root .editorconfig so editors and
editorconfig-checker stay aligned. Today this applies to Markdown via rumdl,
Rust via rustfmt, and Java via google-java-format.
Note
Biome is also root-discovered on purpose. Flint treats root biome.jsonc as
the canonical Biome config rather than managing it through
FLINT_CONFIG_DIR.
Click a name in the table below for details. See the linter reference for scope semantics and per-linter notes.
| Name | Description | Fix |
|---|---|---|
actionlint |
Lint GitHub Actions workflow files | — |
biome |
Lint JS/TS/JSON files | yes |
biome-format |
Format JS/TS/JSON files | yes |
cargo-clippy |
Lint Rust code; runs on all .rs files, not just changed | yes |
cargo-fmt |
Format Rust code; runs on all .rs files, not just changed | yes |
codespell |
Check for common spelling mistakes | yes |
dotnet-format |
Format C# code | yes |
editorconfig-checker |
Check files comply with EditorConfig settings | — |
flint-setup |
Keep Flint setup current and mise.toml lint tooling canonical | yes |
gofmt |
Format Go code | yes |
golangci-lint |
Lint Go code; uses --new-from-rev to scope analysis to changed code | — |
google-java-format |
Format Java code | yes |
hadolint |
Lint Dockerfiles | — |
ktlint |
Lint and format Kotlin code | yes |
license-header |
Check source files have the required license header | — |
lychee |
Check for broken links | — |
renovate-deps |
Verify Renovate dependency snapshot is up to date | yes |
ruff |
Lint Python code | yes |
ruff-format |
Format Python code | yes |
rumdl |
Lint Markdown files for style and consistency | yes |
ryl |
Lint YAML files for style and consistency | yes |
shellcheck |
Lint shell scripts for common mistakes | — |
shfmt |
Format shell scripts | yes |
taplo |
Format TOML files | yes |
xmllint |
Validate XML files are well-formed | — |
This project uses Semantic Versioning. Breaking changes will be documented in CHANGELOG.md and will result in a major version bump.
See RELEASING.md.