Skip to content

Scoping

A rule never judges the whole repository. It narrows from the walked index to a specific set of files through three gates applied in a fixed order: when: decides whether the rule runs at all, paths: selects files by glob, and scope_filter: refines that selection per file. Only what survives all three is evaluated.

A rule narrows the file index through gates in a fixed order A when: gate decides whether the rule runs. Then the walked index of 40 files narrows through the paths: glob to 12 and through scope_filter: to 8, leaving 8 files evaluated. Each gate is a narrower bar than the one above it. The scope_filter gate offers several predicates (has_ancestor, changed_since, manifest paths) that AND-compose. when: facts.has_rust true: the rule runs. false: dropped before any file is read. walked index40 files paths: src/**/*.rs12 scope_filter: has_ancestor8 evaluate8 each gate ANDs onto the last; the set only ever shrinks scope_filter predicates, AND-composed: has_ancestor changed_since manifest_paths

when: is a run-wide switch, not a per-file filter. It is a bounded boolean expression over four namespaces (facts., vars., env., and iter. inside iteration contexts), evaluated once per run against facts computed once. If it is false, the whole rule is dropped before a single file is read, so a rule gated on an absent ecosystem costs nothing. A missing fact reads as null (falsy).

paths: is the primary selector: a glob, a list of globs, or an {include, exclude} pair matched against the walked index. It answers “which files is this rule about.”

scope_filter: refines that per file, for the cases a glob cannot express. Its predicates AND-compose: when more than one is set, a file must satisfy all of them. At least one predicate must be present. Cross-file rules (pair, for_each_dir, and their siblings) reject scope_filter: at build time, with a pointer to the for_each_dir + when_iter: pattern instead; the rule-major per-file kinds (filename_case, file_max_size, and their siblings) honor it as of v0.15.

  • has_ancestor: keeps a file only when a named manifest sits somewhere in its ancestor directory chain. The engine walks Path::parent() upward (the file’s own directory counts) and stops at the first match, so a content rule scopes to just its ecosystem’s subtree in a polyglot monorepo. The bundled ecosystem rulesets use it to confine per-file rules to their package subtrees.
  • changed_since: <git-ref> keeps only files in the <ref>...HEAD merge-base diff, the form --changed --base=<ref> uses (bare --changed uses a working-tree diff instead). It accepts {{env.X}} interpolation and resolves the diff once per run.
  • include_manifest_paths: / exclude_manifest_paths: scope by membership in a path set a manifest declares (a Cargo.toml workspace.members, a package.json bin), so the manifest that owns the truth and the rule that depends on it stay in one place. An optional derive_target: { from, to } regex maps a declared build output back to its source (dist/cli.js back to src/cli.ts); expect_nonempty: (default true) warns when an include set resolves to nothing rather than silently matching no files.

A manifest value only gates which files a rule sees, never what it decides about them: extraction is pure parsing (no spawn, so it is safe inside an extends:’d ruleset), and alint explain <rule> prints the resolved set.

Require an SPDX header, but only on the Rust files a PR actually touched, and only in a repo that has Rust at all:

version: 1
facts:
- id: has_rust
any_file_exists: [Cargo.toml]
rules:
- id: spdx-on-changed-rust
kind: file_header
when: facts.has_rust
paths: "**/*.rs"
pattern: "^// SPDX-License-Identifier:"
scope_filter:
has_ancestor: Cargo.toml
changed_since: "{{env.ALINT_BASE_SHA | default('origin/main')}}"
level: error
message: "add an SPDX-License-Identifier header"

On a PR that adds one unheadered crates/core/src/new.rs:

error spdx-on-changed-rust add an SPDX-License-Identifier header

Unchanged Rust files, and every file outside a Cargo.toml subtree, are never considered.

  • Configuration is the field reference for every scope_filter: predicate and its options.
  • The walker and git is the index these gates narrow, and where git_tracked_only: is defined.
  • Changed mode is the run-wide --changed counterpart to the per-rule changed_since: predicate.