Configuration
.alint.yml is the only file alint reads. It declares the rules, the rule sources, the facts they’re gated on, and a handful of run-time knobs.
Point your YAML language server at the JSON Schema for editor autocomplete:
# yaml-language-server: $schema=https://alint.org/_alint/configuration/schema.jsonversion: 1extends: - alint://bundled/oss-baseline@v1The schema is also published in the alint repo at schemas/v1/config.json.
Top-level fields
Section titled “Top-level fields”version
Section titled “version”Schema version. Always 1 for the current schema. Required.
version: 1A future schema bump (version: 2, …) would be an explicit migration; v1 is stable.
extends
Section titled “extends”Configs to inherit from. Resolved left-to-right; later entries override earlier ones; the current file’s own definitions override everything it extends.
Each entry is either a bare string or a mapping with url: and optional only: / except: filters:
extends: # Local file (relative to the current .alint.yml): - ./shared/team-defaults.yml
# HTTPS URL with required SHA-256 SRI: - https://example.com/rules.yml#sha256-abc123…
# Bundled ruleset, resolved offline from the binary: - alint://bundled/oss-baseline@v1
# Mapping form, same source kinds, but with filters: - url: alint://bundled/ci/github-actions@v1 only: [gha-pin-actions-to-sha]
- url: alint://bundled/oss-baseline@v1 except: [oss-code-of-conduct-exists]only: and except: are mutually exclusive on a single entry. Listing an unknown rule id is a load-time error.
Bundled and HTTPS configs cannot themselves declare extends:, because relative-path resolution in a fetched body has no principled base. Nest extends locally instead.
ignore
Section titled “ignore”Extra glob patterns to exclude from the walk, on top of .gitignore. Same gitignore-style syntax. Use this for repo-specific exclusions you don’t want in .gitignore itself (because they’re an alint concern, not a git concern):
ignore: - "vendor/**" - "**/*.snapshot.json" - "fixtures/golden/**"ignore: patterns apply regardless of respect_gitignore. See The walker and .gitignore for what gets filtered by default and how absence-style rules interpret git state.
respect_gitignore
Section titled “respect_gitignore”Whether to honor .gitignore files (and .git/info/exclude, the global gitignore, and .ignore files) during the walk. Default true.
respect_gitignore: true # default; honor .gitignore# respect_gitignore: false # lint everything on disk regardlessSetting it to false is rarely useful during normal development because absence-style rules (dir_absent, file_absent) start firing on every locally-built artefact (target/, node_modules/, __pycache__/, and so on). It’s appropriate for one-off audits or for directories that aren’t git repos at all. The CLI’s --no-gitignore flag overrides this for one invocation.
The full implications (including how absence-style rules interpret “tracked” vs “ignored” and where this approximation diverges from git’s actual index) live in The walker and .gitignore.
Free-form string variables referenced from rule messages as {{vars.<name>}} and from when: clauses as vars.<name>.
vars: copyright_year: "2026" org_name: "Acme"
rules: - id: copyright-header kind: file_header paths: "src/**/*.rs" pattern: '^// Copyright \\(c\\) {{vars.copyright_year}} {{vars.org_name}}' level: errorProperties of the repo evaluated once per run. Used in when: clauses to gate rules conditionally.
facts: - id: has_rust any_file_exists: [Cargo.toml] - id: n_rs_files count_files: "**/*.rs" - id: has_src_dir all_files_exist: ["src/.keep"]
rules: - id: rust-snake-case when: facts.has_rust and facts.n_rs_files > 5 kind: filename_case paths: "src/**/*.rs" case: snake level: errorAvailable fact kinds: any_file_exists, all_files_exist, count_files, file_content_matches, git_branch, custom.
custom (which spawns a subprocess) is a security boundary: it’s only allowed in your own top-level config. Any extends: ancestor that declares one is rejected at load time, so a malicious or compromised ruleset can’t execute arbitrary code merely by being fetched.
The rules themselves. Each has at least an id, kind, and level. Most have a paths glob; some kinds add their own option fields (e.g. min_lines:, path: + equals: for structured queries). See the Rules section for every kind.
rules: - id: readme-exists kind: file_exists paths: ["README.md", "README", "README.rst"] root_only: true level: error fix: file_create: content: "# Project\n"
- id: no-bidi-controls kind: no_bidi_controls paths: "**/*" level: error policy_url: "https://trojansource.codes/"Common per-rule fields:
id(required): kebab-case identifier. Stable; used to override or disable the rule from a child config.kind(required): which built-in implementation to invoke. Required somewhere in theextends:chain.level(required):error,warning,info, oroff.offdisables the rule entirely.paths: glob, list of globs, or{include, exclude}pair. Required for most kinds.when: bounded expression gating the rule on facts / vars.scope_filter: closest-ancestor manifest scoping for per-file rules (see below). Cross-file rules reject this field at build time.fix: fix-op declaration (e.g.file_trim_trailing_whitespace: {}).message: override the rule’s display message.policy_url: link surfaced when the rule fires.
scope_filter (per-file rules, v0.9.6+)
Section titled “scope_filter (per-file rules, v0.9.6+)”Narrows a per-file rule to files that have a specified manifest somewhere in their ancestor directory chain. The engine walks Path::parent() upward from the file (the file’s own directory counts as an ancestor) and consults the file index at each step; first-match-wins on the upward walk gates the rule per-file. Combine with the rule’s existing paths:. Both must match for the rule to fire.
rules: - id: rust-sources-no-bidi when: facts.has_rust kind: no_bidi_controls paths: "**/*.rs" scope_filter: has_ancestor: Cargo.toml # single string OR a list level: errorhas_ancestor: accepts a literal filename or a list of filenames; path separators and glob metacharacters are rejected at build time. The bundled ecosystem rulesets (rust@v1, node@v1, python@v1, go@v1, java@v1) use this to scope per-file content rules to their ecosystem’s package subtrees in polyglot monorepos.
Cross-file rules (pair, for_each_dir, file_exists, etc.) reject scope_filter: at build time with a pointer to the for_each_dir + when_iter: pattern. Rule-major rules like filename_case silently ignore the field; gate them via the rule’s paths: glob instead.
fix_size_limit
Section titled “fix_size_limit”Maximum file size, in bytes, that content-editing fixes will read and rewrite. Files over this limit are reported as Skipped in the fix report and a one-line warning is printed to stderr.
fix_size_limit: 1048576 # 1 MiB; the default# fix_size_limit: null # disable the cap entirely (not recommended)Path-only fixes (file_create, file_remove, file_rename) ignore the cap, since they don’t read content.
nested_configs
Section titled “nested_configs”Opt in to discovery of .alint.yml / .alint.yaml files in subdirectories. Default false.
# repo-root .alint.ymlversion: 1nested_configs: trueextends: - alint://bundled/oss-baseline@v1When true, the loader walks the tree from the root config’s directory (respecting .gitignore and ignore:) and picks up every nested config. Each nested rule’s path-like scope fields (paths, select, primary) are auto-prefixed with the nested config’s relative directory, so the rule scopes to that subtree.
version: 1rules: - id: components-pascal kind: filename_case paths: "components/**/*.{tsx,jsx}" # ↑ evaluates as if it read paths: "packages/frontend/components/**/*.{tsx,jsx}" case: pascal level: errorGuardrails: nested configs may only declare version: and rules:; every nested rule must have at least one scope field; absolute paths and ..-prefixed globs are rejected; rule-id collisions across configs error with a clear message.
Only the user’s top-level config may set nested_configs: true. Nested configs themselves cannot spawn further nested discovery (one level of opt-in, intentionally).
See also
Section titled “See also”- JSON Schema: authoritative source for option types.
- Rules: every rule kind, organised by family, with per-rule options.
- Concepts: the rule model and
when:expression language explained in depth.