Lint Gate
One aggregate lint command runs every linter that is installed, skips the rest with a clear message, and fails only on real findings.
- Kind
- Contract
- Domain
- Environment & Tooling
- Applies at
scripts/lint.sh- Status
- Stable
- Source
scripts/lint.sh
01 Defines
The repository’s single lint entry point: make lint runs the configured LINT_COMMAND, here scripts/lint.sh, which runs each linter against the tracked files it applies to and reports one overall result.
02 Applies when
- Several linters cover different file types and nobody should need to know them all.
- The same command runs in a bare checkout, in the dev container, and in CI.
- A tool is legitimately absent in some environments and its absence should not look like a failure.
03 Boundaries
- The gate checks; rewriting files is the separate, deliberate
make format. - It does not install linters; the environment provides them and
make doctorreports what is missing. - Terraform checks are delegated to the make targets that know which roots exist.
Expected behaviour
- 01
make lintdelegates toLINT_COMMANDfromrepository.mk, or printsskip: no lint command configured. - 02A linter that is not installed prints
skip: <tool> is not installedand the run continues. - 03Every installed linter runs even after an earlier one fails; the result is reported once at the end.
- 04Linters read their configuration from
.linter/by explicit path. - 05File lists come from Git, so ignored and generated files are not linted.
- 06The run ends with
Lint PASSEDorLint FAILEDand the matching exit status.
Behaviour#
The gate is written so that it works anywhere. A small run helper checks
whether the linter’s command exists; if not, it prints a skip: line and moves
on. If it does, the linter runs and any failure is recorded, not raised, so the
remaining linters still report.
- 01
Locate
cdto the Git top level - 02
Collect files
Tracked and untracked-but-not-ignored
*.sh,*.md,*Dockerfile* - 03 Run linters ShellCheck, MarkdownLint, Hadolint, shfmt, Actionlint, EditorConfig, Codespell Explicit Skips
- 04
Prettier
npm run format:checkwhennode_modules/.bin/prettierexists - 05
Terraform
make check.terraform.fmt check.terraform.lint - 06
Report
Lint PASSED, orLint FAILEDand exit 1
A linter is skipped only when it is missing. When there are no files of a type — no Dockerfiles, say — that linter is not invoked at all.
Skipping versus failing#
Lint gate
- Missing tool:
skip:and carry on - Exit status reflects lint findings only
- Answers “is this code clean?”
Environment doctor
- Missing tool:
missing:and a non-zero exit - Exit status reflects the environment only
- Answers “is this environment complete?”
The two are complementary. The gate stays usable in a bare checkout, and the Environment Doctor is where a missing linter shows up as a problem.
Examples#
run() {
local name="$1"
shift
if ! command -v "$1" > /dev/null 2>&1; then
printf 'skip: %s is not installed\n' "$1"
return 0
fi
printf '\n--- %s ---\n' "$name"
"$@" || failed=1
}
[ "${#markdown_files[@]}" -eq 0 ] \
|| run MarkdownLint markdownlint --config .linter/.markdownlint.json "${markdown_files[@]}"
The wiring through the command surface is one line of repository configuration:
LINT_COMMAND := bash "$(REPO_ROOT)/scripts/lint.sh"
Connections