Repository structure linter
alint enforces the shape of your repository: which files must exist, what they're named, what's inside them at the structural level, and how they relate to one another. Most linters check the code inside files; alint checks the files themselves.
What "structure" means in practice
Four rule families cover the bulk of structural validation. The full catalogue lists 60 kinds; these are the load-bearing ones.
| Family | Rule kinds | What it answers |
|---|---|---|
| Existence | file_exists, dir_exists |
Required files and directories are present:
LICENSE, README.md,
CODEOWNERS, language manifests,
per-package scaffolding.
|
| Naming | filename_case, filename_regex | Filenames follow conventions: PascalCase React components, kebab-case docs, snake_case Rust modules, test files alongside their subject. |
| Text hygiene | final_newline,
trailing_whitespace,
line_endings | Format invariants hold across the tree: POSIX final newlines, no stray trailing whitespace, consistent line endings, no Unicode bidi control characters. |
| Structured query | json_path_*,
yaml_path_*,
toml_path_* |
Values inside config files are correct: every
package.json has a license;
every Cargo.toml sets
edition = "2021"; every GitHub
workflow pins actions/checkout@v4.
|
Cross-file relations (pair,
for_each_dir, unique_by,
every_matching_has) layer on top: every
*.proto has a generated counterpart, every TS
path-alias resolves, no two package.jsons
share a name.
How it's expressed
One .alint.yml, declarative, with a
JSON
Schema for editor autocomplete. extends:
pulls in bundled per-ecosystem rulesets; the
rules: block adds repo-specific checks.
extends:
- alint://bundled/rust@v1
- alint://bundled/oss-baseline@v1
rules:
- kind: filename_case
paths: ["src/**/*.rs"]
case: snake
- kind: file_exists
path: "CODEOWNERS"
Performance
alint is a single static Rust binary with no runtime dependencies. Recent benchmarks:
- NixOS nixpkgs, 39k files, 79-rule ruleset: 273 ms wall time.
- Synthetic 100K-file workspace: sub-second.
- Synthetic 1M-file mega-monorepo: ~12 seconds.
Per-release benchmark history lives in
docs/benchmarks/HISTORY.md.
See alint applied to real codebases in the case-study gallery, or read the vs other repo-level linters comparison.