A fast, general linter

ESLint, Clippy and Ruff check the code inside a file. alint checks the repository around it: which files must or mustn't exist, what they're named, what they contain, the values in files like package.json or a workflow, and how files relate to each other.

Rules such as "every package has a README" or "workflows pin actions to a commit" go in one .alint.yml. alint checks them on every pull request, in pre-commit and in your editor. It takes over from Repolinter and from scripts that test files and config values.

Guides for monorepos, coding agents, check scripts and moving off Repolinter or ls-lint.

  • acme/the 6 findings of alint check, marked on this Rust workspace's files
  • .alint.yml✓
  • .env

    error hygiene-no-env-files

    Environment files containing real values must not be committed. Use .env.example (or similar) for shared non-secret defaults.

  • .github/
  • .github/CODEOWNERS✓
  • .github/dependabot.yml✓
  • .github/workflows/
  • .github/workflows/ci.yml

    warning gha-workflow-contents-read

    Workflow grants (or defaults to) write access to the GITHUB_TOKEN. Restrict it: declare permissions: contents: read (or read-all, or {}) at the workflow level, or set per-job permissions:, so a compromised step can't push to the repo.

    warning gha-pin-actions-to-sha

    External action, workflow, or container is not pinned to an immutable commit or digest. Pin Actions with @<full-commit-sha> and images with @sha256:<64-hex-digest> so a compromised tag cannot silently change what runs. Local ./ references are allowed.

  • LICENSE✓
  • README.md✓
  • crates/
  • crates/cli/
  • crates/cli/Cargo.toml✓
  • crates/cli/src/
  • crates/cli/src/ParseArgs.rs

    error rust-sources-snake-case fixable

    Rust module filenames must be snake_case.

  • crates/cli/src/main.rs✓
  • crates/core/
  • crates/core/Cargo.toml

    error crates-use-edition-2024

    value at path does not equal expected: expected "2024", got "2021"

  • crates/core/src/
  • crates/core/src/lib.rs

    warning rust-sources-final-newline fixable

    file does not end with a newline

  • 6 more files: .gitignore, CODE_OF_CONDUCT.md, Cargo.lock, Cargo.toml, SECURITY.md, rust-toolchain.toml✓
Summary (6 violations):
  ✗ 3 errors   ⚠︎ 3 warnings
  34 passing · 6 failing · 2 auto-fixable

  → run `alint fix` to resolve 2 fixable violations.

One file configures it

The .alint.yml at the root of that workspace, and what alint check and alint fix printed for it.

.alint.yml
version: 1
extends:
  - alint://bundled/oss-baseline@v1
  - alint://bundled/rust@v1
  - alint://bundled/ci/github-actions@v1
  - alint://bundled/hygiene/no-tracked-artifacts@v1

rules:
  - id: crates-use-edition-2024
    kind: toml_path_equals
    paths: "crates/*/Cargo.toml"
    path: "$.package.edition"
    equals: "2024"
    level: error
.alint.yml
version: 1
extends:
  - alint://bundled/oss-baseline@v1
  - alint://bundled/rust@v1
  - alint://bundled/ci/github-actions@v1
  - alint://bundled/hygiene/no-tracked-artifacts@v1

rules:
  - id: crates-use-edition-2024
    kind: toml_path_equals
    paths: "crates/*/Cargo.toml"
    path: "$.package.edition"
    equals: "2024"
    level: error
  1. Lines 2 to 6

    Start from rulesets. Each extends: line adds a ruleset that ships with alint, 39 rules in all here: the open-source basics, Rust, GitHub Actions and files that shouldn't be tracked. The Rust rules only run where there's Rust.

  2. Lines 9 to 14

    Add rules of your own. This one reads edition from every crate's Cargo.toml; crates/core is still on 2021. It's a toml_path_equals rule, one of 102 kinds.

