alint
A language-agnostic linter for repository structure, file existence, filename conventions, and file content rules.
Status: early development. v0.1 is the MVP — see docs/design/ROADMAP.md for scope per version.
What alint does
alint enforces declarative rules over a repository tree. The rule model and DSL are described in full at docs/design/ARCHITECTURE.md. Representative rules (some shipped in v0.1, some planned for v0.2):
- A
README.mdmust exist at the repo root. (v0.1) - Every
.javafile must start with a required license-header comment. (v0.1) - Filenames under
components/must be PascalCase. (v0.1) - No binary files may exist under
src/. (v0.1) - Every
.cfile must have a matching.hfile in the same directory. (v0.2 — cross-file rules) - For every subdirectory of
src/, amod.rsmust exist. (v0.2 — per-directory quantification)
Rules are defined in .alint.yml. alint walks the tree (honoring .gitignore by default), matches each rule against the index, and emits results in human, json, sarif, or github (GitHub Actions annotations) format, with JUnit and Markdown arriving in v0.3.
Non-goals
alint is deliberately not:
- a code / AST linter — use ESLint, Clippy, ruff
- a SAST scanner — use Semgrep, CodeQL
- an IaC scanner — use Checkov, Conftest, tfsec
- a commit-message linter — use commitlint
- a secret scanner — use gitleaks, trufflehog
Scope is the filesystem shape and contents of a repository, not the semantics of the code inside it.
Install
From a tagged release (recommended)
|
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
From source
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
rules:
- id: readme-exists
kind: file_exists
paths:
root_only: true
level: error
- id: no-large-blobs
kind: file_max_size
paths: "**"
max_bytes: 1048576
level: warning
- id: components-pascal
kind: filename_case
paths: "components/**/*.{tsx,jsx}"
case: pascal
level: error
- id: java-license-header
kind: file_header
paths: "**/*.java"
lines: 20
pattern: "(?s)Copyright \\(c\\) \\d{4}"
level: error
Then run:
Output formats:
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.1.0
All inputs (all optional):
- uses: asamarts/alint@v0.1.0
with:
version: v0.1.0 # 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.1.0
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 }}
Docs
- ARCHITECTURE.md — rule model, DSL, execution model, crate layout, plugin model.
- ROADMAP.md — scope per version from v0.1 through v1.0.
- docs/benchmarks/METHODOLOGY.md — how benchmarks are measured and published.
- Per-version, per-platform benchmark results under
docs/benchmarks/<version>/.
Development
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.