Roadmap
This roadmap is scope-based; dates are deliberately omitted. Each version is a closed cut, work that doesn’t fit moves to a later version. See ARCHITECTURE.md for the design these phases build out.
Source convention: this file is the canonical roadmap. The generated public version at alint.org is produced by
xtask gen-public-roadmap, which elides sections wrapped in pairedalint:internal-*HTML-comment markers. Seev0.11/roadmap_generator.mdfor the marker syntax and the v0.9.22 migration plan.
Latest release: v0.13.0 (2026-06-17). The spec-driven-development program
landed as a release: the config JSON Schema is now generated from the Rust
option types (powering the per-rule options docs), facts.json and
roadmap.json publish the surface-area counts and the public roadmap as
machine-readable contracts, the crate-dependency graph and the architecture are
a single LikeC4 model rendered interactively on alint.org (and exported to
Mermaid for GitHub), and the v0.12.0 path-confinement boundary gains a
Kani-verified proof. All are generated and regenerate-and-diff gated, and
alint.org renders its headline counts from facts.json. See the
Engineering foundations
section below.
v0.12.0 (2026-06-07). The case-study-driven rule-kind expansion paired with
a security cycle. New kinds: file_graph (file-dependency-graph firewalls +
cycle / orphan / dangling checks), for_each_match (a per-line predicate
quantifier), a unified cross_file value-relation kind, pair_changed_together
(a co-change gate), and generated_file_fresh mutating / in-place mode, plus
markerless ordered_block, a php@v1 bundled ruleset, JSONC-tolerant structured
parsing, and more import_gate presets. Security: every config-declared path a
rule reads or resolves is now confined to the repo root (the untrusted-extends:
threat model), with allow_out_of_root: as the explicit top-level opt-in, the
walker pruning symlinks that escape the tree, and a fixed git argument-injection
in the since: range mode (write/truncate of an arbitrary out-of-tree file;
affected releases back to v0.9.21).
v0.11.1 (2026-05-31)
was a JetBrains-plugin patch that
clears JetBrains Marketplace moderation: the v0.11.0 plugin read its own
version via platform plugin-lookup APIs (PluginManagerCore /
PluginManager) the Marketplace rejects as internal even though
intellij-plugin-verifier passes them; the version is now stamped into
a build-time classpath resource with zero platform-API surface, gated by
a new verifyNoMarketplaceDeniedApis bytecode check. Linter behavior
unchanged. v0.11.0 (2026-05-28) was the LSP + editor-integration
release: the alint lsp language server (the new alint-lsp crate)
with in-editor diagnostics, hover-to-explain, and apply-fix quick
actions, wired across the VS Code, JetBrains, and Zed extensions plus
the Neovim / Sublime / Emacs / Helix configs; the v0.9.21-derived DSL
polish (scope_filter.changed_since:, the git_commit_*
commit-validation family, {{env.X}} interpolation); and an
informational-notes channel. v0.10.2 (2026-05-21) was the asciinema-demo
OSC 8 hyperlink fix. v0.10.1 (2026-05-20) extended
crate::io::read_capped to json_schema_passes +
for_each_dir’s literal-path bypass (two pre-existing read
sites missed by the original Phase 3 sweep), plus CI hygiene
(bench-record PR-body template refresh, runner agent bump
2.332.0 → 2.334.0, Docker channel example :0.9 → :0.10).
v0.10.0 (2026-05-20) was the case-study
coverage push (eight new rule kinds, registry_paths_resolve,
cross_file_value_equals, ordered_block, generated_file_fresh,
import_gate, command_idempotent, xml_path_equals /
xml_path_matches, pair_hash, and two new bundled rulesets,
apache/governance@v1 + dotnet@v1; three pre-release
security retirements: spawn-trust-gate generalised, XML
recursion bound, whole-file reads capped at 256 MiB; rule-kind
count 71 → 79, bundled-ruleset count 19 → 21).
v0.9.23 (2026-05-17) shipped GitHub Action pinning plus release-
pipeline hardening (pinned-ref binary, cargo-deny, the bench-gate
re-engineering); v0.9.22 (2026-05-15) was doc-drift cleanup plus
prevention automation; v0.9.21 shipped commit-range mode for
git_commit_message (closes #26);
v0.9.12 - v0.9.20 brought the launch-readiness capstone (width-
aware output, bundled-rule message audit, em-dash scrub, install-
snippet pin sweep, polyglot demo). See
CHANGELOG.md for per-version detail and
docs/development/launch-evidence.md
for the case-study corpus + rule-kind demand aggregation.
Launch readiness: clean. Pre-flight gates wired:
cargo fmt --check + clippy + test + docs + version-pin
consistency + dogfood, all bundled into
ci/scripts/preflight.sh and gated by an opt-in pre-push hook
(ci/githooks/pre-push). The install-snippets-match-workspace -version rule in the dogfood .alint.yml flags drift between
Cargo.toml’s workspace version and any user-facing install
snippet, so a release with stale v0.9.X README/Docker/GHA pins
fails CI before it ships. The project has been launch-ready
since the v0.9.x preflight-gate capstone; public-launch timing
is tracked in the alint.org marketing repo (marketing/STATE.md
and marketing/launch-strategy.md), deliberately not pinned to
a version here, since a scope-based, dateless roadmap shouldn’t
carry a launch-version that keeps going stale.
v0.9 cut closed (2026-05-02). A scaling-profile
investigation surfaced a +28-37% 1M S3 regression vs
v0.5.6, for_each_dir cross-file dispatch was running
nested rules in O(D × N) shape (5 B glob-match operations
at 1M with 5,000 packages). The fix landed on main as
v0.9.5 (lazy path-index on FileIndex + literal-path fast
paths in file_exists, structured_path,
iter.has_file); 1M S3 wall: 731.9 s → 11.19 s (65×) full,
6.73 s (108×) changed. v0.9.5 also folded in the
test/coverage/dogfood floor that prevents the same class of
regression from slipping by again:
- v0.9.5.5, cross-file dispatch fast paths (the headline)
- v0.9.5.6, coverage audits (pass/fail symmetry, bundled- ruleset coverage, git-mode symmetry)
- v0.9.5.7, 16 coverage scenarios filling the audit punch list
- v0.9.5.8, bench-scale S6 (per-file content fan-out) / S7 (cross-file relational) / S8 (git-tracked overlay)
- v0.9.5.9,
docs/development/rule-authoring.mdworkflow doc - v0.9.5.reorg,
docs/benchmarks/{micro,macro,investigations,archive}/layout + per-version results +xtask publish-benches
Full v0.9.5 design: docs/design/v0.9/coverage-and-dogfood.md.
v0.9.6 (2026-05-02), scope_filter: primitive. Closest-
ancestor manifest scoping for per-file rules, plus the
bundled-ecosystem-ruleset migration that motivated it. The
is_* ecosystem facts (is_rust, is_node, …) renamed to
has_* and broadened from root-only [Cargo.toml] to
[Cargo.toml, "**/Cargo.toml"] so polyglot monorepos with
no root manifest correctly fire the ruleset; per-file content
rules add scope_filter: { has_ancestor: <manifest> } so
they only fire on files inside their ecosystem’s package
subtree. New bench-scale S9 scenario (nested polyglot:
rust + node + python over crates/ + packages/ + apps/)
captures the dispatch shape this primitive was designed for.
Full design: docs/design/v0.9/scope-filter.md.
Forward plan, ordered by user-facing payoff. The next two
versions split what was originally one v0.10 release into two
focused cuts. Rule-kind coverage ships first because the
case-study aggregation (30 repos under
docs/development/launch-evidence.md)
identified 8 primitives with 5-13 demand sources each. LSP +
editor integration is the second cut because no user is blocked
on it for adoption, they can lint without it.
-
v0.10, Case-study coverage push. 8 case-study-validated rule kinds + 2 bundled rulesets. Closes the gap that today requires
command:shell-out for what should be declarative rules. Sized 2-4 weeks. Headline primitives:cross_file_value_equals(12 sources),registry_paths_resolve(13 sources),ordered_block(8),generated_file_fresh(8),import_gate(5),command_idempotent(5),pair_hash(3),xml_path_matches+xml_path_equals(2, completes the structured-query family). Bundled rulesets:apache/governance@v1(3 Apache TLPs converge),dotnet@v1(Microsoft / Azure SDK adopter surface). Design candidates land opportunistically as a second demand source materialises. -
v0.11, LSP + developer experience. LSP server, VS Code extension, ScopeFilter generalisation (
has_sibling,has_descendant), single-file re-eval hot path, and the v0.11+ ship-targets that didn’t make v0.10:cross_language_implementation_complete(5 sources: arrow, tensorflow, protobuf, angular, flutter), Bazel-licensing-declaration-aware rule kind (tensorflow’slicenses(["notice"])discipline),walk_error_policy:engine knob (pnpm broken-symlink fixtures). LSP design +tower-lspworkspace dep already landed in v0.9.7 so the crate scaffold is the only structural lift. -
v0.12, Real-world coverage expansion + gap rule kinds. A 100+ repo case study (3-4× the 30-repo re-analysis) plus the demand-ranked rule-kind backlog it produced:
git_commit_subject_matches, the diff “must-add” family (changeset_requires_path/pair_changed_together), the value-set membership family, anormalize:transform oncross_file_value_equals, richerimport_gate(default-deny + glob-discovered rule files), a resolved-graph dependency allowlist, and the ASF compliance-bundle over-fire fix. -
v0.14, WASM plugins. Bumped again to make room for the engineering-foundations program, which took the v0.13.0 cut.
wasmtimehost, signed plugin registry, blessed examples (mock-ratio checker, near-dup detector, debug-statement stripper). -
v1.0, Stability. DSL committed, plugin ABI committed,
alint-corepublic API frozen, docs site committed.
Single-source design candidates that surfaced in the
case-study aggregation but haven’t hit a second demand source:
*_path_contains, pair_inverse, command_per_repo,
json_schema_passes config-shape mode, *_path_array_iter,
json_key_sort_order, column_alignment, line_spacing,
not_executable, directory_hash. Most won’t survive
demand-validation; they’re tracked under “Emerging gaps” in
examples/README.md
so post-launch users can vote with their adoption.
Positioning
Section titled “Positioning”alint’s scope is the filesystem shape and contents of a
repository, not the semantics of the code inside it. Sweet
spot: workspace-tier monorepos (Cargo, pnpm, yarn, Lerna) and
OSS-style polyglot monorepos. Honest limits: dependency-graph
problems (cargo deny, bazel mod, buildifier) and
code-content problems (linters, SAST) are explicit non-goals;
hyperscale Bazel monorepos are not the design center:
some primitives (notably for_each_dir) need a per-iteration
when: filter to apply there cleanly, addressed in the v0.5
Monorepo & scale subsection below.
The adoption ladder this design points toward:
one-line bundled start → ecosystem overlay (rust@v1 /
node@v1 / python@v1 / go@v1 / java@v1) → CI hardening
(ci/github-actions@v1) → field-level overrides → custom
structured-query rules → pre-commit + GHA wiring →
git_tracked_only for absence rules → nested_configs: true.
v0.5 prioritizes the next rung: tighter monorepo ergonomics
for workspace-tier and OSS-polyglot adopters.
v0.1: MVP (shipped)
Section titled “v0.1: MVP (shipped)”The smallest scope that is usefully adoptable.
- ✅ Walker (honors
.gitignore), config loader (YAML + JSON Schema validation), globset-based scopes. - ✅ Rule primitives:
file_exists,file_absent,dir_exists,dir_absent,file_content_matches,file_content_forbidden,file_header,filename_case,filename_regex,file_max_size,file_is_text. - ✅ Output formats:
human,json. - ✅ CLI subcommands:
check,list,explain. - ✅ JSON Schema published for editor autocomplete (
schemas/v1/config.json). - ✅ Benchmarks published with the release, criterion micro-benches under
crates/alint-bench/and hyperfine macro-benches viaxtask bench-release. Methodology atdocs/benchmarks/METHODOLOGY.md; per-platform results underdocs/benchmarks/archive/v0.1/. - ✅ Static binaries on GitHub Releases, install script,
cargo install alint, release workflow at.github/workflows/release.yml, installer atinstall.sh. - ✅ Pre-publish hygiene: binary package renamed
alint-cli→alint; internal crates flaggedpublish = false(onlyalint+alint-corepublish); crates.io metadata populated on the public crates;LICENSE-APACHE+LICENSE-MIT+ rootREADME.mdadded. - ✅ Dogfood
.alint.ymlexercising the tool against its own repo.
v0.2: Cross-file and composition (shipped)
Section titled “v0.2: Cross-file and composition (shipped)”- Cross-file primitives: ✅
pair, ✅for_each_dir, ✅for_each_file, ✅every_matching_has, ✅dir_contains, ✅dir_only_contains, ✅unique_by. (complete) - Facts system: ✅
any_file_exists, ✅all_files_exist, ✅count_files, ✅file_content_matches, ✅git_branch, ✅custom(security-gated; only allowed in the top-level config, never inextends:); ⏳detect: linguist, ⏳detect: askalono, both likely v0.5 alongside bundled rulesets. - ✅
whenexpression language, bounded grammar withand/or/not, comparison ops (==!=<<=>>=),in(list/substring),matches(regex), literal types (bool/int/string/list/null), andfacts.X/vars.Xidentifiers. Parsed at rule-build time; gates rules in Engine + nested rules infor_each_*. - ✅
extends: local files (recursive resolution, cycle detection, child-overrides-parent merge) + HTTPS URLs with SHA-256 SRI and caching under the platform user cache dir (~/.cache/alint/rulesets/on Linux). Nested remote extends deferred to v0.3, a relative path inside a fetched config has no principled base. - ✅
fixsubcommand withfile_create,file_remove,file_prepend,file_append,file_rename(the latter wired tofilename_case, target name derived from the rule’scase:setting; extension preserved).- Deferred for later (likely v0.5 when bundled rulesets land):
content_from: <path>forfile_create/file_prepend/file_append, so long bodies (LICENSE texts, standard boilerplate) can live alongside the rule rather than inline in YAML. - Deferred (likely v0.3): a
rename_to:template forfilename_regex, so the pattern’s capture groups can drive a substitution target. Not yet designed.
- Deferred for later (likely v0.5 when bundled rulesets land):
- ✅ Output formats:
sarif,github. - ✅ Official GitHub Action (
action.ymlat repo root; composite action wrappinginstall.sh).
v0.3: Hygiene, portable metadata, byte fingerprints (shipped)
Section titled “v0.3: Hygiene, portable metadata, byte fingerprints (shipped)”The v0.3 cut shifted scope mid-cycle. The originally-planned “structured content” family (JSON/YAML/TOML path queries) was rolled over to v0.4; the freed capacity was spent on content and metadata rules that surfaced during dogfooding as common pain points in real repos.
- ✅ Text hygiene:
no_trailing_whitespace,final_newline,line_endings,line_max_width,indent_style,max_consecutive_blank_lines(+file_collapse_blank_linesfix op). - ✅ Security / Unicode sanity:
no_merge_conflict_markers,no_bidi_controls,no_zero_width_chars(+file_strip_bidi/file_strip_zero_widthfix ops). - ✅ Encoding + content fingerprint:
no_bom(+file_strip_bom),file_is_ascii,file_hash. - ✅ Structure:
max_directory_depth,max_files_per_directory,no_empty_files. - ✅ Portable metadata:
no_case_conflicts,no_illegal_windows_names. - ✅ Unix metadata:
no_symlinks,executable_bit,executable_has_shebang,shebang_has_executable. - ✅ Git hygiene:
no_submodules. - ✅ Byte-level fingerprint:
file_starts_with,file_ends_with. - ✅ Auto-fix ops added:
file_trim_trailing_whitespace,file_append_final_newline,file_normalize_line_endings,file_strip_bidi,file_strip_zero_width,file_strip_bom,file_collapse_blank_lines. - ✅
fix_size_limittop-level config knob (default 1 MiB;nulldisables), content-editing fixers skip oversize files with a stderr warning rather than rewrite them. - ✅ Short-name rule aliases (
content_matches,content_forbidden,header,max_size,is_text) for rules without adir_*sibling.
Deferred to v0.4: structured-query primitives (json_path_*, yaml_path_*, toml_path_*, json_schema_passes), file_footer, file_max_lines, file_shebang, opt-in nested .alint.yml discovery for monorepos, markdown / junit / gitlab output formats, alint facts subcommand for debugging when clauses.
v0.4: Bundled rulesets + pre-commit (shipped)
Section titled “v0.4: Bundled rulesets + pre-commit (shipped)”Pulled forward from what was v0.5: bundled rulesets are the
single biggest adoption lever, turning “write 20 rules” into
“add one extends: line.” Also lands pre-commit framework
integration so any pre-commit user adopts alint with 4 lines of
YAML.
- ✅
.pre-commit-hooks.yaml, exposesalint(check) andalint-fix(manual-stage) hooks.language: rustmeans zero setup for pre-commit users. - ✅ Bundled rulesets infra:
alint://bundled/<name>@<rev>URI scheme resolved offline viainclude_str!. Cycle-safe, leaf-only (bundled rulesets cannot themselvesextends:). Inherits the samecustom:-fact guard as HTTPS extends. - ✅
alint://bundled/oss-baseline@v1, 9 rules. Community docs + content hygiene most OSS repos want. - ✅
alint://bundled/rust@v1, 10 rules. Gatedwhen: facts.has_rustso it’s a safe no-op in polyglot trees. - ✅
alint://bundled/node@v1, 8 rules. Gatedwhen: facts.has_node. - ✅
alint://bundled/monorepo@v1, 4 rules. Language-agnosticfor_each_dirover{packages,crates,apps,services}/*.
v0.4.x point releases (shipped)
Section titled “v0.4.x point releases (shipped)”Ten point releases shipped after v0.4.0, expanding scope well past the original cut. Most of what was originally planned for v0.5 landed here.
- v0.4.1, packaging fix.
- v0.4.2, pretty
humanformatter overhaul. - v0.4.3, composition: field-level rule override; nested
.alint.ymldiscovery for monorepos (nested_configs: true). Four bundled rulesets:hygiene/no-tracked-artifacts@v1,hygiene/lockfiles@v1,tooling/editorconfig@v1,docs/adr@v1. - v0.4.4,
file_min_size+file_min_linescontent rules; six structured-query rule kinds ({json,yaml,toml}_path_{equals,matches}). README rewritten as a 12-pattern cookbook. - v0.4.5,
alint://bundled/ci/github-actions@v1;if_present: trueon structured-query rules; selective bundled adoption (only:/except:onextends:entries). - v0.4.6,
alint://bundled/python@v1+alint://bundled/go@v1;alint factssubcommand for debuggingwhen:clauses. - v0.4.7, distroless Docker image (
ghcr.io/asamarts/alint)- Homebrew tap (
asamarts/alint).
- Homebrew tap (
- v0.4.8,
git_tracked_only: bool, first git-aware rule primitive. Closes the absence-rule false-positive on locally built artifacts. - v0.4.9,
alint://bundled/java@v1. First bundled use ofgit_tracked_only. - v0.4.10,
file_max_lines+file_footer+file_shebanground out the content family. Catalogue at ~55 rule kinds.
v0.5: Monorepo scale + plugins v1 + remaining distribution
Section titled “v0.5: Monorepo scale + plugins v1 + remaining distribution”The v0.4.x cuts cleared most of the original v0.5 scope (structured-query, ecosystem rulesets, alint facts, Docker, Homebrew, first git-aware primitive). What remains, plus new monorepo-scale work surfaced by the 2026-04 monorepo positioning analysis.
Composition & reuse
Section titled “Composition & reuse”A coherent sub-theme on making .alint.yml shareable,
overridable, and monorepo-friendly. Ranked by leverage ÷ effort.
- ✅ Field-level rule override. Children in the
extends:chain can specify only the fields they change (rules: - {id: X, level: off}); kind/paths/etc inherit from the earliest ancestor that declares them. Shipped 2026-04-22 (commit261dda5). - ✅ Refreshed
extends:schema docs. Mention SRI syntax,alint://bundled/URLs, merge semantics, and thelevel: offdisable idiom. Shipped 2026-04-22 (commit261dda5). - ✅ Nested
.alint.ymldiscovery for monorepos. Opt-in vianested_configs: trueon the root config. Each nested rule’s path-like scope fields (paths,select,primary) auto-prefix with the config’s relative directory. Cross- subtree id collisions are rejected for MVP. Shipped 2026-04-22. - ✅ Rule templates / parameterized rules, shipped in
v0.5.10 (2026-04-27). New top-level
templates:block defines reusable rule bodies; rules instantiate them viaextends_template: <id>and avars:map for the{{vars.<name>}}substitution. Templates merge through theextends:chain by id. Leaf-only, a template can’t itself reference another, mirroring the bundled-rulesets restriction. - ✅ Selective bundled adoption. Mapping form on
extends:entries withonly: [...](keep listed rules) orexcept: [...](drop listed rules); mutually exclusive; unknown ids error at load. Closes the all-or-nothing limitation. Shipped 2026-04-23 in v0.4.5. - ✅
.alint.d/*.ymldrop-ins, shipped in v0.5.10 (2026-04-27). Auto-discovered next to the top-level.alint.ymland merged alphabetically; the last drop-in wins on field-level conflict (/etc/*.d/shape). Trust-equivalent to the main config, drop-ins live in the same workspace and CAN declarecustom:facts andkind: commandrules. Non-yaml files in the dir are skipped silently. Sub-extended configs don’t get their own.alint.d/; only the top-level config does.
Monorepo & scale
Section titled “Monorepo & scale”Identified by the 2026-04 monorepo positioning analysis as the largest delta between alint’s current shape and what workspace-tier + OSS-polyglot monorepos typically reach for. Ranked by leverage.
- ✅
alint check --changed [--base=<ref>]. Incremental mode: diffgit diff --name-only <base>...HEAD(orgit ls-files --modified --others --exclude-standardwhen no base) and only evaluate rules whose path scopes intersect the changed-file set. Cross-file rules (pair,for_each_dir,every_matching_has,unique_by,dir_contains,dir_only_contains) and existence rules (file_exists,file_absent,dir_exists,dir_absent) opt out of the changed-set filter for iteration, their verdicts span the whole tree by definition, but existence rules still skip when theirpaths:scope doesn’t intersect the diff, so an unchanged-but-missing LICENSE doesn’t fire on every PR. Empty diffs short-circuit to an empty report. Pairs naturally withgit_tracked_only. Shipped in v0.5.0. - ✅ Per-iteration
when_iter:filter onfor_each_dir/for_each_file/every_matching_has, shipped in v0.5.2 (2026-04-26). Newiter.*namespace in the existingwhen:grammar exposes the iterated entry’spath,basename,parent_name,stem,ext,is_dir, andhas_file(pattern); iterations whose verdict is false are skipped before any nested rule is built.iter.has_file("Cargo.toml")/iter.has_file("**/*.bzl")/iter.has_file("BUILD") or iter.has_file("BUILD.bazel")cover the Cargo / Bazel-style workspace gates without a language-specific parser. - ✅
alint init [--monorepo]discovery preset, shipped in v0.5.4 (2026-04-26). Newalint initsubcommand detects ecosystem (Rust / Node / Python / Go / Java) from root manifests and writes a.alint.ymlextending the matching bundled rulesets. With--monorepo, also detects Cargo[workspace], pnpm-workspace.yaml, andpackage.jsonworkspacesfield, and emitsmonorepo@v1+monorepo/<flavor>-workspace@v1plusnested_configs: true. Bazel / Lerna / Nx / Turbo detection deferred (the three covered flavours match the bundled-overlay set). The runtime-side--monorepoflag onalint checkis deferred too,alint initis the primary adoption shape. - ✅ Workspace-aware bundled rulesets, shipped in
v0.5.3 (2026-04-26). Three thin overlays on
monorepo@v1:monorepo/cargo-workspace@v1,monorepo/pnpm-workspace@v1,monorepo/yarn-workspace@v1. Each gated by anis_*_workspacefact (declared inline in the ruleset) and useswhen_iter: 'iter.has_file(...)'to scope per-member checks to actual package directories, no false positives on straycrates/notes/orpackages/drafts/. - ✅ Documented scale ceiling, shipped in v0.5.6
(2026-04-26) as
xtask bench-scale. Publishes hyperfine timings across (size × scenario × mode) matrix with hardware fingerprint; numbers underdocs/benchmarks/macro/results/linux-x86_64/v0.5.7/. 1M-file size opt-in via--include-1m. - ✅ Competitive comparisons, shipped in v0.5.7
(2026-04-26). Same harness now drives ls-lint,
Repolinter, and
find+ripgreppipelines alongside alint, gated to the scenarios each tool can sanely express. Reproducibility via theghcr.io/asamarts/alint-benchDocker image (pinned versions of every competitor) plus a--dockerflag that re-execs the bench inside the image.
Other scope
Section titled “Other scope”- ✅ Structured-query primitives (v0.4.4, 2026-04-23):
json_path_equals,json_path_matches,yaml_path_equals,yaml_path_matches,toml_path_equals,toml_path_matches. JSONPath per RFC 9535; YAML and TOML coerce through serde into the same tree shape.json_schema_passesstill ⏳. - ✅
if_present: trueon structured-query rules (v0.4.5). - ✅ Additional content primitives (v0.4.10):
file_footer,file_max_lines,file_shebang. - ✅
alint factssubcommand (v0.4.6). - ✅ Homebrew formula via
asamarts/alinttap (v0.4.7). - ✅ Distroless Docker image at
ghcr.io/asamarts/alint(v0.4.7). - ✅ Git-aware primitive:
git_tracked_only(v0.4.8). - ✅ Additional bundled rulesets:
python(v0.4.6),go(v0.4.6),ci/github-actions(v0.4.5),java(v0.4.9). - ✅ Output formats:
markdown,junit,gitlab, shipped in v0.5.8 (2026-04-26). Brings the format count to seven; SARIF / GitHub /JUnit/ GitLab fall through to the human formatter onalint fixsince they describe findings, not remediations. - ✅
commandplugin kind (v0.5.1, 2026-04-26). Per-file rule wrapping any CLI onPATH(actionlint/shellcheck/taplo/kubeconform/ etc.); exit0= pass, non-zero = violation carrying stdout+stderr. Trust-gated: only the user’s own top-level config can declare these (mirror of thecustom:fact gate). Pairs naturally with--changedso external checks become incremental in CI. - ✅ npm shim (
@alint/alint), shipped in v0.5.11 (2026-04-27). Closes the install-path gap for JS adopters who don’t already have Cargo, Homebrew, or Docker. Wraps a download of the matching pre-built binary at install time; package itself ships zero JS runtime behaviour. Auto-published fromrelease.ymlalongside crates.io / Docker / Homebrew. - ✅ Git-aware primitives:
git_no_denied_paths,git_commit_message, both shipped in v0.5.9 (2026-04-27). The first fires on tracked paths matching a glob denylist (secrets / artefacts / “do not commit”); the second validates HEAD’s commit message shape via regex / max-subject-length / requires-body. Both no-op silently outside a git repo. - ✅
json_schema_passesprimitive, shipped in v0.5.9 (2026-04-27). Validates JSON / YAML / TOML targets against a JSON Schema; reuses the same serde-tree normalisation asjson_path_*. Schema is loaded + compiled lazily and cached on the rule viaOnceLock. - ✅ Remaining bundled rulesets:
compliance/reuse@v1+compliance/apache-2@v1shipped in v0.5.5 (2026-04-26). Both usefile_headerfor SPDX / Apache header checks; reuse adds adir_existsonLICENSES/; apache-2 addsfile_content_matchesfor the LICENSE text +file_existsfor NOTICE. Both extend without a fact gate, adopting the ruleset signals intent. - ✅ Additional Scorecard-overlap rules in
oss-baseline@v1, shipped in v0.5.9 (2026-04-27). Four new rules: SECURITY.md non-empty, Dependency-Update-Tool (Dependabot OR Renovate), CODEOWNERS exists, CODEOWNERS non-empty. Branch-protection state is GitHub-API-only and out of scope; the on-disk piece (CODEOWNERS) is what alint can see.ci/github-actions@v1is unchanged, its scope is workflow content, not on-disk artefacts; CODEOWNERS belongs in oss-baseline.
Generic hygiene rulesets (shipped in v0.4.3)
Section titled “Generic hygiene rulesets (shipped in v0.4.3)”Identified in a research pass across Turborepo/Nx/Bazel/Cargo/ pnpm docs, OpenSSF Scorecard, Repolinter’s archived corpus, and large orgs’ community-health-file conventions. Four rulesets built on the existing primitive set, no new rule kinds needed.
- ✅
hygiene/no-tracked-artifacts@v1, node_modules, target, dist, .next, .DS_Store, editor backups, .env variants, 10 MiB size gate. Several auto-fixable. - ✅
hygiene/lockfiles@v1, one rule per package manager (npm/pnpm/yarn/bun/Cargo/Poetry/uv) forbidding nested lockfiles. - ✅
tooling/editorconfig@v1,.editorconfig+.gitattributesexistence with atext=normalization directive. - ✅
docs/adr@v1, MADR naming pattern + required## Status,## Context,## Decisionsections. Gap-free numbering deferred (needsnumeric_sequenceprimitive).
v0.6: Agent-era bundled rulesets and output
Section titled “v0.6: Agent-era bundled rulesets and output”Two bundled rulesets aimed at the most common AI-coding
leftovers, plus a new output format for agents consuming alint
inside their own self-correction loops. All work composes from
existing rule kinds, no engine changes, no new primitives:
matching the same shape as the language-ecosystem rulesets
(python@v1, go@v1, ci/github-actions@v1, …) shipped in
earlier cuts. The agent-driven-development moment makes
alint’s existing niche especially valuable; v0.6 ships
ecosystem-specific bundled rulesets to capitalise on that
without changing the underlying tool.
- ✅
alint://bundled/agent-hygiene@v1, backup-suffix bans (*.bak,*.orig,*~,*.swp), versioned-duplicate filename guards (*_v2.ts,*_old.py), scratch-doc bans at root (PLAN.md,NOTES.md,ANALYSIS.md, …),.env-file bans, AI-affirmation regex ("You're absolutely right", emoji watermarks), debug-residue bans (console.log,debugger,breakpoint()), and model-attributed TODO bans (TODO(claude:),TODO(cursor:), …). All composable fromfile_absent/filename_regex/file_content_forbidden. - ✅
alint://bundled/agent-context@v1, hygiene rules forAGENTS.md/CLAUDE.md/.cursorrules: existence recommended, stub guard viafile_min_lines, bloat guard viafile_max_lines(per Augment Code research, context files >300 lines correlate with worse agent performance), stale-path heuristic via regex. Subsumes ctxlint’s niche with no new rule kinds. - ✅
--format=agentJSON output, sibling of--format=jsonshaped for LLM consumption. Each violation carries anagent_instructionfield templated from the rule’smessage+fixblock: a remediation phrasing optimised for an agent to act on, not for a human to read. Closes the “agents already consume our JSON, but the SARIF shape is awkward in their context” feedback gap.
Out-of-scope for v0.6 (deliberately): new rule kinds, semantic analysis, secret-entropy scanning, AGENTS.md export. All of those land in v0.7 or later.
v0.7: New rule kinds for agentic problems (shipped)
Section titled “v0.7: New rule kinds for agentic problems (shipped)”Targeted rule-kind additions that close the gaps Tier-1 exposed. Each got a short design doc before implementation because heuristic detection has a real false-positive surface that bundled rulesets don’t.
Per-feature design drafts live under
docs/design/v0.7/, each settled schema,
semantics, false-positive surface, implementation notes, and
open questions before code started, then was flipped to
Status: Implemented on the matching feature commit.
- ✅
markdown_paths_resolverule kind (v0.7.1): validates that backticked paths in markdown files resolve to real files. Targets the AGENTS.md staleness problem more precisely than the v0.6 regex heuristic. Requiredprefixes:field eliminates the “is this a path or a word” question by construction. - ✅
commented_out_coderule kind (v0.7.2), heuristic detector for blocks of commented-out source code, scored on punctuation density rather than identifier-token density (English prose has identifier-shaped words too). Severity floorwarning, heuristics have non-zero FP rate. - ✅
git_blame_agerule kind (v0.7.3), fire on lines matching a regex whosegit blameauthor-time exceedsmax_age_days. Closes the gap betweenlevel: warningon every TODO (too noisy) andlevel: off(accepts unbounded debt accumulation). Engine plumbing introduces a sharedBlameCacheand the{{ctx.match}}message placeholder. - ✅
alint suggestsubcommand (v0.7.4), scans the current repo for known antipatterns and proposes rules that would catch them. Three suggester families ship: bundled-ruleset (high confidence), antipattern (medium: agent-hygiene leftovers), stale-TODO (medium, eats the v0.7.3 dogfood). Three output formats (human / yaml / json) and a strict stdout-vs-stderr split for slow operations:--progress=auto|always|nevercontrols animated bars on stderr,-q/--quietsilences both. - ✅
alint export-agents-mdsubcommand (v0.7.5): renders the active rule set as anAGENTS.mddirective block. Inline mode splices between<!-- alint:start -->/<!-- alint:end -->markers; re-runs are byte-identical (no mtime bump on round-trip). Closes the “67% of teams maintain duplicate configs between AGENTS.md and CI lint” gap by making alint the single source of truth.
v0.8: Comprehensive test + bench foundation (shipped)
Section titled “v0.8: Comprehensive test + bench foundation (shipped)”Five sub-phases (v0.8.2 → v0.8.5) building the
test/bench/rot-prevention foundation that engine
optimization (now v0.9) needs to land safely. Scope agreed
2026-04-28 after a four-agent coverage audit; phases
merged to main 2026-04-28 / 2026-04-29 with full CI
(Linux self-hosted + Coverage 90.57% + Cross-Platform
macOS/Windows + Mutants nightly) green throughout.
v0.8.2: Rule-kind coverage uplift
Section titled “v0.8.2: Rule-kind coverage uplift”Goal: every rule kind has ≥5 unit tests + ≥2 e2e (pass +
fail). Pre-v0.8.2: 34 of 54 rule kinds had 0 unit tests; 4
had 0 e2e (json_schema_passes, git_no_denied_paths,
git_commit_message, command); 3 had only pass-variant
e2e (no_symlinks, executable_bit,
executable_has_shebang).
- ✅ ~155 new unit tests across the 34 under-covered rule kinds (build / options / evaluate fires / evaluate silent / edge cases, the standard quintet).
- ✅ Fail-variant e2e for the 3 pass-only unix-metadata rules.
- ✅ E2E for the 4 zero-e2e rules.
- ✅ Integration tests (under
crates/alint-rules/tests/) for the shell-out rules,git_no_denied_paths,git_commit_message,command(mirrors the v0.7.3git_blame_ageintegration-test pattern).
v0.8.2: Infrastructure-crate coverage
Section titled “v0.8.2: Infrastructure-crate coverage”alint-core’s engine.rs, walker.rs, registry.rs,
report.rs, error.rs, level.rs, scope.rs,
config.rs, rule.rs all had 0 unit tests pre-v0.8.2.
The most important crate had the worst coverage of its own
internals.
- ✅ Unit tests for
walker::walk,Registry::build,Reportaggregation,Scope::matchesedge cases,Engine::run(changed-mode path-scope intersection, fact-eval failure paths),BlameCachethread-safety under contention.config.rsandrule.rscovered during v0.8 housekeeping (2026-04-29). - ✅ alint-dsl edges: extends-chain cycle detection,
diamond inheritance,
extends:filter validation (only:/except:), nested-config path-prefix rewriting,.alint.d/merge order determinism, HTTPS timeout / size-cap enforcement, SRI algorithm-mismatch errors, template-instantiation edge cases. - ✅ Cross-formatter snapshot test:
crates/alint-output/tests/cross_formatter.rs. Same fixed Report rendered through all 8 output formatters with 13 invariant tests. Catches silent formatter divergence; SARIF + agent + JSON-schema validation bundled in.
v0.8.3: CLI surface coverage
Section titled “v0.8.3: CLI surface coverage”trycmd 33 → 56 cases (pre-v0.8.3 had 27 happy-path; only 2 stderr snapshots).
- ✅ Stderr snapshots for every error path:
--changedon non-git,--base invalid-ref, malformed YAML, unknown rule kind,--fail-on-warningexit-code verification,export-agents-md --inlinemalformed markers. - ✅ Per-subcommand
--helpsnapshot tests for all 9 subcommands. - ✅
--color auto×NO_COLOR×CLICOLOR_FORCEmatrix (5 cases). Surfaced + fixed a real bug: CLICOLOR_FORCE wasn’t honored under--color=auto. - ✅
--progress=auto|always|never× TTY/non-TTY matrix via trycmd env vars +portable-ptyintegration test for the actual TTY branch.
v0.8.4: Benchmark uplift
Section titled “v0.8.4: Benchmark uplift”Pre-v0.8.4: 6 of ~50 rule kinds had isolated criterion benches. 0 output formatters benched. 0 fix-throughput benches.
- ✅
single_file_rules.rs, every per-file rule kind, parameterised over file size and tree size. - ✅
cross_file_rules.rs,pair,for_each_dir,every_matching_has,unique_by,dir_contains,dir_only_containsat varying tree shapes. - ✅
structured_query.rs, JSON / YAML / TOML parse + path-query throughput;json_schema_passesvalidation. - ✅
output_formats.rs, 1k / 10k / 100k violation Reports rendered through all 8 formatters. - ✅
fix_throughput.rs, every fix-op type on synthetic violation lists. - ✅
blame_cache.rs, cold/warm/miss-rate characterisation. - ✅
dsl_extends.rs, extends-chain depth + drop-in merge cost. - ✅ Two new hyperfine scenarios: S4
agent-hygiene, S5fix-pass. Walker parallelism baseline captured for v0.9’sbuild_parallelswitch.
v0.8.5: Regression-guard + rot-prevention
Section titled “v0.8.5: Regression-guard + rot-prevention”Closes the v0.8 cut. Makes test/bench rot mechanically impossible to ship.
- ✅
xtask bench-compare, diffs twotarget/criterion/trees; fails when any scenario regresses past--threshold(default ±10%). PR-time gate-ready. - ✅ Baseline against v0.7.0 captured at
docs/benchmarks/micro/results/linux-x86_64/v0.7.0/so v0.9 engine work has a documented floor. - ✅ Fixture-completeness test:
alint-dsl/tests/schema.rs::fixture_covers_every_registered_rule_kindasserts every registered kind appears inall_kinds.yaml(now 70 kinds, up from 18). - ✅ Scenario-coverage audit test:
crates/alint-e2e/tests/coverage_audit.rs. - ✅ Default-option snapshot test:
crates/alint-dsl/tests/default_options_snapshot.rswith elide rules for crate-internal Debug churn. - ✅ CLI flag inventory snapshot per subcommand:
crates/alint/tests/cli_flag_inventory.rs(separate from--helptext snapshots). - ✅ JSON report schemas at
schemas/v1/{check-report,fix-report}.json+ cross- formatter validation tests. - ✅
cargo llvm-covinstrumentation:.github/workflows/coverage.ymlenforces 85% line coverage floor (90.57% achieved).xtaskexcluded (dev tooling, structurally low coverage). Codecov upload opt-in viaCODECOV_TOKEN. - ✅
cargo mutantsnightly:.github/workflows/mutants.ymlrotates one crate per night. - ✅ Cross-platform CI:
.github/workflows/cross-platform.ymlrunscargo test --workspace --lockedon macOS-arm64 + Windows-x86_64. Caught two real production bugs not surfaced by the Linux lane: a glob mixed-separator bug in the DSL nested-config prefix handling and a bench-compare key separator bug, both fixed before merge.
Out of scope (deferred to v0.9 engine cut)
Section titled “Out of scope (deferred to v0.9 engine cut)”- Per-file-rule dispatch flip (engine restructure).
- Parallel walker (
WalkBuilder::build_parallel). - Memory-footprint pass (Cow / lazy file content / dhat profile).
All three were originally v0.8 sub-themes; they shift to v0.9 because the v0.8 test/bench foundation is the gate that lets engine work land without regressing user-visible behaviour.
v0.9: Engine optimization
Section titled “v0.9: Engine optimization”(Was v0.8 sub-themes 2-4 in the pre-2026-04-28 plan;
displaced by the v0.8 test/bench foundation. Per-feature
design drafts live under
docs/design/v0.9/, same shape as the v0.7
design pass.)
- ✅ Parallel walker (v0.9.1, 2026-04-30), replaces
WalkBuilder::build()withbuild_parallel()driving a per-threadParallelVisitorthat accumulatesFileEntrys in a thread-localVecand merges viaDrop. A deterministicsort_unstable_bypost-sort restores the byte-identical output snapshot tests + formatters depend on. Walker bench: -64% at 10k files, -41% at 1k files, +61% at 100 files (1ms thread-spawn overhead, accepted trade per design doc). - ✅ Memory-footprint pass, type-level only (v0.9.2,
2026-04-30).
Arc<Path>onFileEntry::path/Violation::path,Arc<str>onRuleResult::rule_id/policy_url,Cow<'static, str>onViolation::message. Per-violation path / id clones become atomic refcount bumps. Byte-slice scanning + bounded prefix/suffix reads bundled into v0.9.3 alongside the dispatch flip (rule bodies get touched once instead of twice). - ✅ Per-file-rule dispatch flip + 8-rule reference
migration (v0.9.3, 2026-04-30). New
PerFileRuletrait; engine partition; file-major loop reads each matched file once and dispatches to every applicable rule. 6 line-oriented rules (no_trailing_whitespace,final_newline,line_endings,max_consecutive_blank_lines,indent_style,line_max_width) and 2 bounded-read rules (file_starts_with,file_ends_with) migrated; bounded-read helpers (read_prefix_n/read_suffix_n) incrates/alint-rules/src/io.rs. - ✅ Content-rule mechanical migration (v0.9.4,
2026-04-30). 16 more per-file content rules opt into
the dispatch flip:
file_content_matches,file_content_forbidden,file_header,file_footer,file_shebang,file_max_lines,file_min_lines,file_hash,file_is_ascii,file_is_text,no_bom,no_bidi_controls,no_zero_width_chars,no_merge_conflict_markers,markdown_paths_resolve,structured_path(json/yaml/toml_path_*).file_max_size/file_min_sizestay rule-major (metadata-only);json_schema_passesstays rule-major (repo-level error path doesn’t fit per-file dispatch). - v0.8.5’s
bench-comparegate catches any regression from the engine restructure for free.
v0.9.11: git_tracked_only structural fix + held v0.9 follow-ups
Section titled “v0.9.11: git_tracked_only structural fix + held v0.9 follow-ups”The same recurrence-risk shape that produced the v0.9.6 /
v0.9.7 / v0.9.9 silent-no-op scope_filter: bug class
applies to git_tracked_only (declared on 4 existence
rules; an audit test landed in v0.9.10 as a pragmatic
backstop, but the structural fix was held). v0.9.11
chooses the engine-side filtered-FileIndex approach
(Option C in the design comparison) over Scope ownership
(Option A), there’s no per-rule check to forget at all,
and the dir-mode vs file-mode discriminator never
pollutes Scope’s path-predicate model.
- Engine-side filtered FileIndex for git-tracked rules.
Engine::build_filtered_indexmirrors the existing--changedfiltered-index pattern: builds a file-tracked subset (set.contains(path)filter) and a dir-tracked subset (dir_has_tracked_filesfilter) once per run when any rule opts in. Existence rules (file_exists,file_absent,dir_exists,dir_absent) receive the pre-filtered index viapick_ctx; theif self.git_tracked_only && !ctx.is_git_tracked(...)runtime check disappears from each rule’sevaluate. Rule::wants_git_tracked()engine consultation consolidation. Once the filtered index handles the narrowing,wants_git_tracked()can derive from inspecting rules’ specs at engine-construction time rather than per-rule trait override.- No breaking API change (in contrast to v0.9.10): the change is internal to engine + 4 rules.
- Acceptance: S8 macro bench at 100k/full and 1m/full
within ±5 % of v0.9.10 (slight win expected from
amortising the HashSet lookup across multiple rules
opting in);
coverage_audit_git_tracked_only.rsaudit retained as backstop.
v0.9.12 - v0.9.20 (shipped)
Section titled “v0.9.12 - v0.9.20 (shipped)”Nine patch releases between the v0.9.11 structural fix and the launch-ready cut. Headline contents (full per-version detail in CHANGELOG.md):
- v0.9.12 (2026-05-03), backlog cleanup of the explicitly- held v0.9 follow-up items.
- v0.9.13 (2026-05-04), dependency refresh (Dependabot PR drainage).
- v0.9.14 (2026-05-05), CI automation:
bench-record.ymlworkflow now opens PRs with bench results so post-release backfill is mechanical. - v0.9.15 / v0.9.16 (2026-05-06), Config DX hardening:
21-pitfall catalogue with #18 (
respect_gitignore: false) and #19 (literal_is_nestedruntime guard) fixed in the engine; 30 OSS case studies completed; P2b Wave 2 aggregation; operator-polish (alint --versionSHA+date,alint validate-configsubcommand, JSON Schema generation, did-you-mean parse errors, domain-specific error messages, pre-filled crash-report URL on panic). v0.9.16 is the tag-only release that never published due to a clippy gate failure; v0.9.17 is the corrective re-publish. - v0.9.18 (2026-05-08), pre-launch fix wave: 6 bundled-
ruleset refinements (A1-A6: hygiene-no-js-build-outputs
sibling-package.json gate; long-form Apache-2 preamble;
python@v1 test-fixture excludes; cargo-workspace member
parsing; LICENSE.TXT/.md recognition; rust-sources-snake-
case
allow_compiler_namingknob), 3 case-study config fixes (B1-B3: TypeScript pitfall #22, deno defensive|-, tensorflow header rule scope), B4 cross-cutting revalidation pass,dir_absentengine extension (now supportsscope_filter). - v0.9.19 (2026-05-09), width-aware human output wrapping
--no-docsflag; verbose-message tightening; demo refresh.
- v0.9.20 (2026-05-10), width-aware output extended to
every command;
cmd_list/cmd_explain/cmd_facts/cmd_suggestcolor parity withcheck/fix; newstyling_uniformintegration test enforces the contract uniformly going forward; bundled-rule message length audit; em-dash scrub on first-impression marketing surfaces; install-snippet pin sweep (curl + bash now leads everywhere); polyglot CLI demo with 5 auto-fixable seeds;install-snippets-match-workspace-versiondogfood rule; preflight + pre-push hook bundle.
v0.10: Case-study coverage push
Section titled “v0.10: Case-study coverage push”The 8 rule kinds + 2 bundled rulesets the case-study aggregation demand-validated. Splits out from the original v0.10 “LSP” framing because rule-kind coverage is what unblocks early adopters; LSP is developer-experience polish that can ship separately (now v0.11). Order within the cut by demand × adopter surface:
| # | Primitive / ruleset | Demand | Notes |
|---|---|---|---|
| 1 | registry_paths_resolve | 13 | Largest demand surface. Spans rust, clap, cpython, next.js, arrow, pytorch, nodejs/node, NixOS, dotnet, flutter, kubernetes, protobuf, tensorflow. |
| 2 | cross_file_value_equals (incl. value_extractor:) | 12 | Past saturation. istio surfaces the per-file extractor refinement (pitfall #20); dotnet/runtime SDK band coherence raises the count. |
| 3 | ordered_block | 8 | Lines between marker pairs sorted unique under configurable comparator. |
| 4 | generated_file_fresh | 8 | Run a generator, diff against on-disk file. Opt-in framing: alint’s deliberate non-goal is running codegen. |
| 5 | import_gate | 5 | Forbid imports of pattern X in scope Y. k8s prometheus-imports + airflow + go + helm + pytorch. |
| 6 | command_idempotent mode | 5 | --check mode that fails if working tree would change. ruff-format / prettier --check / dprint check / deno fmt --check / eslint --no-fix shape. Promoted from design candidate in the deep-analysis aggregation. |
| 7 | xml_path_matches + xml_path_equals | 2 | Completes the structured-query family (JSON / YAML / TOML / XML). spark + dotnet/runtime ~7,100 manifests. |
| 8 | pair_hash | 3 | Hash of file A appears at offset Y of file B. golang/go FIPS = highest stakes (CMVP submission references the file format). |
| 9 | apache/governance@v1 (bundled ruleset) | 3 | LICENSE + NOTICE + KEYS + RAT discipline. Apache TLPs converge: arrow + spark + airflow on 9 of 12 governance artefacts. v0.9.18 A2 is a prerequisite. |
| 10 | dotnet@v1 (bundled ruleset) | 1 | Single demand source but huge adopter surface (every dotnet/* + every Azure SDK + every microsoft/* .NET project). Depends on xml_path_*. |
Plus design candidates landing opportunistically when a 2nd
demand source materialises: *_path_contains (3 sources today;
resolves pitfall #17), pair_inverse (2 sources), command_per_repo
(1), json_schema_passes config-shape mode (2), *_path_array_iter
(1), multi_doc_mode: knob on yaml_path_* (1; resolves
pitfall #21).
Per-repo coverage tables + per-primitive demand citations live
in examples/README.md
and docs/development/launch-evidence.md.
v0.11: LSP + DSL polish
Section titled “v0.11: LSP + DSL polish”Shipped in v0.11.0 (2026-05-28): the alint lsp language server (the new alint-lsp crate) wired across the VS Code, JetBrains, Zed, Neovim, Sublime, Emacs, and Helix integrations, the git_commit_* commit-validation family, scope_filter.changed_since:, and {{env.X}} interpolation. The detail below was the cut’s plan; has_sibling / has_descendant were deferred to v0.12.
Originally framed as “LSP + developer experience”; renamed to
capture the DSL-level work that lands alongside LSP. The trigger
was v0.9.21’s #26 fix, which added since: to git_commit_message
and surfaced two natural generalisations: the scope-narrowing
pattern wants to extend to other rule kinds, and the env-var
interpolation in since: wants to extend to every string-typed
config field. The per-file dispatch shape from v0.9.3 powers the
per-file-edit re-eval hot path; v0.9.5’s FileIndex::contains_file
makes single-file path-scope re-tests O(1). Plus the v0.11+ ship-
targets the case-study aggregation surfaced for the smaller-demand
long tail.
Note (post-ship): the originally-planned LSP demonstration of the
interpolation system, hover-on-rule rendering the resolved value of
every {{env.X}} site in the user’s config, did not land in v0.11.0.
The shipped hover surfaces a violation’s rule id, severity, message,
and policy_url; the interpolation system shipped as a config-load
feature without the hover demo.
LSP + editor support
Section titled “LSP + editor support”- LSP server (
alint lsp), shipped as thealint-lspcrate built ontower-lsp. Design pass:docs/design/v0.11/lsp_server.md. - VS Code extension bundling the LSP, shipped alongside the JetBrains
and Zed extensions. Design:
docs/design/v0.11/vscode_extension.md. - Tier-2 editor configs (Neovim, Sublime, Emacs) plus Helix and Eclipse, config-only LSP-client integrations, also shipped in v0.11.0. Neovim is upstreamed to nvim-lspconfig; the rest are documented snippets.
Scope generalisation (v0.9.21 #26 follow-up)
Section titled “Scope generalisation (v0.9.21 #26 follow-up)”Design pass: docs/design/v0.11/scope_filter_changed_since.md.
- [shipped v0.11.0]
ScopeFilter.changed_since:predicate. Restricts a per-file rule to files modified in<since>..HEAD. Composable with the existinghas_ancestor(AND semantics) and with the rule’spaths:glob. Equivalent to the existing CLI--changed --base <ref>but expressible per-rule rather than run-wide, so mixed-mode configs (“SPDX-header rule on new files only; filename-case rule always”) become expressible, the first example that’s not expressible today. Reuses v0.9.5’s path- index for cheap per-file gating. - [still deferred after v0.12.0]
has_sibling,has_descendantpredicates (carried over from the original v0.11 plan). v0.9.10’sScope::from_specmakes additions purely additive (no API churn). Not built in v0.11, and not in v0.12.0 either (see that version’s “Deferred from v0.11”). Onlychanged_sinceshipped here. - [shipped v0.11.0]
git_no_denied_pathssince:option. Path-listing analog of v0.9.21’sgit_commit_message.since:. Fires on tracked paths added in<since>..HEAD, not on all currently-tracked paths. Closes the secrets-introduced-in-PR gap cleanly. Conceptually a sibling toscope_filter.changed_since:, kept as a rule-level option because the rule’s semantics already enumerate paths from git rather than walking the tree.
Commit-validation rule family (v0.9.21 #26 follow-up)
Section titled “Commit-validation rule family (v0.9.21 #26 follow-up)”Design pass: docs/design/v0.11/commit_validation_rules.md.
Four new rule kinds that share v0.9.21’s git_commit_message
shape (since:, include_merges:, env-var interpolation,
per-commit violations with abbreviated SHAs). Designed as a
family so the shared infrastructure cost amortises across all
four. All four shipped in v0.11.0.
git_commit_signed_off, DCO-styleSigned-off-by:trailer in commit footer. Maps to the kernel / Linux Foundation contribution convention; demand surface includes every CNCF / LF-hosted project that requires DCO.git_commit_no_fixup, fail on residualfixup!/squash!/amend!commits left after rebase. Catches the “I forgot to rebase before push” PR shape.git_commit_author_allowlist, author email or name matches a pattern. Use cases: enforce committer identity against an org domain, exclude bot accounts, gate against unverified contributors.git_commit_gpg_signed,git verify-commitsucceeds on every commit in range. Sister togit_commit_signed_offfor projects requiring signature attestation.
All four ship with since: from day one. No HEAD-only-then-
retrofit shape; the issue-26 work made the shared infrastructure
cheap to reuse.
Variable expansion across the DSL (v0.9.21 #26 follow-up)
Section titled “Variable expansion across the DSL (v0.9.21 #26 follow-up)”Design pass: docs/design/v0.11/variable_interpolation.md.
- [shipped v0.11.0]
{{env.X}}interpolation at config-load time across every string-typed value field:extends:URLs,paths:,pattern:,policy_url:,since:,changed_since:,content:,content_from:, and thevars:value side. Type-like and identifier-like fields (id:,kind:,level:) are skipped by design, env-driven rule IDs would break audit trails. - [shipped v0.11.0]
| default(...)filter for fallbacks. Jinja-conventional; future filter additions cost nothing (| upper,| lower). Example:since: "{{env.ALINT_BASE_SHA | default('origin/main')}}". - [shipped v0.11.0]
env.Xin thewhen:expression language as a third namespace alongsidevars.Xandfacts.X. Symmetric with how vars/facts surface today. - [shipped v0.11.0, deprecation warning]
${VAR}ingit_commit_message.since:is deprecated. Emits a load-time warning recommending{{env.X}}; remove the legacy path in v1.0 (clean break at the version-stability gate). - [not shipped] the planned LSP × DSL hover (render the resolved
value of every
{{env.X}}site on hover) did not land; v0.11.0’s hover surfaces a violation’s rule id, severity, message, andpolicy_url.
Long-tail rule kinds (opportunistic)
Section titled “Long-tail rule kinds (opportunistic)”Carried over unchanged from the original v0.11 plan; not gating the release. Anything not picked up opportunistically here rolls into the v0.12 gap-closing cut (see below), where the 100+ repo study refreshes the demand ranking. None were picked up in v0.11; all carried into the v0.12 cut (per-item status below).
- [expressible in v0.12.0 via
cross_fileset_equals]cross_language_implementation_complete(5 sources: arrow, tensorflow, protobuf, angular, flutter). Densest demand: protobuf’s 10 in-tree language bindings + 1 spun-out (~45 cross-language assertions one rule would express). Three distinct topologies (data-format-driven, within- language source ↔ golden, platform-driven). - [still open] Bazel-licensing-declaration-aware rule kind (1 source:
tensorflow’s
licenses(["notice"])BUILD-file discipline). Single-source but the source is a 100k+ file tree with high alignment cost. - [still deferred, see v0.12 “Deferred from v0.11”]
walk_error_policy:engine knob (1 source: pnpm’stests/fixtures/has-broken-symlinks/).strict/skip-broken-symlinks/permissivemodes.
v0.12: Real-world coverage expansion (100+ repos) + gap rule kinds
Section titled “v0.12: Real-world coverage expansion (100+ repos) + gap rule kinds”v0.12.0 shipped (2026-06-07) a large first cut of this scope. New rule kinds that landed: file_graph (file-dependency-graph firewalls plus cycle / orphan / dangling-edge checks, the top demand-ranked kind of the 111-repo study), for_each_match (a per-line predicate quantifier), cross_file (a unifying value-relation kind that subsumes cross_file_value_equals and adds subset / superset / set_equals / identical / resolves), git_commit_subject_matches, changeset_requires_path, and pair_changed_together. Also shipped: generated_file_fresh’s in-place outputs: mode, markerless ordered_block, the php@v1 bundled ruleset (21 → 22), JSONC-tolerant structured parsing, the cross_file normalize: promotion, and import_gate presets for Scala / Java / Dart / Nix, plus a security cycle that confines every config-declared path to the repo root and fixes a git argument-injection in since: reaching back to v0.9.21. Per-item status is tagged inline below. The 100+ repo study (111 repos done) and the still-open gap kinds continue in the v0.12 line.
The v0.10 case-study coverage push and the post-v0.11 30-repo
re-analysis (see
docs/development/case-study-v011-reanalysis-log.md)
proved a method: clone a real repo, catalogue its bespoke
manual/script/CI validation, express as much as alint can, and record
the residual as either a concrete new-kind candidate or a deliberate
non-goal. The 30-repo pass found no v0.10/v0.11 regressions but
surfaced a ranked backlog of additive gaps. v0.12 scales the study
3-4× (100+ repos, broader ecosystem spread) and closes the
rule-kind gaps the 30-repo pass already identified, folding any
further gaps the wider study turns up into the same cut.
Design index: docs/design/v0.12/.
The 100+ repo case study
Section titled “The 100+ repo case study”Design pass: docs/design/v0.12/case_study_100_repos.md.
- [done in v0.12.0: 111 repos] the corpus the v0.12.0 rule kinds were derived from. As planned: expand from 30 to 100+ OSS repos. The 30-repo corpus skewed toward big-tech monorepos + Rust/Go/JS; the expansion deliberately broadens ecosystem coverage: more Python (django, pandas, scikit-learn, fastapi, poetry), JVM (spring-boot, gradle, kotlin), Ruby (rails), PHP (laravel, symfony), systems (llvm, postgres, redis, sqlite, curl), infra/data (terraform, ansible, grafana, prometheus, dbt), ML (transformers, jax), web (vue, svelte, solid), and a long tail of mid-size single-language libraries, where the bespoke-validation / alint fit is often highest.
- Same per-repo methodology, run through the proven subagent
batch-orchestration pipeline (clone → catalogue → draft config →
parent validates with
validate-config/ integrates / commits per batch). - Outputs: per-repo example configs, an expanded findings log, and a refreshed gap ranking that supersedes the 30-repo synthesis.
- Gate: the study runs first; any new-kind candidates it surfaces beyond the list below are triaged into this same release.
Gap rule kinds (ranked by demand from the 30-repo pass)
Section titled “Gap rule kinds (ranked by demand from the 30-repo pass)”Each ships design-doc-first per the project convention; demand counts name the corpus repos that independently needed the capability.
- [shipped v0.12.0]
git_commit_subject_matches(go, node, nixpkgs), the commit-validation family’s missing subject-shape rule. Reuses the v0.11git_commit_*plumbing (since:, per-commit violations with abbreviated SHAs); the cheapest, clearest single win. Design:git_commit_subject_matches.md. - [both shipped v0.12.0] the diff-aware “must-add” family,
changeset_requires_path(“the diff must add a file matching glob X”: prettier changelog_unreleased, cpython Misc/NEWS.d, pnpm.changeset/) andpair_changed_together(two files must change in one commit: rust rustdoc_json FORMAT_VERSION, turbo/rust release guards). Both build on v0.11’sscope_filter.changed_sincemachinery. Design:changeset_requires_path.md. - [shipped v0.12.0, unified into
cross_fileassubset/superset/set_equals] the value-set membership family, originally proposed asregistry_value_used(every TS diagnostic / react error code referenced ≥1×),cross_file_keys_cover(pnpm catalog ⊆ keys),cross_file_set_equals(rust features ↔ unstable-book, tf v1/v2 goldens).cross_file_value_equalsis 1:1 andpair_hashis digest-only; N-in-1 / set relations are the gap. First verify how farregistry_paths_resolve’s existingorphans/must_contain/exclude_queryalready reaches. Design:value_set_membership.md. - [shipped v0.12.0 on
cross_file, assemver-minorplus composable lists]normalize:value-transform (protobuf4.36-dev↔4.36.0, pnpmpnpm@11.3.0↔11.3.0): strip-prefix / semver-floor transforms so “same value, two forms” stops forcing dual regex pins. Extends the existing trim/lowernormalize:. Design:cross_file_normalize.md. - [partly shipped] richer
import_gate: a default-deny / table-driven allowlist mode (vscodecode-import-patterns) and glob-discovered per-directory rule files (k8s’s 66.import-restrictions= 66 hand-written rules today). The whole-repo layering-firewall this targets shipped instead asfile_graphforbidden_edgesin v0.12.0; theimport_gatedefault-deny / glob-discovery enrichment itself is still open. Design:import_gate_enrichment.md. - [still open] dependency-graph allowlist kind (distinct from
import_gate): acargo metadata/ lockfile-aware permitted-dependency firewall for rustPERMITTED_DEPENDENCIESand go’s transitivedeps_test.goclosure.import_gatereads source text, not the resolved graph, so this is genuinely separate. v0.12.0’sfile_graphcovers the file-reference graph but stays path-based (the resolved package graph is its declared non-goal), so this lockfile-aware dependency firewall is still unbuilt. Design:dependency_graph_allowlist.md. - [partly shipped] niche kinds (1-2 sources each, one design doc):
embedded_checksum(cpython Argument Clinic self-digests), full-filelines:{}equality with diff-on-mismatch (tokio README mirror:pair_hashreports only a digest mismatch),no_case_collisions(tensorflow Windows dup-casing),dir_name_equals_field(turbo crate dir ↔ name),cross_language_implementation_complete(carried from the v0.11 long-tail; arrow/tf/protobuf/angular/flutter parity), and a Bazel-licensing-declaration-aware kind (tensorflowlicenses([...])discipline; also a v0.11 long-tail carryover). Of these, v0.12.0 shipped the full-filelines:{}equality ascross_fileidentical,no_case_collisionsasunique_bycase_insensitive:, and cross-language parity ascross_fileset_equalsover a glob-union source;embedded_checksum,dir_name_equals_field, and the Bazel-licensing kind are still open. Design:niche_rule_kinds.md. - [not shipped;
php@v1shipped instead]nix@v1ecosystem bundle (nixpkgs). v0.12.0 shippedphp@v1(the higher-demand ecosystem that lacked a bundle), taking the bundled count to 22, and gave Nix animport_gatepreset; a dedicatednix@v1bundle is still open.
Deferred from v0.11
Section titled “Deferred from v0.11”Design pass: deferred_from_v011.md.
v0.11-plan items not picked up before the cut, with no home in the case-study gap backlog, collected here so they don’t slip untracked. None gated v0.11; all are additive and slip-tolerant. All three remain open after v0.12.0.
has_sibling/has_descendantscope predicates, the twoScopeFilterpredicates from the v0.11 scope-generalisation plan that did not ship alongsidechanged_since. Purely additive via v0.9.10’sScope::from_spec.walk_error_policy:engine knob (1 source: pnpm broken-symlink fixtures),strict/skip-broken-symlinks/permissivewalker modes. Single-source and touches the walker error path; the 100-repo study (far more filesystem shapes) is the right place to settle the mode set first.- LSP “Add rule to ignore” code action, explicitly deferred in
v0.11’s
lsp_server.md(it edits.alint.yml, a separate design decision). Only relevant if v0.12 reopens LSP work.
Bundled-ruleset + engine tuning
Section titled “Bundled-ruleset + engine tuning”Design pass: asf_bundle_overfire.md.
- [shipped v0.12.0] fix the ASF compliance-bundle over-fire.
compliance/apache-2@v1andapache/governance@v1over-fire on every large Apache/CNCF repo in the corpus, 5 confirmations (airflow, helm, istio, kubernetes, tensorflow). Universal cause: branded/abbreviated headers, generated files (.pbtxt/.pb.go/.gen.go/_pb2.py),third_party/vendored trees, “The X Authors” attribution, no top-level NOTICE. Ship generated-file + third_party excludes and header tolerance in the bundles, with a documented per-rule override recipe for the rest. Every batch of the 30-repo pass independently re-derived the samepaths.excludeworkaround, strong signal the bundle defaults are wrong for real ASF repos. - [shipped v0.12.0]
import_gatepresets for scala / java / dart / nix (generic + explicitimport_patternworks but a preset is cleaner; spark, flutter, nixpkgs). - [shipped v0.12.0] docs:
generated_file_freshis stdout-only. Real codegen mutates files in place, socommand_idempotent --checkis the broadly-applicable form. Make the distinction explicit in the rule reference so users don’t reach for the wrong kind (the dominant pattern across the corpus was the mutating one). v0.12.0 also added the in-placeoutputs:mode togenerated_file_freshfor that mutating pattern, so the kind now covers it directly.
v0.13: Engineering foundations: spec-driven development
Section titled “v0.13: Engineering foundations: spec-driven development”A drift-elimination program run on main after v0.12. Conceived as a foundations
track interleaved with the release cadence, it grew substantial enough to be cut
as the v0.13.0 release, so it owns that slot on the timeline. The premise an
anti-drift linter has to honour itself — alint’s
own claims can’t drift from the tool — now holds end to end. Six workstreams
shipped, each artifact regenerate-and-diff gated:
- Schema from types.
schemas/v1/config.jsonis generated from the RustOptionsstructs (schemars) and gated bygen-schema --check, so a field rename propagates to the schema (and the IDE) automatically. - Generated rule reference. Every rule page’s
## Optionstable is rendered from the type-derived schema (name / type / required / default / constraints), so the published options can’t drift from the engine. facts.jsoncontract. A committed surface-area manifest — version, the six headline counts (rule kinds, families, rulesets, fix ops, output formats, subcommands), and catalogue lists — gated bygen-facts --check. It is the single contract the public site consumes (next).- Architecture as code. The crate dependency graph is extracted from
cargo metadatainto a committed Mermaid diagram (runtime vs dev/build edges) and a hand-modeled Structurizr C4 model is kept honest against the workspace members, both gated bygen-arch --check. - Pragmatic formal methods. proptest properties as an always-on behaviour spec, plus a verified Kani bounded proof of the path-confinement security policy.
- Contract-consuming site. alint.org reads
facts.jsonthrough a typed loader, cross-checks at sync time that the bundled contract agrees with the site’s own manifest, and renders its headline counts and roadmap claims from it — so the public site can’t lag a release either. The site also dogfoods alint’s own house-style rules on its prose, closing the loop from the consuming end.
Recorded in spec-driven-development.md,
ADR-0001,
and the per-workstream design docs. An adversarial multi-reviewer pass after the cut
found and fixed a CI gate bypass, a too-weak Kani proof, and a crate graph that
conflated dev and runtime edges — the value of reviewing your own anti-drift work
adversarially. Wiring the site to the contract then proved the thesis in miniature:
two of the project’s own drift gates — the site’s version-pin consistency check and
alint’s prose-no-em-dash rule, dogfooded on the site’s prose — caught drift that
very change introduced, and it was fixed before the next release.
v0.14: WASM plugins
Section titled “v0.14: WASM plugins”Bumped again to make room for the engineering-foundations program, which took the v0.13.0 cut; otherwise unchanged from the previous post-v0.11 scope.
wasmplugin kind with awasmtimehost, stable WIT interface. Plugins receive their config post-interpolation per the v0.11 variable-expansion work, the host resolves{{env.X}}references before passing the config dict to the guest, so plugins never see (and can’t accidentally exfiltrate) the raw env namespace.- Plugin registry scaffolding with signature verification.
- Bless a few canonical agent-aware semantic plugins (mock-ratio checker, file-similarity / near-dup detector, debug-statement auto-stripper) as documented examples, not bundled, to keep the binary lean.
v1.0: Stability
Section titled “v1.0: Stability”- DSL schema committed; semver on
version: 1. - Plugin ABI committed.
alint-corepublic API frozen; breaking changes follow semver-major.- Documentation site.
- Remove the deprecated
${VAR}interpolation path ingit_commit_message.since:(replaced by{{env.X}}in v0.11). Clean break at the version-stability gate; the deprecation-warning overlap window of one minor release is enough for a feature that shipped four days before its successor.