alint check
.env  ✗  error    hygiene-no-env-files              Environment files containing real values must not be              committed. Use `.env.example` (or similar) for shared              non-secret defaults.  2 more findings crates/cli/src/ParseArgs.rs  ✗  error    rust-sources-snake-case   fixable              Rust module filenames must be snake_case.  2 more findings Summary (6 violations):  ✗ 3 errors   ⚠︎ 3 warnings  34 passing · 6 failing · 2 auto-fixable   → run `alint fix` to resolve 2 fixable violations.
.env  ✗  error    hygiene-no-env-files              Environment files              containing real values              must not be committed. Use              `.env.example` (or              similar) for shared              non-secret defaults.  2 more findings crates/cli/src/ParseArgs.rs  ✗  error    rust-sources-snake-case   fixable              Rust module filenames must              be snake_case.  2 more findings Summary (6 violations):  ✗ 3 errors   ⚠︎ 3 warnings  34 passing · 6 failing · 2 auto-fixable   → run `alint fix` to resolve 2 fixable violations.

alint groups what it finds by file, names the rule behind each finding and ends with a summary. The exit code fails CI on errors.

alint fix
error [rust-sources-snake-case]:  ✓ renamed crates/cli/src/ParseArgs.rs -> crates/cli/src/parse_args.rs warning [rust-sources-final-newline]:  ✓ appended final newline to crates/core/src/lib.rs  4 more findings
error [rust-sources-snake-case]:  ✓ renamed crates/cli/src/ParseArgs.rs    -> crates/cli/src/parse_args.rs warning [rust-sources-final-newline]:  ✓ appended final newline to    crates/core/src/lib.rs  4 more findings

alint fix renamed the module and added the missing newline. There are 26 kinds of fix, from renames to license headers; the other 4 findings here need a person.

Where these rules live now

No code linter checks rules like these. Today they live in one of three places, and each one has a gap:

