git_blame_age
Fire on lines matching a regex whose git blame author-time is older than max_age_days. Same regex match shape as file_content_forbidden, but with a per-line age gate: a recent TODO passes silently; an old TODO fires. The comparison uses one deterministic UTC reference: --as-of YYYY-MM-DD, then ALINT_AS_OF, then the HEAD commit's committer timestamp. Every finding ends with (as of YYYY-MM-DD).
{{ctx.match}} substitutes the regex capture group 1 when present, otherwise the full match — useful for surfacing which marker was caught (TODO vs FIXME vs …). alint explain <id> shows the resolved date and whether it came from explicit input or HEAD.
Heuristic notes:
- Formatting passes reset blame age.
cargo fmt/prettierrewrites every touched line, attributing it to the format commit rather than the original author. List formatting-sweep commits in a tracked, regular.git-blame-ignore-revs; alint applies that repository input but ignores global and clone-localblame.ignoreRevsFilelists. - Vendored / imported code carries the import commit's timestamp — exclude
vendor/,third_party/, generated trees. - Squash-merged PRs collapse to a single commit date, so the squash date wins over the actual edit date.
- Performance.
git blameis O(file_size × commits_touching_file) per file. On large monorepos pair withalint check --changedso blame only runs over modified files in CI.
If Git cannot be run or the lint root is not a Git repository, the rule reports a resource_read analysis failure even when --as-of supplies the reference date. In a usable repository with an unborn HEAD, provide --as-of or ALINT_AS_OF; otherwise the missing reference date is an evaluation failure. Untracked files and individual files whose blame fails remain silent no-ops. Check-only — auto-removing matched lines is destructive and pinning a line as "do nothing" doesn't help.
Options
Section titled "Options"| Option | Type | Required | Default | Description |
|---|---|---|---|---|
max_age_days | integer (>= 1) | yes | Minimum line age (in days) for a matching line to fire as a violation. Lines younger than this pass silently. Common values: 90 (one quarter), 180 (half a year), 365 (a full year of debt). | |
pattern | string | yes | Rust-regex pattern applied to each blame line's content. Capture group 1 (when present) is exposed as {{ctx.match}} in the message template; without a capture group, {{ctx.match}} substitutes the full match. |
Plus the common paths, level, id, when, scope_filter, and expect_matches fields. This table is generated from the JSON Schema; option types and defaults are authoritative.
Example
Section titled "Example"A TODO comment that has gone stale
Section titled "A TODO comment that has gone stale"The rule fires on this repository:
- reference.txt✓
- src/
- src/main.rs
⚠ warning stale-todos
2:1 This TODO is older than 30 days; address it or remove the marker. (as of 2026-10-16)
advance HEAD's deterministic analysis timefn main() { // TODO: refactor this}With this .alint.yml:
version: 1rules: - id: stale-todos kind: git_blame_age paths: "**/*.rs" pattern: '\bTODO\b' max_age_days: 30 message: "This TODO is older than 30 days; address it or remove the marker." level: warningcommitted with this history (oldest first):
2000-01-01T00:00:00 add a todo (adds src/main.rs)2026-10-16T00:00:00 advance head (adds reference.txt)alint check reports:
--- src/main.rs ---------------------------------------------------------------- ! warning stale-todos 2:1 This TODO is older than 30 days; address it or remove the marker. (as of 2026-10-16)
Summary (1 violation): ! 1 warning 0 passing * 1 failingA freshly-added TODO
Section titled "A freshly-added TODO"This repository is compliant:
- src/
- src/main.rs✓
fn main() { // TODO: refactor this}With this .alint.yml:
version: 1rules: - id: stale-todos kind: git_blame_age paths: "**/*.rs" pattern: '\bTODO\b' max_age_days: 30 message: "This TODO is older than 30 days; address it or remove the marker." level: warningcommitted with this history (oldest first):
add a todo (adds src/main.rs)alint check reports:
v All 1 rule(s) passed.