Migrating from Repolinter to alint
Repolinter, the TODO Group's checker for the files an open-source repository should have, was archived on 6 February 2026. Its last release, v0.12.0, came out in May 2025. This guide maps its 19 rule types and its default ruleset to alint, gives you that ruleset as an alint config and lists the checks that need a different tool.
What changes
| Repolinter | alint |
|---|---|
repolinter.json or .yaml, rules as a map | .alint.yml, rules as a list with an id each |
A type and glob options per rule | A kind and paths per rule, with include and exclude lists |
Axioms (linguist, licensee, packagers, contributor-count) and where: | Facts you declare, such as any_file_exists, and when: conditions |
nocase on globs | Case-sensitive globs; list each spelling, as in {README,readme}*, or give each letter a class, as in [Rr][Ee][Aa] |
| Levels error, warning and off | Levels error, warning, info and off; a bundled rule's level can be changed by its id |
| Fixes: create, modify or remove a file | 26 kinds of fix, applied by alint fix |
| Output: console, JSON, Markdown | 8 formats, including SARIF and GitHub annotations |
| Node.js, plus the Ruby gems linguist and licensee for the axioms | One static binary |
Watch the names and levels. The bundled oss-baseline ruleset (15 rules) has README, LICENSE, SECURITY.md and code of conduct checks, but it accepts fewer file names than Repolinter did, warns about README and LICENSE and only informs about the other two. The translated config in the next section checks Repolinter's file names and makes all four errors, as Repolinter's default ruleset does.
oss-baseline also checks for merge-conflict markers and Unicode bidi controls, which Repolinter's default config doesn't.
A config to start from
Two rules from Repolinter's default ruleset, one of them with a where: condition. Beside them, the same two rules in alint: a fact stands in for linguist's language detection.
{
"version": 2,
"axioms": { "linguist": "language" },
"rules": {
"license-file-exists": {
"level": "error",
"rule": {
"type": "file-existence",
"options": { "globsAny": ["LICENSE*", "COPYING*"], "nocase": true }
}
},
"javascript-package-metadata-exists": {
"level": "error",
"where": ["language=javascript"],
"rule": {
"type": "file-existence",
"options": { "globsAny": ["package.json"] }
}
}
}
}version: 1
facts:
- id: has_js
any_file_exists: ["**/*.js" , "**/*.mjs" , "**/*.cjs" , "**/*.jsx" ]
rules:
# nocase: a class for each letter
- id: license-file-exists
kind: file_exists
paths:
- "[Ll][Ii][Cc][Ee][Nn][Ss][Ee]*"
- "[Cc][Oo][Pp][Yy][Ii][Nn][Gg]*"
root_only: true
level: error
- id: javascript-package-metadata-exists
when: facts.has_js
kind: file_exists
paths: package.json
level: errorThe 24 rules the default ruleset turns on, translated, are below and as a file. Delete the rules you didn't use in Repolinter, run alint check and compare the output with your last Repolinter run. There's no converter for a repolinter.json of your own: the two tables below give the alint rule for each Repolinter rule type and default rule.
curl -fsSL -o .alint.yml https://alint.org/downloads/repolinter-default.alint.yml Repolinter's default ruleset as an alint config
# Repolinter's default ruleset (v0.12.0) translated to alint: every rule
# it turns on. Two things work differently. alint has no linguist, so a
# fact that looks for a language's files stands in for where: language=...
# And alint's globs are case-sensitive, where many Repolinter rules set
# nocase: add the other spellings your repository uses, or write a
# character class for each letter as the README, LICENSE and code of
# conduct rules below do.
version: 1
extends:
- alint://bundled/oss-baseline@v1
facts:
- id: has_js
any_file_exists: ["**/*.js" , "**/*.mjs" , "**/*.cjs" , "**/*.jsx" ]
- id: has_ruby
any_file_exists: ["**/*.rb" ]
- id: has_java
any_file_exists: ["**/*.java" ]
- id: has_python
any_file_exists: ["**/*.py" ]
- id: has_objective_c
any_file_exists: ["**/*.m" , "**/*.mm" ]
- id: has_swift
any_file_exists: ["**/*.swift" ]
- id: has_erlang
any_file_exists: ["**/*.erl" , "**/*.hrl" ]
- id: has_elixir
any_file_exists: ["**/*.ex" , "**/*.exs" ]
- id: is_apache_2
file_content_matches:
paths: LICENSE
pattern: 'Apache License,?\s*Version 2'
rules:
# Repolinter's default rules are errors, apart from the source header
# check, which warns. oss-baseline only informs about a security policy,
# so raise it.
- { id: oss-security-policy-exists, level: error }
# oss-baseline's README, LICENSE and code of conduct rules accept a
# fixed list of names. Repolinter accepts any README*, LICENSE*,
# COPYING* or CODE-OF-CONDUCT* file in any case, so these three rules
# use its globs instead, one character class per letter.
- { id: oss-readme-exists, level: off }
- { id: oss-license-exists, level: off }
- { id: oss-code-of-conduct-exists, level: off }
- id: readme-file-exists
kind: file_exists
paths: "[Rr][Ee][Aa][Dd][Mm][Ee]*"
root_only: true
level: error
- id: license-file-exists
kind: file_exists
paths:
- "[Ll][Ii][Cc][Ee][Nn][Ss][Ee]*"
- "[Cc][Oo][Pp][Yy][Ii][Nn][Gg]*"
root_only: true
level: error
- id: code-of-conduct-file-exists
kind: file_exists
paths:
- "[Cc][Oo][Dd][Ee][Oo][Ff][Cc][Oo][Nn][Dd][Uu][Cc][Tt]*"
- "[Cc][Oo][Dd][Ee]-[Oo][Ff]-[Cc][Oo][Nn][Dd][Uu][Cc][Tt]*"
- "[Cc][Oo][Dd][Ee]_[Oo][Ff]_[Cc][Oo][Nn][Dd][Uu][Cc][Tt]*"
- "{docs,.github}/[Cc][Oo][Dd][Ee][Oo][Ff][Cc][Oo][Nn][Dd][Uu][Cc][Tt]*"
- "{docs,.github}/[Cc][Oo][Dd][Ee]-[Oo][Ff]-[Cc][Oo][Nn][Dd][Uu][Cc][Tt]*"
- "{docs,.github}/[Cc][Oo][Dd][Ee]_[Oo][Ff]_[Cc][Oo][Nn][Dd][Uu][Cc][Tt]*"
level: error
- id: contributing-file-exists
kind: file_exists
paths: ["CONTRIB*", "docs/CONTRIB*" , ".github/CONTRIB*" ]
level: error
- id: changelog-file-exists
kind: file_exists
paths: "CHANGELOG*"
level: error
- id: support-file-exists
kind: file_exists
paths: ["SUPPORT*", "docs/SUPPORT*" , ".github/SUPPORT*" ]
level: error
- id: readme-references-license
kind: file_content_matches
paths: "README*"
pattern: '(?i)license'
level: error
- id: binaries-not-present
kind: file_absent
paths:
include: ["**/*.exe" , "**/*.dll" ]
exclude: ["node_modules/**" ]
level: error
- id: test-directory-exists
kind: dir_exists
paths: ["**/test*" , "**/specs" ]
level: error
- id: integrates-with-ci
kind: file_exists
paths:
- ".gitlab-ci.yml"
- ".travis.yml"
- "appveyor.yml"
- ".appveyor.yml"
- "circle.yml"
- ".circleci/config.yml"
- "Jenkinsfile"
- ".drone.yml"
- ".github/workflows/*"
- "azure-pipelines.yml"
level: error
- id: code-of-conduct-file-contains-email
kind: file_content_matches
paths:
- "CODEOFCONDUCT*"
- "CODE-OF-CONDUCT*"
- "CODE_OF_CONDUCT*"
- ".github/CODEOFCONDUCT*"
- ".github/CODE-OF-CONDUCT*"
- ".github/CODE_OF_CONDUCT*"
pattern: '(?i).+@.+\..+'
level: error
- id: source-license-headers-exist
kind: file_header
paths:
include: ["**/*.js" ]
exclude: ["node_modules/**" ]
lines: 5
pattern: '(?is)copyright.*license|license.*copyright'
level: warning
- id: github-issue-template-exists
kind: file_exists
paths: ["ISSUE_TEMPLATE*", ".github/ISSUE_TEMPLATE*" , ".github/ISSUE_TEMPLATE/*" ]
level: error
- id: github-pull-request-template-exists
kind: file_exists
paths:
- "PULL_REQUEST_TEMPLATE*"
- ".github/PULL_REQUEST_TEMPLATE*"
- ".github/PULL_REQUEST_TEMPLATE/*"
- ".github/pull_request_template.md"
- "docs/pull_request_template.md"
- "docs/PULL_REQUEST_TEMPLATE*"
- "pull_request_template.md"
level: error
- id: notice-file-exists
when: facts.is_apache_2
kind: file_exists
paths: "NOTICE*"
level: error
# One rule per language, where Repolinter had where: language=...
- id: javascript-package-metadata-exists
when: facts.has_js
kind: file_exists
paths: package.json
level: error
- id: ruby-package-metadata-exists
when: facts.has_ruby
kind: file_exists
paths: Gemfile
level: error
- id: java-package-metadata-exists
when: facts.has_java
kind: file_exists
paths: ["pom.xml", "build.xml", "build.gradle"]
level: error
- id: python-package-metadata-exists
when: facts.has_python
kind: file_exists
paths: ["setup.py", "requirements.txt"]
level: error
- id: objective-c-package-metadata-exists
when: facts.has_objective_c
kind: file_exists
paths: ["Cartfile", "Podfile", "*.podspec"]
level: error
- id: swift-package-metadata-exists
when: facts.has_swift
kind: file_exists
paths: Package.swift
level: error
- id: erlang-package-metadata-exists
when: facts.has_erlang
kind: file_exists
paths: rebar.config
level: error
- id: elixir-package-metadata-exists
when: facts.has_elixir
kind: file_exists
paths: mix.exs
level: errorTwo things work differently. A fact that looks for a language's files stands in for linguist's language detection, and alint's globs are case-sensitive where many Repolinter rules weren't: the README, LICENSE and code of conduct rules spell out every case, and for the others, add the spellings your repository uses. The 2 rules Repolinter leaves off, licensee detection and the Best Practices badge, aren't in the file.
Rule types
Full means an alint rule checks the same thing, bundled or written by you. Partial means alint checks part of it. None means no rule kind does it; a command rule or another tool can.
19 rule types: 11 full, 3partial, 5 none.
| Repolinter type | Coverage | alint |
|---|---|---|
file-existence | Full | file_exists, or dir_exists for directories |
file-not-exists | Full | file_absent |
directory-existence | Full | dir_exists |
file-contents | Full | file_content_matches |
file-not-contents | Full | file_content_forbidden |
file-starts-with | Full | file_header, one regex per rule |
file-hash | Full | file_hash, SHA-256 only (Repolinter's default algorithm) |
file-hashes-not-exist | None | No rule kind. A command rule can compare sha256sum output with a denylist. |
file-type-exclusion | Full | file_absent with the same globs |
large-file | Full | file_max_size |
json-schema-passes | Full | json_schema_passes, which also reads YAML, TOML, XML, dotenv, INI, HCL and Java properties files. The schema comes from a file. |
file-no-broken-links | None | No rule kind. markdown_paths_resolve checks backticked file paths in Markdown and doesn't follow links. |
apache-notice | Full | file_exists for NOTICE*, or extend compliance/apache-2 |
license-detectable-by-licensee | None | No license detection. A command rule can run licensee. |
best-practices-badge-present | Partial | file_content_matches finds the badge in the README. The minimum-level option calls the badge API, which alint doesn't. |
git-grep-commits | None | No rule kind searches the content of old commits. gitleaks covers secrets. |
git-grep-log | Partial | The commit rules check every message in a range against a pattern that must match. A list of banned words needs a command rule. |
git-list-tree | Partial | git_no_denied_paths checks tracked paths, or the paths a pull request changes. It doesn't search older commits. |
git-working-tree | None | No rule kind. A command rule can run git rev-parse. |
The default ruleset
default.json has 26 rules, and 2 of them are off unless you turn them on. Each row names what replaces it in alint.
26 rules: 24 full, 1partial, 1 none.
| Repolinter rule | Coverage | alint |
|---|---|---|
license-file-exists | Full | A file_exists rule with Repolinter's globs, LICENSE* and COPYING* in any case. oss-baseline's oss-license-exists accepts fewer names. |
readme-file-exists | Full | A file_exists rule for README* in any case. oss-baseline's oss-readme-exists accepts only README.md, README and README.rst. |
contributing-file-exists | Full | A file_exists rule. oss-baseline has no CONTRIBUTING check. |
code-of-conduct-file-exists | Full | A file_exists rule with Repolinter's globs for CODE-OF-CONDUCT* and its spellings. oss-baseline's oss-code-of-conduct-exists accepts only CODE_OF_CONDUCT.md. |
changelog-file-exists | Full | A file_exists rule |
security-file-exists | Full | oss-security-policy-exists, plus oss-security-policy-non-empty |
support-file-exists | Full | A file_exists rule |
readme-references-license | Full | A file_content_matches rule |
binaries-not-present | Full | A file_absent rule. The hygiene/no-tracked-artifacts ruleset covers build output and caches and has no rule for executables. |
test-directory-exists | Full | A dir_exists rule |
integrates-with-ci | Full | A file_exists rule over the CI config paths |
code-of-conduct-file-contains-email | Full | A file_content_matches rule |
source-license-headers-exist | Full | A file_header rule, or the compliance/apache-2 and compliance/reuse rulesets |
github-issue-template-exists | Full | A file_exists rule |
github-pull-request-template-exists | Full | A file_exists rule |
*-package-metadata-existsjavascript, ruby, java, python, objective-c, swift, erlang, elixir | Full | A file_exists rule gated by a fact that looks for the language's files. The node ruleset only checks once some package.json exists. |
license-detectable-by-licenseeoff by default | None | No license detection (see the rule types) |
notice-file-exists | Full | apache-2-notice-file-exists in compliance/apache-2, or a rule gated on the license text |
best-practices-badge-presentoff by default | Partial | A file_content_matches rule for the badge |
Checks that need another tool
alint doesn't make network requests, except to fetch a hash-pinned extends: URL, and its git rules read the tracked tree or a range of commits.
- Broken links
- Repolinter's
file-no-broken-linksrenders Markdown and other markup to HTML, requests every absolute link and checks relative links against the files. alint doesn't make requests. - lychee or markdown-link-check, as its own CI step. markdown_paths_resolve catches a narrower problem: backticked file paths in docs such as AGENTS.md that no longer exist.
- License detection
- alint doesn't identify licenses. If you used
where: license=Apache-2.0, gate rules on a fact that matches the license text, as the translated config does. - The compliance/apache-2 ruleset for Apache projects, or licensee run from a command rule for SPDX detection.
- Best Practices badge level
- A
file_content_matchesrule can require the badge in the README. Checking the level the project reached needs the badge API. - OpenSSF Scorecard reports on the same area.
- Git history
git_no_denied_pathschecks the tracked tree, or withsince:the paths a pull request changed. The commit rules check messages, subjects, sign-offs, GPG signatures, authors and leftoverfixup!commits across a range. None of them searches the content of every past commit.- A
commandrule aroundgit log, or gitleaks for secrets. Repolinter'sgit-working-treecheck becomes acommandrule that runsgit rev-parse. - Hash denylists
file_hashpins the hash a file must have, and pair_hash checks hashes against a checksum file. Neither rejects a list of known hashes, asfile-hashes-not-existdoes.- A
commandrule that runssha256sumand compares the result with your list.
What alint adds
- Rules across files: pairs of files, a required file in every package directory, values that must match between files, and file graphs with layering and cycle checks.
- Values inside config files, queried with JSONPath in JSON, YAML, TOML, XML, dotenv,
.properties, INI and HCL files. See the structured query rules. - 22 bundled rulesets, for Rust, Node, Python, Go, Java, .NET and PHP projects, GitHub Actions, monorepos, license compliance and coding-agent files.
- A baseline:
alint baselinerecords today's findings, and CI fails only on new ones. --changed --base origin/main, which checks only the files a pull request touches, and extensions for VS Code and JetBrains IDEs throughalint lsp. A Zed extension is in the alint repository and isn't in Zed's registry yet.
Speed is on the benchmarks page, measured for each release that changes the linter.
Moving CI over
- Install alint. The installation page lists curl, Homebrew, npm, PyPI, cargo and Docker.
- Write
.alint.ymlfrom the translated config, keeping the rules you used. - Run
alint checkon the same commit as your last Repolinter run and compare the two reports. - Run
alint fix --dry-runto see what alint can fix for you, thenalint fixto apply it. - If the repository has findings you won't fix yet, record them with
alint baseline, commit.alint-baseline.jsonand addbaseline: .alint-baseline.jsonto.alint.yml.alint checkand the Action read the baseline only when the config names it;alint check --baseline FILEalso works. - Add the workflow below, then delete the Repolinter step and its config.
The alint step is pinned too. If you extend ci/github-actions, its gha-pin-actions-to-sha rule asks for a commit SHA on every third-party action, alint's included. Pinned by SHA, the Action installs the binary named in version:; the tag page lists each release's commit.
name: alint
on: [push, pull_request]
permissions:
contents: read
jobs:
alint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: asamarts/alint@c08c68be72e0c3a3a02051bd15f4a7d768fa5cbe # v0.18.2
with:
version: v0.18.2The Action prints each finding as an annotation on the pull request. Its docs cover the inputs and SARIF upload. On GitLab or another CI, run alint check --format gitlab (or --format junit) in a job instead.
Try it
Start from the translated default ruleset, or from one line that brings in the README, license, security policy and code of conduct checks:
version: 1
extends:
- alint://bundled/oss-baseline@v1 The one line keeps oss-baseline's levels: README and LICENSE are warnings, and the security policy and code of conduct only inform. Install alint and run alint check at the root of the repository.
Next: the oss-baseline ruleset, rule by rule; configuration for facts, when: and overrides; the GitHub Action's inputs; and how alint compares with the tools people use next to it.