hack/verify-readmes.sha check script
#!/usr/bin/env bash
set -euo pipefail
fail=0
for d in packages/*/; do
  if [ ! -f "${d}README.md" ]; then
    echo "missing: ${d}README.md" >&2
    fail=1
  fi
done
exit $fail
bash hack/verify-readmes.sh
missing: packages/emails/README.mdmissing: packages/web/README.md
.alint.ymlthe same check as a rule
- id: package-readme
  kind: for_each_dir
  select: "packages/*"
  require:
    - kind: file_exists
      paths: "{path}/README.md"
      message: "{path} has no README.md"
  level: error
alint check
packages/emails  ✗  error    package-readme              packages/emails has no README.md packages/web  ✗  error    package-readme              packages/web has no README.md
packages/emails  ✗  error    package-readme              packages/emails has no              README.md packages/web  ✗  error    package-readme              packages/web has no              README.md

The rule finds the same two packages. It also runs in the same walk as every other rule, reports under its own name in any of alint's 8 output formats, shows up in your editor and keeps checking when someone adds a package.

What you can check

Rules aboutFor exampleRule kinds
Files that must or mustn't existA README, license and security policy; no .env, .DS_Store or build output.file_exists file_absent
Namessnake_case modules, kebab-case docs, no spaces, no two files that differ only in case.filename_case no_case_conflicts
ContentsLicense headers, banned words, merge-conflict markers, hidden Unicode and final newlines.file_header no_bidi_controls
Values in config filesA field in package.json, Cargo.toml, pyproject.toml, a workflow, .env or Terraform equals a value, matches a pattern or is absent.json_path_equals hcl_path_matches
Relations between filesEvery package has a README and every migration a down file; generated files are fresh; imports have no cycles.for_each_dir pair file_graph
Commits and pull requestsConventional commit subjects and files that have to change together.git_commit_message pair_changed_together

All 102 rule kinds, and 24 rulesets to start from.

Turn it on without fixing everything first

alint baseline
Records the findings you have today. Point the config's baseline: field at the file, and CI fails only on new findings while the old ones get fixed as you go.
alint check --changed
Checks only files with changes you haven't staged yet, new or edited, which keeps a local run short. In a pull request, --base origin/main checks every file the pull request touches.
alint init
Writes a starter .alint.yml for the languages and tools it finds.
alint suggest
Looks for problems already in your tree and proposes the rules that would catch them.

How the baseline works, and what --changed still checks across the whole tree.

Runs where you work

The same rules give the same findings in CI, pre-commit, your editor and your coding agent. This is the misnamed module from the workspace, in two of alint's 8 output formats.

.github/workflows/ci.ymlthe alint step
- uses: asamarts/alint@10d1c0a2bafd0bd316dbad9fe63ea5e1e3761c71 # v0.20.0
alint check --format github
::error title=rust-sources-snake-case,file=crates/cli/src/ParseArgs.rs::Rust module filenames must be snake_case.

In a pull request. The GitHub Action turns each finding into an annotation on the pull request, and can write a SARIF file for code scanning. The step is pinned to the release's commit, like any other action.

In your editor and hooks. Extensions for VS Code and JetBrains IDEs, and an LSP server for Neovim, Helix, Emacs and others, show the same findings as you type. The pre-commit hook checks each commit, and --compact prints one line per finding for grep.

alint check --format agentone finding, abridged
{  "rule_id": "rust-sources-snake-case",  "severity": "error",  "file": "crates/cli/src/ParseArgs.rs",  "agent_instruction": "error: Rust module filenames must be snake_case. To resolve: edit crates/cli/src/ParseArgs.rs (or run `alint fix --only rust-sources-snake-case` to apply the auto-fix).",  "fix_command": [    "fix",    "--only",    "rust-sources-snake-case"  ]}

For a coding agent. Each finding comes with an instruction and, where one exists, the command that fixes it. Above is one finding from the report, cut to the fields an agent acts on. How agents use it.

On real repositories

The reference configs cover 30 open-source repositories, written to test alint on real trees; the projects don't use alint. Three things the configs found:

  1. flutter/flutter oss-no-bidi-controls

    U+202C, an invisible bidi control, hides in 5 Markdown files, probably pasted in with contributor names.

  2. facebook/react react-package-repository-directory-matches

    react-refresh sets repository.directory to packages/react, copied from a sibling package, so npm links to the wrong folder.

  3. NixOS/nixpkgs hygiene-no-ruby-bundler-cache

    Two packages commit Ruby's bundler cache, .bundle/, which none of nixpkgs' CI checks looks for.

All 30 reference configs, each with what it checks, what stays outside alint and, where a run found something, what it found.

Try it on the repository you have open

  1. Install it. Other ways to install.

    curl -fsSL https://alint.org/install.sh | bash
  2. Add a .alint.yml at the root. These three lines turn on 15 rules: a README that isn't a stub, a license, a security policy, code owners, a code of conduct, a .gitignore and Dependabot or Renovate; and in Markdown, text, TOML and YAML files, no merge-conflict markers, bidi controls or trailing whitespace, and a final newline.

    .alint.yml
    version: 1
    extends:
      - alint://bundled/oss-baseline@v1
  3. Run alint check. On a small Python project with a README and no license, it prints this:

    alint check
    Repository-level  ⚠︎  warning  oss-license-exists              An open-source repo should declare a              license at the root.  ℹ︎  info     oss-security-policy-exists              Consider adding a SECURITY.md so              vulnerability reporters know where to              disclose.  ℹ︎  info     oss-dependency-update-tool              Consider configuring Dependabot or              Renovate to keep dependencies and actions              up to date.  ℹ︎  info     oss-codeowners-exists              Consider adding a CODEOWNERS file so PR              reviews are auto-routed.  ℹ︎  info     oss-code-of-conduct-exists              Consider adding a CODE_OF_CONDUCT.md. Summary (5 violations):  ⚠︎ 1 warning   ℹ︎ 4 info  10 passing · 5 failing
    Repository-level  ⚠︎  warning  oss-license-exists              An open-source repo should              declare a license at the              root.  ℹ︎  info     oss-security-policy-exists              Consider adding a              SECURITY.md so              vulnerability reporters              know where to disclose.  ℹ︎  info     oss-dependency-update-tool              Consider configuring              Dependabot or Renovate to              keep dependencies and              actions up to date.  ℹ︎  info     oss-codeowners-exists              Consider adding a              CODEOWNERS file so PR              reviews are auto-routed.  ℹ︎  info     oss-code-of-conduct-exists              Consider adding a              CODE_OF_CONDUCT.md. Summary (5 violations):  ⚠︎ 1 warning   ℹ︎ 4 info  10 passing · 5 failing

    Fix what it found, or turn a rule off with level: off. Then add the Action so every pull request runs it.