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:

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:

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.