Why this case study matters
bazelbuild/bazel is the canonical “build system that builds itself”
repository: 322 BUILD files, 100 *.bzl Starlark macro / rule files, a
482-line MODULE.bazel, plus Java + C++ + Python + shell + .bat source
trees, all driven by Bazel itself. There is no pom.xml, no
build.gradle, no Cargo.toml, no go.mod, no package.json at the
repo root despite the multi-language content. The actual build-and-test
CI runs OUT of GitHub on Buildkite via 309 lines of
.bazelci/presubmit.yml covering 25+ platform tasks.
Roughly 46% of that structural surface maps to declarative alint
rules. ~16% shells out via command: rules (mostly buildifier --lint
for the Starlark AST layer plus shellcheck). And ~38% is
deliberately out of alint’s scope — the entire Starlark-AST surface
where buildifier already does excellent work.
That out-of-scope fraction is the highest of any case study to date, and it is the launch-honesty anchor: bazel is the repo where the boundary between “structural shape” and “language semantics” is at its most visible.
Headline catch
.bazelversion was the canonical motivating example for v0.9.17’s
respect_gitignore: false per-rule knob. The file IS tracked in git,
but ALSO listed in bazel’s own .gitignore (line 34) — contributors
override it locally to use a different installed Bazel version, and the
gitignore stops those local edits from drifting back into commits.
Pre-v0.9.17, alint’s walker respected .gitignore for discovery, so a
file_exists rule against .bazelversion would silently miss the file
even though git ls-files confirmed it tracked. bazel was the first
repo in the launch-evidence corpus to expose the pattern.
v0.9.17 closed it: per-rule respect_gitignore: false overrides the
workspace default for one rule. Verified working against
/tmp/bazel/.bazelversion during the 2026-05-07 revalidation pass —
rule passes with the override, rule fails (“expected a file matching
[.bazelversion] at the repo root”) without it.
- id: bazel-version-pinned kind: file_exists paths: .bazelversion respect_gitignore: false # ← new in v0.9.17 root_only: true level: errorbazel is now the documented canonical example in the project’s pitfall catalogue. The fix is general (any repo that ships local-override patterns hits the same shape), but bazel was the demand source.
Where alint earns its keep here
alint owns the filesystem-shape layer that crosses every language in this tree:
- Filename/path conventions (
BUILDvsBUILD.bazel,.bzlfor Starlark,MODULE.bazelandMODULE.bazel.lockat root) - Presence/absence of structural files (no legacy WORKSPACE,
.bazelrc,.bazelci/presubmit.yml, root BUILD) - Apache 2.0 license headers across Java + C++ + shell + *.bzl — enforced nowhere statically today, only by code review etiquette
.gitattributesinvariants (* -text,*.bzl linguist-language=Python, lockfile merge driver wiring)- GitHub Actions hardening across all 9 in-repo workflows
The Starlark AST tier — cc_library deps integrity, glob([...]) vs
filesystem, target visibility, load() ordering, deprecated
rule-attribute usage — stays on buildifier and bazel query. alint
orchestrates buildifier via a command: rule so the Starlark AST
findings appear in the same alint check invocation, but doesn’t
pretend to parse Starlark.
The pattern is consistent: alint shells out to ruff / black / pyink for
Python AST work, gofmt / go vet for Go AST work, clang-format /
clang-tidy for C++, clippy for Rust, and buildifier / buildozer
for Starlark. The cross-language file-structure layer is alint’s; the
per-language AST/semantic layer stays with the existing per-language
tools.
A new contributor staring at bazel’s structural-validation surface today reads:
- 309 lines of
.bazelci/presubmit.yml - 117 lines of
.bazelrc - 100 lines of
CODEOWNERS - 482 lines of
MODULE.bazel - 9 GitHub Actions workflows
- the implicit Apache 2.0 header convention enforced socially
- the implicit
MODULE.bazel↔MODULE.bazel.lockfreshness convention - the implicit
*.bzllicense header convention
That is ~1,000+ lines of structural-validation surface, half of which is enforced only by code-review etiquette. The 81-rule alint config in this case study is one file, declarative, with each rule’s scope, severity, and rationale visible in 5-10 lines.
Future story angles
- The “structural floor under buildifier” framing as a recurring template for any repo where a domain-specific DSL has a dominant AST tool (Bazel/Starlark, Terraform/HCL, OPA/Rego, Nix, Lean).
- Pitfall #18 retrospective —
.bazelversionis the kind of detail that only surfaces when a real-world repo hits the corner. bazel was the demand source; v0.9.17 was the fix; every future repo that ships local-override files now has the per-rule knob.