alint-core 0.4.3

Core types and execution engine for the alint language-agnostic repository linter.
Documentation

alint

Crates.io CI License

alint is a language-agnostic linter for repository structure. You declare the shape your repo should have — required files, filename conventions, content patterns, cross-file relationships — in a single .alint.yml, and alint enforces it. It walks the tree honoring .gitignore, runs rules in parallel, reports violations in human / JSON / SARIF / GitHub-annotation form, and can auto-fix what it flags. One static Rust binary, any language, any repo.

v0.3 ships ~42 rule kinds across ten families and 12 auto-fix ops — see docs/rules.md for the full catalogue. alint fills the active-maintenance gap left when Repolinter was archived in early 2026, with a superset of its rule catalogue plus first-class cross-file and conditional-rule primitives.

Core capabilities

  • ~42 rule kinds across ten families (full reference: docs/rules.md):
    • Existencefile_exists, file_absent, dir_exists, dir_absent.
    • Contentfile_content_matches, file_content_forbidden, file_header, file_starts_with, file_ends_with, file_hash, file_max_size, file_is_text, file_is_ascii.
    • Namingfilename_case, filename_regex.
    • Text hygieneno_trailing_whitespace, final_newline, line_endings, line_max_width, indent_style, max_consecutive_blank_lines.
    • Security / Unicodeno_merge_conflict_markers, no_bidi_controls, no_zero_width_chars.
    • Encodingno_bom.
    • Structuremax_directory_depth, max_files_per_directory, no_empty_files.
    • Portable metadatano_case_conflicts, no_illegal_windows_names.
    • Unix metadatano_symlinks, executable_bit, executable_has_shebang, shebang_has_executable.
    • Git hygieneno_submodules.
    • Cross-filepair, for_each_dir, for_each_file, dir_contains, dir_only_contains, unique_by, every_matching_has.
  • Auto-fix — 12 file ops covering content edits (trim whitespace, append newline, normalize line endings, strip BOM / bidi / zero-width, collapse blank lines) and path-level changes (create / remove / rename / prepend / append). Preview with alint fix --dry-run. Content-editing ops honour a configurable fix_size_limit (default 1 MiB) that skips oversize files rather than rewriting them.
  • Conditional rules — a bounded when: expression language (boolean logic, comparisons, matches regex, in list membership) gates rules on facts evaluated once per run: any_file_exists, all_files_exist, count_files.
  • Four output formatshuman, json (stable schema), sarif (GitHub Code Scanning), github (inline PR annotations).
  • JSON Schema at schemas/v1/config.json for editor autocomplete.
  • Official GitHub Actionasamarts/alint@v0.4.3.

Typical use cases

  • "Every package in a monorepo has a README.md and a LICENSE*" — dir_contains across packages/*.
  • "All Rust source files carry a copyright header; auto-prepend any that don't" — file_header + file_prepend.
  • "No stray *.bak or *.swp files in committed history; delete any that slip in" — file_absent + file_remove.
  • "Filename case convention enforced per language" — filename_case with when: facts.is_typescript gating.
  • "Every module directory has a mod.rs" — for_each_dir with nested file_exists.
  • "No two files share a basename across the tree" — unique_by with key: "{basename}".

The full DSL is documented in docs/design/ARCHITECTURE.md.

Non-goals

alint is deliberately not:

Scope is the filesystem shape and contents of a repository, not the semantics of the code inside it.

Install

From a tagged release (recommended)

curl -sSL https://raw.githubusercontent.com/asamarts/alint/main/install.sh | bash

Detects platform (Linux / macOS, x86_64 / aarch64), downloads the matching tarball, verifies the SHA-256, and installs to $INSTALL_DIR (default ~/.local/bin). Windows users download the Windows tarball from the Releases page.

From crates.io

cargo install alint

From source

git clone https://github.com/asamarts/alint
cd alint
cargo build --release -p alint
./target/release/alint --help

Quick start

Create a .alint.yml at the root of your repository:

# yaml-language-server: $schema=https://raw.githubusercontent.com/asamarts/alint/main/schemas/v1/config.json
version: 1

facts:
  - id: is_rust
    any_file_exists: [Cargo.toml]

rules:
  - id: readme-exists
    kind: file_exists
    paths: ["README.md", "README"]
    root_only: true
    level: error
    fix:
      file_create:
        content: "# Project\n"

  - id: no-backup-files
    kind: file_absent
    paths: "**/*.{bak,swp}"
    level: warning
    fix:
      file_remove: {}

  - id: components-pascal
    kind: filename_case
    paths: "components/**/*.{tsx,jsx}"
    case: pascal
    level: error
    fix:
      file_rename: {}

  - id: rust-snake
    when: facts.is_rust
    kind: filename_case
    paths: "src/**/*.rs"
    case: snake
    level: error
    fix:
      file_rename: {}

  - id: java-license-header
    kind: file_header
    paths: "**/*.java"
    lines: 20
    pattern: "(?s)Copyright \\(c\\) \\d{4}"
    level: error
    fix:
      file_prepend:
        content: |
          // Copyright (c) 2026 Acme Corp

Bundled rulesets (one-line baseline)

For common cases you don't want to hand-roll, alint ships a small catalogue of rulesets built into the binary. Use them via extends: without a network round-trip:

