Monorepo linter
Linting a monorepo is a structural-validation problem, and most per-language linters punt on it. ESLint, Clippy, ruff, and golangci-lint all check the code inside a package; none of them check that your packages agree with each other. alint sits at the layer that sees the whole tree.
What alint catches that per-package linters miss
- Cross-file rules.
pair,for_each_dir,dir_contains,unique_by,every_matching_hasexpress invariants that span packages. Every workspace member has aREADME.md; no twopackage.jsonfiles claim the same npm name; every crate markedpublish = falseis documented as such. - Cross-language conventions. In a polyglot tree
(C++, Java, Python, Rust, Go, JS), no per-language linter sees
the LICENSE-header shape, SPDX identifiers,
.editorconfigcompliance, or whetherCODEOWNERSmatches the actual directory layout. - Workspace-shape invariants. Every TS path-alias
in
tsconfig.jsonresolves to a realpackages/<name>/directory; the[workspace] memberslist inCargo.tomlmatches what's on disk; lockfiles are committed where you said they would be.
alint is not a replacement for Bazel, Nx, or Turborepo. Those orchestrate builds. alint enforces conventions. They coexist cleanly.
Real monorepos using alint
| Repo | Shape | What alint adds |
|---|---|---|
| vercel/next.js | Hybrid pnpm + Cargo dual-workspace | Surfaces 3 of 19 npm packages without license fields and 4 of 63 crates with non-canonical licenses, drift no per-language linter catches because each only sees half the tree. |
| apache/arrow | 6 languages in one tree | 21 lint hooks across 14 tool repos and 0 of them see cross-language structural shape. alint fills that gap. |
| pnpm/pnpm | 169-package pnpm-workspace | Replaces 11 of 13 cross-package field invariants currently enforced by an in-tree 470-line meta-updater plugin, with no plugin install required. |
| pytorch/pytorch | ~80k-file ML mega-monorepo | Sits beneath lintrunner as the structural floor; ~86% of lintrunner's 57 adapters are structural and map cleanly onto alint rules. |
| nixos/nixpkgs | 39,101 files, 20,678 package dirs | Full 79-rule structural pass in 273 ms wall-clock, the empirical anchor for "fast at any size". |
| istio/istio | Go modules + 9 Helm charts | Helm-chart structural discipline that helm lint and golangci-lint don't reach: cross-chart field equality, release-note YAML shape, Dockerfile co-location. |
Browse all 30 case studies → · See how alint compares to other repo-level linters →
Get started
The bundled monorepo@v1 ruleset covers the
language-agnostic shape (every packages/* /
crates/* / apps/* entry has a
README.md; manifests where applicable). Layer
ecosystem-specific overlays on top:
# .alint.yml
extends:
- alint://bundled/monorepo@v1
- alint://bundled/monorepo/cargo-workspace@v1 # if Cargo
- alint://bundled/monorepo/pnpm-workspace@v1 # if pnpm
- alint://bundled/monorepo/yarn-workspace@v1 # if yarn
# Pick up per-package .alint.yml files alongside the root.
nested_configs: true
nested_configs: true lets each package layer its own
assertions on top of the root config without bloating it. This is
the shovel-ready pattern for trees the size of nixpkgs.
One static binary, no Node/JVM/Docker runtime in your CI pipeline.