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 105 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,
no_trailing_whitespace,
line_endings | Format invariants hold across the tree: POSIX final newlines, no stray trailing whitespace, consistent line endings. |
| 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.
Checks teams usually script by hand
Most repos already enforce a few of these with a shell loop in CI or a reviewer's memory. Each one is a single rule:
-
Required directories exist (
docs/,.github/workflows/):dir_exists. -
Nothing over a size cap gets committed, such as a stray dataset
or build artifact:
file_max_size. -
Source files stay under a line budget:
file_max_lines. -
Scripts start with the interpreter line you expect:
file_shebang. -
No two paths differ only by case (
README.mdnext toreadme.md), which breaks checkouts on macOS and Windows:no_case_conflicts. -
No path Windows can't check out, like
con.txtor a name ending in a dot:no_illegal_windows_names. -
Trees stay shallow and folders stay browsable:
max_directory_depthandmax_files_per_directory. -
A committed generated file still matches what its generator
produces today:
generated_file_fresh.
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.
version: 1
extends:
- alint://bundled/rust@v1
- alint://bundled/oss-baseline@v1
rules:
- id: rust-snake-case
kind: filename_case
paths: "src/**/*.rs"
case: snake
level: error
- id: no-files-over-5mb
kind: file_max_size
paths: "**"
max_bytes: 5242880
level: error
Adopting it on an existing repo
alint init scaffolds a
starter .alint.yml for the ecosystem it detects, and
alint suggest scans the
tree for antipatterns and proposes rules that would catch them. On a
large, older codebase,
alint baseline
snapshots today's violations so CI fails only on new ones, and
alint fix applies the
automatic fixes rules declare (12 fix operations, from
appending a final newline to renaming a file into the right case).
In CI it runs as a GitHub
Action or a pre-commit
hook, and --format sarif feeds GitHub code scanning
(see output formats).
Performance
alint is a single static Rust binary with no runtime dependencies. Recent benchmarks:
- NixOS nixpkgs, 39,101 files, 79-rule ruleset: 273 ms wall time.
- Synthetic 100K-file workspace bundle: ~1.7 s; lighter scans (filename hygiene, cross-file relations) finish in under a second.
- Synthetic 1M-file workspace: ~18 s.
Per-release benchmark history lives in
docs/benchmarks/HISTORY.md.
Where it fits
Structure rules pay off more as a tree grows. For workspaces with dozens of packages, see alint as a monorepo linter. Rules match paths and file contents instead of parsing a language, so one config covers Rust, TypeScript, Python and Go side by side: language-agnostic linting. Coming from a naming-only tool, read migrating from ls-lint; from Repolinter, see the Repolinter alternative. AI coding agents get the same rules back as structured findings: agent-friendly output.
See alint applied to real codebases in the case-study gallery, or read the vs other repo-level linters comparison.