6 releases
Uses new Rust 2024
| new 0.1.12 | May 15, 2026 |
|---|---|
| 0.1.11 | May 12, 2026 |
#12 in #multi-language
1MB
30K
SLoC
polint
AI-agent-native, shadcn-style linting for rules you own.
polint turns repo-specific engineering instructions into executable lint rules.
AI agents do not always follow prose in CLAUDE.md, AGENTS.md, prompts, or
review comments. polint lets you encode the parts that are actually analyzable.
Think shadcn, but for linting: you own the rule code in your repository; polint brings the scaffolding and infrastructure to create, run, test, and ship it.
polint ships no built-in policy rules. It gives you the SDK, parsers, facts, diagnostics, local rule runner, config, cache, and CI output so your repo can own the rules.
Quick Example
Say your frontend must use design tokens instead of raw colors. A polint rule in your repo can catch the violation and tell the AI agent exactly how to fix it:
That is the point: the rule does not just fail the code. It injects the missing project context back into the agent at the moment it needs to repair the change.
Try It
Install polint:
cargo install polint --locked
Or from GitHub Releases:
curl -sSfL https://raw.githubusercontent.com/emilwareus/polint/main/scripts/install.sh | bash
Run a self-contained example:
git clone https://github.com/emilwareus/polint.git
cd polint/examples/config-denied-literal
polint check --color always --fail-on none
Expected output:
Use It In Your Repo
polint init
polint add-skill
polint new-rule ts no-raw-colors
polint check
polint init creates .polint.toml, .polint/rules/src/, .polint/cache/, .polint/.gitignore (ignoring cache/), and root rust-toolchain.toml when missing (see Minimum Rust version).
polint new-rule <go|ts|js|generic> <name> adds a Rust rule module to your
local rule pack. polint check discovers and runs that rule pack.
Rule packs live in your repo:
.polint.toml
.polint/
rules/
Cargo.toml
src/
main.rs
no_raw_colors.rs
Rules should use the public SDK (polint::sdk::prelude::*) and runner
(polint::runner::run_cli) only. Rule modules use #[polint::rule] functions:
the typed fact-view parameters (StringLiterals<'_>, Imports<'_>,
GoTests<'_>, and similar) are the facts the rule can read, and polint derives
the analysis capabilities from that function signature. Rule functions are plain
sync Rust functions with &mut RuleCtx<'_> first and a RuleResult return.
RuleCtx is for
reporting diagnostics, source paths, rule options, and capability/setup
metadata. The fact reference in docs/facts/ describes the raw and
derived building blocks available to rule authors: functions, reusable metric
signals, imports, branches, Go tests, TS/JS facts, literals, and JSX attributes.
Rule-specific TOML fields that are not one of the common shortcuts are available
through ctx.options().settings.
use polint::sdk::prelude::*;
#[polint::rule(
id = "local/no-raw-colors",
description = "Require design tokens instead of raw color literals.",
severity = "error"
)]
pub(crate) fn no_raw_colors(
ctx: &mut RuleCtx<'_>,
literals: StringLiterals<'_>,
) -> RuleResult {
for literal in literals.iter() {
if literal.value.starts_with('#') {
ctx.report(
Diagnostic::error(
ctx.rule_id(),
ctx.file_path(literal.file),
literal.span.diagnostic_range(),
"Use a design token instead of a raw color literal.",
)
.with_evidence("literal", literal.value.clone()),
);
}
}
Ok(())
}
Profiles are explicit:
polint checkruns every discovered rule.polint check --profile webruns exactly[profiles.web].- Unknown profiles are errors.
- Profile names are arbitrary. There is no default profile.
Cache
polint keeps local, untracked cache data under .polint/cache by default:
.polint/cache/
analysis/ compact parser/fact JSON artifacts
rules-target/ Cargo target dir for repo-local rule hosts
derived/ reserved for future project-level derived facts
--no-cache disables analysis/fact cache reads and writes for that run. It does
not disable the rules-target Cargo cache, because rebuilding the local rule
host on every run is usually wasted work.
Analysis cache keys include the source path and content, rule/options digest,
loaded config, requested capability plan, cache format, and polint version.
If a cache artifact cannot be decoded, polint treats it as a miss and removes
that artifact.
Useful commands:
polint cache status
polint cache status --format json
polint cache prune --max-size-mb 512
polint cache prune --max-age-days 14 --dry-run
polint cache clean --category analysis
polint cache clean
Set POLINT_CACHE_DIR to move the whole cache root, or
POLINT_RULES_TARGET_DIR to move only the repo-local rule-host Cargo target
directory.
Comment Ignores
Use comment ignores for intentional, local suppressions:
// polint-ignore-next-line local/no-raw-colors -- legacy fixture
const color = "#ff00aa";
polint-ignore-line, polint-ignore-next-line, polint-ignore-start /
polint-ignore-end, and top-of-file polint-ignore-file are supported.
Selectors are required and use exact IDs, prefix/*, or *. Ignores suppress
policy diagnostics only; parser, internal, capability, and polint/*
diagnostics still report.
To inspect ignored debt:
polint ignores --stat --filter local/no-raw-colors
See docs/IGNORE-COMMENTS.md. The checked-in comment-ignores example shows one suppressed finding and one visible finding from the same rule.
For quick human scan summaries during normal checks:
polint check --shortstat
polint check --stat
These flags summarize scanned files, diagnostics, and ignore suppression counts for human output. They do not change JSON or SARIF output.
Baselines
Use a baseline when adopting polint in a repository that already has valid
findings. The baseline is always checked in at .polint/baseline.yaml as
compact YAML:
version: 1
baseline:
- "local/backend-context-propagation e337fbb73d44b2b7 backend/app/handler.go"
ignore:
- "local/no-raw-colors 1b7c9a00e493aa21 frontend/Button.tsx"
Each entry is one string:
<rule_id> <fingerprint> <file>
baseline entries are existing debt: they stay visible in human output but do
not fail the process. ignore entries are central accepted exceptions: they are
suppressed from output and failure. Baseline matching uses rule_id + fingerprint and refreshes unambiguous moved paths; ignore matching is
file-specific to avoid suppressing unrelated findings with the same fingerprint.
polint baseline create
polint check --baseline --new-only
polint baseline update
--new-only emits and fails only on diagnostics not covered by the baseline or
central ignore list.
Machine contract (JSON)
Stable JSON reports (polint check --format json) match the schema at
docs/schemas/polint-report-v1.json. Emitters
also set a top-level schema URL when using current polint. Diagnostics are
deduplicated and sorted deterministically; --only-rule and --max-diagnostics
apply after that pipeline for emitted reports. --max-diagnostics does not hide
failures from --fail-on (see docs/AGENT-PLAYBOOK.md).
Minimum Rust version
Rule packs under .polint/rules are normal Rust crates that depend on the polint library. The published crate declares rust-version = "1.95" (MSRV). Cargo refuses to build the rule pack if the active rustc is older, even when the stub uses edition = "2024".
polint initwritesrust-toolchain.tomlat the repository root only when that file does not already exist, aligning the default toolchain with polint’s MSRV sopolint check(which runscargowith--manifest-path .polint/rules/Cargo.toml) succeeds.- If your repo already pins an older toolchain, raise
channelinrust-toolchain.tomlto 1.95 or newer, or run with an override, for example:RUSTUP_TOOLCHAIN=1.95 polint check
When the rules crate fails for this reason, the CLI adds a short note on top of Cargo’s stderr.
Semver: generated Cargo.toml uses polint = "0.1.x" (caret). Patch updates within 0.1 are accepted automatically; a 0.2 release requires updating that dependency line.
This repository pins Rust 1.95 in rust-toolchain.toml for developing polint itself.
Versions
| Item | Where it is defined |
|---|---|
CLI and polint crate version |
Workspace version in the repo root Cargo.toml |
| Published crate | polint on crates.io |
| Minimum supported Rust | rust-version in workspace Cargo.toml |
| Generated rule packs | Rust edition 2024 (polint new-rule template) |
CI
name: polint
on: [push, pull_request]
jobs:
polint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: emilwareus/polint@v1
with:
version: latest
args: check --format github
Rules that request Go symbol/reference facts use the embedded Go sidecar by
default, which needs Go 1.24 or newer on PATH. polint supports monorepos by
inferring Go module roots from discovered files, or from
[languages.go].module_roots in .polint.toml; no repository-root go.mod is
required. In GitHub Actions, add this before polint check when using those
facts:
- uses: actions/setup-go@v6
with:
go-version: "1.24.x"
The action caches .polint/cache by default, including the repo-local
rule-host Cargo target directory at .polint/cache/rules-target. A fully cold
first run can still pay install, build, and analysis costs; repeat runs with the
same relevant inputs should restore those caches. See the
GitHub Action guide for inputs, cache keys, and
pinning options.
More
- Examples
- Agent & CI playbook
- Consumer setup / troubleshooting
- GitHub Action
- Comment ignores
- Metric facts
- Go test facts
- Analysis roadmap
- Release process
License
Dependencies
~30–58MB
~1M SLoC