version: 1
extends:
  - alint://bundled/oss-baseline@v1                    # community docs + content hygiene
  - alint://bundled/rust@v1                            # Rust project (no-op if Cargo.toml absent)
  - alint://bundled/node@v1                            # Node project (no-op if package.json absent)
  - alint://bundled/monorepo@v1                        # packages/* / crates/* layout checks
  - alint://bundled/hygiene/no-tracked-artifacts@v1    # node_modules, .DS_Store, .env, big blobs
  - alint://bundled/hygiene/lockfiles@v1               # lockfiles at workspace root only
  - alint://bundled/tooling/editorconfig@v1            # .editorconfig + .gitattributes hygiene
  - alint://bundled/docs/adr@v1                        # MADR-style Architecture Decision Records

Currently shipped (see docs/rules.md for each ruleset's full rule list):

Ecosystem + project-shape baselines

  • oss-baseline@v1 — README / LICENSE / SECURITY.md / CODE_OF_CONDUCT.md / .gitignore existence; merge-marker + bidi-control bans; trailing-whitespace and final-newline hygiene (auto-fixable).
  • rust@v1 — Cargo.toml / Cargo.lock / rust-toolchain.toml existence; no committed target/; snake_case source filenames; Trojan-Source defenses. Gated with when: facts.is_rust.
  • node@v1 — package.json + lockfile; no committed node_modules/, dist/, .next/, etc.; Node-version pin via .nvmrc or engines; JS/TS source hygiene. Gated with when: facts.is_node.
  • monorepo@v1 — every packages/*, crates/*, apps/*, services/* directory has a README + ecosystem manifest; unique basenames.

Namespaced utilities

  • hygiene/no-tracked-artifacts@v1 — build outputs (node_modules, target, dist, __pycache__, …), OS junk (.DS_Store, Thumbs.db), editor backups (*~, *.swp), secret-shaped files (.env and locals), and files over 10 MiB. Several rules auto-fixable via file_remove.
  • hygiene/lockfiles@v1 — enforce lockfiles (yarn.lock, pnpm-lock.yaml, package-lock.json, bun.lock, Cargo.lock, poetry.lock, uv.lock) live only at the workspace root.
  • tooling/editorconfig@v1 — root .editorconfig + .gitattributes with line-ending normalization.
  • docs/adr@v1 — MADR-style Architecture Decision Records under docs/adr/: NNNN-kebab-title.md filename + required ## Status / ## Context / ## Decision sections.

All rulesets ship with non-blocking defaults (info / warning for recommendations, error only for unambiguous bugs). You can upgrade severity, disable individual rules with level: off, or override scope by redeclaring the rule id in your own .alint.yml. More rulesets (python, java, go, ci/github-actions, compliance/reuse) are planned for v0.5.

Then run:

alint check           # run all rules against the current directory
alint fix --dry-run   # preview the auto-fixes that would be applied
alint fix             # apply every fixable violation in place
alint list            # list effective rules
alint explain <id>    # show a rule's definition

Output formats:

alint check --format human    # default; colorized for humans
alint check --format json     # stable, versioned JSON schema
alint check --format sarif    # SARIF 2.1.0 (for GitHub Code Scanning)
alint check --format github   # GitHub Actions workflow commands

Exit codes: 0 no errors; 1 one or more errors; 2 config error; 3 internal error. Warnings do not fail by default — use --fail-on-warning to flip that.

Use in CI

GitHub Actions

Inline PR annotations (default):

- uses: asamarts/alint@v0.4.3

All inputs (all optional):

- uses: asamarts/alint@v0.4.3
  with:
    version: v0.4.3        # alint release tag (default: latest)
    path: .                # directory to lint (default: .)
    format: github         # human | json | sarif | github (default)
    config: |              # extra config path(s), one per line
      .alint.yml
    fail-on-warning: false
    args: ""               # extra CLI args appended verbatim

Upload findings to GitHub Code Scanning:

- uses: asamarts/alint@v0.4.3
  id: alint
  with:
    format: sarif
  continue-on-error: true
- uses: github/codeql-action/upload-sarif@v3
  if: always()
  with:
    sarif_file: ${{ steps.alint.outputs.sarif-file }}

pre-commit

Add to your .pre-commit-config.yaml:

repos:
  - repo: https://github.com/asamarts/alint
    rev: v0.4.3
    hooks:
      - id: alint

The hook runs alint check against the repo's .alint.yml. For auto-fix, add id: alint-fix — it's registered under stages: [manual] so it only runs when invoked explicitly (pre-commit run alint-fix), since fixers mutate the tree.

Docs

Development

git clone https://github.com/asamarts/alint
cd alint
cargo test --workspace        # 400+ tests; includes end-to-end scenarios
cargo run -- check            # dogfood: alint lints itself
cargo bench -p alint-bench    # criterion micro-benches

End-to-end tests live in crates/alint-e2e/scenarios/ as declarative YAML; adding a new scenario only requires a new file. CLI snapshot tests live in crates/alint/tests/cli/ under trycmd. Property-based invariants are in crates/alint-e2e/tests/invariants.rs.

CI is self-hosted with per-job bash scripts under ci/scripts/ that run locally or in GitHub Actions unchanged. See ci/env.example for runner setup.

License

Dual-licensed under either of:

at your option. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in alint shall be dual-licensed as above, without any additional terms or conditions.