diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..7add053
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+root = true
+
+[*]
+max_line_length = 100
+indent_size = 2
+
+[*.{sh,py}]
+max_line_length = 120
+
+[*.py]
+indent_size = 4
+
+[{CLAUDE.md,.editorconfig,super-linter.env,lychee.toml,renovate.json5,default.json,mise.toml}]
+max_line_length = 300
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index 5496477..90918ca 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -1,10 +1,6 @@
{
$schema: "https://docs.renovatebot.com/renovate-schema.json",
- extends: [
- "config:recommended",
- "customManagers:dockerfileVersions",
- "customManagers:githubActionsVersions",
- ],
+ extends: ["config:recommended", "customManagers:dockerfileVersions", "customManagers:githubActionsVersions"],
branchPrefix: "grafanarenovatebot/",
dependencyDashboard: true,
platformCommit: "enabled",
@@ -24,9 +20,7 @@
customType: "regex",
description: "Update _VERSION variables in mise.toml",
managerFilePatterns: ["/^mise\\.toml$/"],
- matchStrings: [
- '# renovate: datasource=(?[a-z-]+?)(?: depName=(?.+?))?(?: packageName=(?.+?))?(?: versioning=(?[a-z-]+?))?\\s.+?_VERSION="?(?[^@"]+?)(?:@(?sha256:[a-f0-9]+))?"?\\s',
- ],
+ matchStrings: ['# renovate: datasource=(?[a-z-]+?)(?: depName=(?.+?))?(?: packageName=(?.+?))?(?: versioning=(?[a-z-]+?))?\\s.+?_VERSION="?(?[^@"]+?)(?:@(?sha256:[a-f0-9]+))?"?\\s'],
},
],
}
diff --git a/CLAUDE.md b/CLAUDE.md
index 7970789..f792d22 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -16,7 +16,11 @@ All task scripts follow these conventions:
- **Metadata**: Shell scripts use `#MISE` comments for metadata; Python scripts use `# [MISE]` comments
- **Usage args**: Shell scripts use `#USAGE` comments to define CLI arguments that mise parses
- **Exit behavior**: Scripts exit with non-zero on errors for CI integration
-- **AUTOFIX mode**: All lint scripts check the `AUTOFIX` environment variable. When `AUTOFIX=true`, linters that support fixing issues will automatically apply fixes; linters without fix capabilities silently ignore it. This allows consuming repos to run all lints with `AUTOFIX=true` via a single task (e.g., `mise run fix`) without needing per-linter configuration
+- **AUTOFIX mode**: All lint scripts check the `AUTOFIX` environment variable.
+ When `AUTOFIX=true`, linters that support fixing issues will automatically
+ apply fixes; linters without fix capabilities silently ignore it. This allows
+ consuming repos to run all lints with `AUTOFIX=true` via a single task
+ (e.g., `mise run fix`) without needing per-linter configuration
### Script Categories
diff --git a/README.md b/README.md
index 6cc5ca9..72afb98 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+
@@ -8,8 +9,11 @@
+
-A toolbox of reusable [mise](https://mise.jdx.dev/) lint task scripts. Pick the ones you need — each task is independent and can be adopted on its own.
+A toolbox of reusable [mise](https://mise.jdx.dev/) lint task scripts.
+Pick the ones you need — each task is independent and can be adopted
+on its own.
**Available tasks:**
@@ -19,11 +23,53 @@ A toolbox of reusable [mise](https://mise.jdx.dev/) lint task scripts. Pick the
| `lint:links` | [lychee](https://lychee.cli.rs/) |
| `lint:renovate-deps` | [Renovate](https://docs.renovatebot.com/) dependency tracking |
+## How it works
+
+Flint relies on two tools that each play a distinct role:
+
+### mise — the task runner
+
+[mise](https://mise.jdx.dev/) is a polyglot dev tool manager and task
+runner. In the context of flint, mise serves two purposes:
+
+1. **Installing tools.** mise's `[tools]` section pins exact versions
+ of the linters each task needs (e.g., `lychee`, `node`,
+ `"npm:renovate"`). Running `mise install` gives every developer and
+ CI runner the same versions, so local runs are consistent with CI.
+
+2. **Running tasks.** mise downloads task scripts from this repository
+ via HTTP, wires them into your project as local commands
+ (`mise run lint`, `mise run fix`), and passes flags and environment
+ variables through to each script. You don't need to clone flint —
+ mise fetches the scripts directly from GitHub URLs pinned in your
+ `mise.toml`.
+
+### Renovate — the dependency updater
+
+[Renovate](https://docs.renovatebot.com/) is an automated dependency update bot.
+Extending the flint [Renovate preset](#automatic-version-updates-with-renovate)
+(`default.json`) is essential for any repository that uses flint — without it,
+SHA-pinned flint URLs and `_VERSION` variables in `mise.toml` would never get
+updated. The preset ships custom managers that detect these patterns and open
+PRs to bump both flint itself and the tools it runs
+(e.g., Super-Linter, lychee).
+
+Optionally, the [`lint:renovate-deps`](#lintrenovate-deps) task adds a second
+layer: it runs Renovate locally to detect which dependencies Renovate is
+tracking, compares this against a committed snapshot, and fails if they
+diverge — catching cases where a dependency silently falls off Renovate's
+radar.
+
## Usage
-⚠️ **Important**: Always pin to a specific version, never use `main`. The main branch may contain breaking changes. See [CHANGELOG.md](CHANGELOG.md) for version history.
+⚠️ **Important**: Always pin to a specific version, never use `main`.
+The main branch may contain breaking changes.
+See [CHANGELOG.md](CHANGELOG.md) for version history.
-Add whichever tasks you need as HTTP remote tasks in your `mise.toml`, pinned to the commit SHA of a release tag with a version comment:
+Add whichever tasks you need as HTTP remote tasks in your `mise.toml`,
+pinned to the commit SHA of a release tag with a version comment:
+
+
```toml
# Pick the tasks you need from flint (https://github.com/grafana/flint)
@@ -38,7 +84,11 @@ description = "Verify renovate-tracked-deps.json is up to date"
file = "https://raw.githubusercontent.com/grafana/flint/30090d5540807f330a94420ad11b57ba93eaaa84/tasks/lint/renovate-deps.py" # v0.3.0
```
-The SHA pin ensures the URL is immutable (tag-based URLs can change if a tag is force-pushed), and the `# v0.3.0` comment tells Renovate which version is currently pinned.
+
+
+The SHA pin ensures the URL is immutable (tag-based URLs can change
+if a tag is force-pushed), and the `# v0.3.0` comment tells Renovate
+which version is currently pinned.
Then wire up top-level `lint` and `fix` tasks that reference whichever tasks
you adopted (add any project-specific subtasks to the `depends` list):
@@ -55,13 +105,30 @@ run = "AUTOFIX=true mise run lint"
## Example
-See [grafana/docker-otel-lgtm](https://github.com/grafana/docker-otel-lgtm) for a real-world example of a repository using flint. Its [CONTRIBUTING.md](https://github.com/grafana/docker-otel-lgtm/blob/main/CONTRIBUTING.md) describes the developer workflow, and its [mise.toml](https://github.com/grafana/docker-otel-lgtm/blob/main/mise.toml) shows how the tasks are wired up.
+See [grafana/docker-otel-lgtm][example-repo] for a real-world example
+of a repository using flint. Its [CONTRIBUTING.md][example-contributing]
+describes the developer workflow, and its [mise.toml][example-mise]
+shows how the tasks are wired up.
+
+[example-repo]: https://github.com/grafana/docker-otel-lgtm
+[example-contributing]: https://github.com/grafana/docker-otel-lgtm/blob/main/CONTRIBUTING.md
+[example-mise]: https://github.com/grafana/docker-otel-lgtm/blob/main/mise.toml
## Tasks
### `lint:super-linter`
-Runs [Super-Linter](https://github.com/super-linter/super-linter) via Docker or Podman. Auto-detects the container runtime (prefers Podman, falls back to Docker) and handles SELinux bind-mount flags on Fedora.
+Runs [Super-Linter](https://github.com/super-linter/super-linter)
+via Docker or Podman. Auto-detects the container runtime (prefers
+Podman, falls back to Docker) and handles SELinux bind-mount flags
+on Fedora.
+
+**mise** fetches this script from the SHA-pinned URL in `mise.toml`
+and runs it as `mise run lint:super-linter`. The
+`SUPER_LINTER_VERSION` environment variable (set in `mise.toml`)
+controls which Super-Linter image is pulled. **Renovate**, via the
+flint preset, opens PRs to bump both the flint script URL and the
+`SUPER_LINTER_VERSION` value when new versions are available.
**Flags:**
@@ -69,21 +136,38 @@ Runs [Super-Linter](https://github.com/super-linter/super-linter) via Docker or
| ----------- | ------------------------------------------------------------ |
| `--autofix` | Enable autofix mode (enables `FIX_*` vars from the env file) |
-When autofix is not enabled, all `FIX_*` lines are filtered out of the env file before running Super-Linter.
+When autofix is not enabled, all `FIX_*` lines are filtered out of
+the env file before running Super-Linter.
**Environment variables:**
+
+
| Variable | Default | Required | Description |
| ----------------------- | --------------------------------- | -------- | ------------------------------------------------- |
| `SUPER_LINTER_VERSION` | — | yes | Super-Linter image tag (e.g. `v8.4.0@sha256:...`) |
| `SUPER_LINTER_ENV_FILE` | `.github/config/super-linter.env` | no | Path to the Super-Linter env file |
+
+
### `lint:links`
-Checks links with [lychee](https://lychee.cli.rs/). By default it runs two checks: **all links (local + remote) in modified files** and **local file links in all files**. This keeps CI fast while catching both broken remote links in changed content and broken internal links across the whole repository.
+Checks links with [lychee](https://lychee.cli.rs/). By default, it
+runs two checks: **all links (local + remote) in modified files** and
+**local file links in all files**. This keeps CI fast while catching
+both broken remote links in changed content and broken internal links
+across the whole repository.
+
+**mise** fetches this script and runs it as `mise run lint:links`.
+Lychee is installed via mise's `[tools]` section — add
+`lychee = ""` to your `mise.toml`. **Renovate**, via the
+flint preset, opens PRs to bump the flint script URL when a new
+version is available.
**Flags:**
+
+
| Flag | Description |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `--full` | Check all links (local + remote) in all files (single run) |
@@ -92,25 +176,42 @@ Checks links with [lychee](https://lychee.cli.rs/). By default it runs two check
| `--lychee-args ` | Extra arguments to pass to lychee |
| `...` | Files to check (default: `.`; only used with `--full`) |
-When running in default mode, if a config change is detected (matching `LYCHEE_CONFIG_CHANGE_PATTERN`), the script falls back to `--full` behavior.
+
+
+When running in default mode, if a config change is detected
+(matching `LYCHEE_CONFIG_CHANGE_PATTERN`), the script falls back
+to `--full` behavior.
**Environment variables:**
+
+
| Variable | Default | Description |
| ------------------------------ | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
| `LYCHEE_CONFIG` | `.github/config/lychee.toml` | Path to the lychee config file |
| `LYCHEE_CONFIG_CHANGE_PATTERN` | `^(\.github/config/lychee\.toml\|\.mise/tasks/lint/.*\|mise\.toml)$` | Regular expression for files whose change triggers a full link check |
+
+
**Examples:**
```bash
-mise run lint:links # All links in modified files + local links in all files (default)
+mise run lint:links # All links in modified + local links in all files (default)
mise run lint:links --full # All links in all files
```
### `lint:renovate-deps`
-Verifies `.github/renovate-tracked-deps.json` is up to date by running Renovate locally and parsing its debug logs.
+Verifies `.github/renovate-tracked-deps.json` is up to date by
+running Renovate locally and parsing its debug logs.
+
+**mise** fetches this script and runs it as `mise run lint:renovate-deps`.
+The Renovate CLI is installed via mise's `[tools]` section — add
+`node = ""` and `"npm:renovate" = ""` to your
+`mise.toml`. **Renovate** plays a dual role here: the flint preset
+keeps the script URL up to date, while the script itself runs Renovate
+locally in `--platform=local` mode to discover which dependencies
+Renovate is tracking and compares them against a committed snapshot.
**Flags:**
@@ -120,35 +221,65 @@ Verifies `.github/renovate-tracked-deps.json` is up to date by running Renovate
**Environment variables:**
+
+
| Variable | Default | Description |
| ------------------------------- | ------- | ----------------------------------------------------------------------------------- |
| `RENOVATE_TRACKED_DEPS_EXCLUDE` | unset | Comma-separated Renovate managers to exclude (e.g. `github-actions,github-runners`) |
+
+
#### Why this exists
-Renovate silently stops tracking a dependency when it can no longer parse the version reference (typo in a comment annotation, unsupported syntax, moved file, etc.). When that happens, the dependency freezes in place with no PR and no dashboard entry — it simply disappears from Renovate's radar.
+Renovate silently stops tracking a dependency when it can no longer
+parse the version reference (typo in a comment annotation,
+unsupported syntax, moved file, etc.). When that happens, the
+dependency freezes in place with no PR and no dashboard entry — it
+simply disappears from Renovate's radar.
-The Dependency Dashboard catches _known_ dependencies that are pending or in error, but it cannot show you a dependency that Renovate no longer sees at all. This linter closes that gap by keeping a committed snapshot of every dependency Renovate tracks and failing CI when the two diverge.
+The Dependency Dashboard catches _known_ dependencies that are
+pending or in error, but it cannot show you a dependency that
+Renovate no longer sees at all. This linter closes that gap by
+keeping a committed snapshot of every dependency Renovate tracks
+and failing CI when the two diverge.
-#### How it works
+#### How the linter works
-The `lint:renovate-deps` task runs Renovate locally in `--platform=local` mode, parses its debug log for the `packageFiles with updates` message, and generates a dependency list (grouped by file and manager). It then diffs this against the committed `.github/renovate-tracked-deps.json`:
+The `lint:renovate-deps` task runs Renovate locally in
+`--platform=local` mode, parses its debug log for the
+`packageFiles with updates` message, and generates a dependency
+list (grouped by file and manager). It then diffs this against the
+committed `.github/renovate-tracked-deps.json`:
- If they match → linter passes
-- If they differ → linter fails with a unified diff showing which dependencies were added or removed
-- With `--autofix` flag (or `AUTOFIX=true` env var) → automatically regenerates and updates the committed file
+- If they differ → linter fails with a unified diff showing which
+ dependencies were added or removed
+- With `--autofix` flag (or `AUTOFIX=true` env var) → automatically
+ regenerates and updates the committed file
#### Typical workflow
-- **A dependency disappears** (e.g., someone removes a `# renovate:` comment or changes a file that Renovate was matching) → CI fails, showing the removed dependency in the diff. The author can then decide whether the removal was intentional or accidental.
+- **A dependency disappears** (e.g., someone removes a
+ `# renovate:` comment or changes a file that Renovate was
+ matching) → CI fails, showing the removed dependency in the diff.
+ The author can then decide whether the removal was intentional or
+ accidental.
-- **A new dependency is added** → CI fails because the committed snapshot is stale. Run `mise run fix` (or `AUTOFIX=true mise run lint:renovate-deps`) to regenerate and update the file, then commit.
+- **A new dependency is added** → CI fails because the committed
+ snapshot is stale. Run `mise run fix` (or
+ `AUTOFIX=true mise run lint:renovate-deps`) to regenerate and
+ update the file, then commit.
-- **Routine regeneration** → After any change to `renovate.json5`, Dockerfiles, `go.mod`, `package.json`, or other files Renovate scans, the linter will detect the change and require regeneration.
+- **Routine regeneration** → After any change to `renovate.json5`,
+ Dockerfiles, `go.mod`, `package.json`, or other files Renovate
+ scans, the linter will detect the change and require
+ regeneration.
## How AUTOFIX Works
-Lint scripts that support fixing accept an `--autofix` flag. Autofix can also be enabled via the `AUTOFIX=true` environment variable, which is how the `fix` meta-task propagates it through the dependency chain.
+Lint scripts that support fixing accept an `--autofix` flag. Autofix
+can also be enabled via the `AUTOFIX=true` environment variable, which
+is how the `fix` meta-task propagates it through the dependency chain.
**Check mode** (default):
@@ -167,14 +298,17 @@ mise run lint:super-linter --autofix # Apply code fixes
mise run lint:renovate-deps --autofix # Regenerate tracked deps
```
-Linters that don't support autofix (like lychee link checker) silently ignore the `AUTOFIX` environment variable.
+Linters that don't support autofix (like lychee link checker)
+silently ignore the `AUTOFIX` environment variable.
## Automatic version updates with Renovate
Flint provides a [Renovate shareable preset](https://docs.renovatebot.com/config-presets/)
with custom managers that automatically update:
-- **SHA-pinned flint versions** in `mise.toml` (`raw.githubusercontent.com` URLs with commit SHA and version comment)
+- **SHA-pinned flint versions** in `mise.toml`
+ (`raw.githubusercontent.com` URLs with commit SHA and version
+ comment)
- **`_VERSION` variables** in `mise.toml` (e.g., `SUPER_LINTER_VERSION`)
Add this to your `renovate.json5`:
@@ -187,20 +321,40 @@ Add this to your `renovate.json5`:
## Per-repo configuration
-Each task expects certain config files that your repository must provide. You only need the files for the tasks you adopt:
-
-- **`lint:super-linter`** — Super-Linter env file (`.github/config/super-linter.env`) to select which validators to enable and which `FIX_*` vars to set, plus any linter config files (`.golangci.yaml`, `.markdownlint.yaml`, `.yaml-lint.yml`, `.editorconfig`, etc.)
-- **`lint:links`** — Lychee config (`.github/config/lychee.toml`) for exclusions, timeouts, remappings
-- **`lint:renovate-deps`** — Renovate config (`.github/renovate.json5`) and committed snapshot (`.github/renovate-tracked-deps.json`)
+Each task expects certain config files that your repository must
+provide. You only need the files for the tasks you adopt:
+
+- **`lint:super-linter`** — Super-Linter env file
+ (`.github/config/super-linter.env`) to select which validators
+ to enable and which `FIX_*` vars to set, plus any linter config
+ files (`.golangci.yaml`, `.markdownlint.yaml`, `.yaml-lint.yml`,
+ `.editorconfig`, etc.)
+- **`lint:links`** — Lychee config
+ (`.github/config/lychee.toml`) for exclusions, timeouts,
+ remappings
+- **`lint:renovate-deps`** — Renovate config
+ (`.github/renovate.json5`) and committed snapshot
+ (`.github/renovate-tracked-deps.json`)
## Versioning
-This project uses [Semantic Versioning](https://semver.org/). Breaking changes will be documented in [CHANGELOG.md](CHANGELOG.md) and will result in a major version bump.
+This project uses [Semantic Versioning](https://semver.org/).
+Breaking changes will be documented in [CHANGELOG.md](CHANGELOG.md)
+and will result in a major version bump.
-**Always pin to a specific commit SHA** in your `mise.toml` file URLs with a version comment (e.g., `# v0.3.0`). Never reference `main` directly as it may contain unreleased breaking changes. To find the commit SHA for a release tag, run `git rev-parse v0.3.0`.
+**Always pin to a specific commit SHA** in your `mise.toml` file
+URLs with a version comment (e.g., `# v0.3.0`). Never reference
+`main` directly as it may contain unreleased breaking changes. To
+find the commit SHA for a release tag, run
+`git rev-parse v0.3.0`.
## Releasing
-Releases are automated via [Release Please](https://github.com/googleapis/release-please). When conventional commits land on `main`, Release Please opens (or updates) a release PR with a changelog.
+Releases are automated via
+[Release Please](https://github.com/googleapis/release-please).
+When conventional commits land on `main`, Release Please opens
+(or updates) a release PR with a changelog.
-> **Note:** CI checks don't trigger automatically on release-please PRs because they are created with `GITHUB_TOKEN`. To run CI, either click **Update branch** or **close and reopen** the PR.
+> **Note:** CI checks don't trigger automatically on release-please
+> PRs because they are created with `GITHUB_TOKEN`. To run CI,
+> either click **Update branch** or **close and reopen** the PR.
diff --git a/tasks/lint/links.sh b/tasks/lint/links.sh
index df46c57..a7790d5 100755
--- a/tasks/lint/links.sh
+++ b/tasks/lint/links.sh
@@ -96,7 +96,8 @@ is_config_modified() {
# Pattern for detecting config changes that should trigger a full lint.
# Consuming repos can override this via LYCHEE_CONFIG_CHANGE_PATTERN.
- local config_change_pattern="${LYCHEE_CONFIG_CHANGE_PATTERN:-^(\.github/config/lychee\.toml|\.mise/tasks/lint/.*|mise\.toml)$}"
+ local default_pattern='^(\.github/config/lychee\.toml|\.mise/tasks/lint/.*|mise\.toml)$'
+ local config_change_pattern="${LYCHEE_CONFIG_CHANGE_PATTERN:-$default_pattern}"
local config_modified
# shellcheck disable=SC2086 # intentional: head may expand to empty
diff --git a/tasks/lint/renovate-deps.py b/tasks/lint/renovate-deps.py
index 0dcc65f..eacc028 100755
--- a/tasks/lint/renovate-deps.py
+++ b/tasks/lint/renovate-deps.py
@@ -23,7 +23,8 @@
REPO_ROOT = Path(_repo_root_env)
COMMITTED = REPO_ROOT / ".github" / "renovate-tracked-deps.json"
-EXCLUDED_MANAGERS = {m.strip() for m in os.environ.get("RENOVATE_TRACKED_DEPS_EXCLUDE", "").split(",") if m.strip()} # pylint: disable=line-too-long # noqa: E501
+_exclude_env = os.environ.get("RENOVATE_TRACKED_DEPS_EXCLUDE", "")
+EXCLUDED_MANAGERS = {m.strip() for m in _exclude_env.split(",") if m.strip()}
def run_renovate(tmpdir):