Migrating from Repolinter to alint
Repolinter
(the TODO Group's tool for OSS-baseline checks) was
archived on 2026-02-06. alint covers Repolinter's
rule catalogue as a strict superset; replacing a typical Repolinter
setup is a one-line extends: plus a handful of rule
renames.
The 60-second migration
If your repolinter.json is a slight variant of
Repolinter's default.json
(LICENSE / README / CONTRIBUTING / SECURITY / CODE_OF_CONDUCT /
.gitignore presence + merge-marker / bidi-control checks), replace
your entire configuration with this:
# .alint.yml
version: 1
extends:
- alint://bundled/oss-baseline@v1
That's the migration. Run alint check, compare the
output to your last Repolinter run, and use the
mapping table below for any custom
rules.
Repolinter axiom → alint primitive
| Repolinter axiom | alint replacement |
|---|---|
linguist / packagers (language detection via Ruby gem) | Built-in facts: has_rust, has_node, has_python, has_go, has_java. No external binary. |
licensee (SPDX license identification via Ruby gem) | Extend alint://bundled/compliance/apache-2@v1 or compliance/reuse@v1. For arbitrary SPDX detection, fall back to a command: rule shelling to licensee. |
where: axiom-condition gating | when: bounded expression language with facts and vars; bundled rulesets are auto-gated by ecosystem facts. |
Per-rule JSONPath via jsonpath-plus | Full RFC 9535 JSONPath against JSON, YAML, and TOML. |
| Custom rule kinds via JS plugin | 60 bundled rule kinds + composition via extends: (local files, HTTPS+SRI, alint://bundled/…). |
Rule mapping
Coverage labels: Full = direct equivalent; Partial = covers the common case, some option may not port; None = no equivalent today, workaround documented below.
Repolinter rule kinds (the type: field)
| Repolinter kind | alint equivalent | Coverage |
|---|---|---|
file-existence | file_exists (with paths: array) | Full |
file-not-exists | file_absent | Full |
directory-existence | dir_exists | Full |
file-contents | file_content_matches | Full |
file-not-contents | file_content_forbidden | Full |
file-starts-with | file_header (line-oriented) / file_starts_with (byte-prefix) | Full |
file-hash | file_hash | Full |
file-hashes-not-exist | No direct equivalent; use file_content_forbidden against bad-substring, or shell to sha256sum via command: until file_hash_not lands. | Partial |
file-type-exclusion | file_absent with the same glob list | Full |
large-file | file_max_size | Full |
json-schema-passes | json_schema_passes; also validates YAML and TOML | Full (superset) |
file-no-broken-links | markdown_paths_resolve for filesystem-relative paths. Does not make HTTP requests for absolute URLs, by design. | Partial |
apache-notice | file_exists for NOTICE, or use compliance/apache-2@v1 for the full bundle. | Full (superset) |
license-detectable-by-licensee | No equivalent. Use compliance/apache-2@v1 / compliance/reuse@v1 for shape checks; command: rule for full SPDX detection. | None (workaround) |
best-practices-badge-present | No equivalent; alint doesn't make HTTP requests. Run OpenSSF Scorecard as a separate CI job. | None |
git-grep-commits | git_commit_message validates HEAD only; history grep needs a command: rule. | Partial |
git-grep-log | Same as above. | Partial |
git-list-tree | git_no_denied_paths + git_tracked_only: per-rule field cover the common shapes. | Partial |
git-working-tree | No direct equivalent; mostly subsumed by alint's walker honouring .gitignore + git_tracked_only:. | Partial |
Repolinter rule names (entries in default.json)
| Repolinter name | alint equivalent | Coverage |
|---|---|---|
license-file-exists | oss-license-exists in oss-baseline@v1 | Full |
readme-file-exists | oss-readme-exists | Full |
contributing-file-exists | One-liner file_exists over CONTRIBUTING* paths | Full (manual) |
code-of-conduct-file-exists | oss-code-of-conduct-exists | Full |
changelog-file-exists | One-liner file_exists over CHANGELOG* | Full (manual) |
security-file-exists | oss-security-policy-exists + oss-security-policy-non-empty (mirrors Scorecard's non-stub check) | Full (superset) |
support-file-exists | One-liner file_exists over SUPPORT* | Full (manual) |
readme-references-license | file_content_matches over README* with (?i)license | Full (manual) |
binaries-not-present | file_absent, or extend hygiene/no-tracked-artifacts@v1 | Full |
test-directory-exists | dir_exists with the same paths | Full |
integrates-with-ci | file_exists, or ci/github-actions@v1 for in-depth GitHub Actions checks | Full (superset) |
code-of-conduct-file-contains-email | file_content_matches with .+@.+\..+ | Full (manual) |
source-license-headers-exist | file_header with lines: 5; for SPDX, use compliance/reuse@v1 | Full |
github-issue-template-exists | file_exists / dir_exists | Full |
github-pull-request-template-exists | file_exists | Full |
javascript-package-metadata-exists | node-package-json-exists in node@v1, auto-gated by has_node | Full (cleaner) |
java-package-metadata-exists | java-manifest-exists in java@v1, auto-gated by has_java | Full |
python-package-metadata-exists | python-manifest-exists in python@v1, adds pyproject.toml (PEP 517) | Full (superset) |
ruby-package-metadata-exists | One-liner file_exists over Gemfile; no has_ruby fact today | Partial (workaround) |
objective-c / swift / erlang / elixir-package-metadata-exists | One-liner file_exists per language; no bundled rulesets yet | Partial (workaround) |
license-detectable-by-licensee | See rule-kind table above | None (workaround) |
notice-file-exists | apache-2-notice-file-exists in compliance/apache-2@v1 | Full (cleaner) |
best-practices-badge-present | See rule-kind table; keep as a Scorecard CI step | None |
Mapping totals (24 entries): 17 full / 14 partial-or-manual-but-trivial / 3 with no clean equivalent + documented workaround. Core OSS-baseline coverage is 100%.
Side-by-side: a real Repolinter config
A representative repolinter.json distilled from
Repolinter's default.json:
{
"$schema": "https://raw.githubusercontent.com/todogroup/repolinter/master/rulesets/schema.json",
"version": 2,
"axioms": {
"linguist": "language",
"licensee": "license",
"packagers": "packager"
},
"rules": {
"license-file-exists": {
"level": "error",
"rule": {
"type": "file-existence",
"options": { "globsAny": ["LICENSE*", "COPYING*"], "nocase": true }
}
},
"readme-file-exists": {
"level": "error",
"rule": {
"type": "file-existence",
"options": { "globsAny": ["README*"], "nocase": true }
}
},
"contributing-file-exists": {
"level": "error",
"rule": {
"type": "file-existence",
"options": { "globsAny": ["{docs/,.github/,}CONTRIB*"], "nocase": true }
}
},
"security-file-exists": {
"level": "error",
"rule": {
"type": "file-existence",
"options": { "globsAny": ["{docs/,.github/,}SECURITY.md"] }
}
},
"code-of-conduct-file-exists": {
"level": "error",
"rule": {
"type": "file-existence",
"options": { "globsAny": ["{docs/,.github/,}CODE_OF_CONDUCT*"], "nocase": true }
}
},
"readme-references-license": {
"level": "error",
"rule": {
"type": "file-contents",
"options": { "globsAll": ["README*"], "content": "license", "flags": "i" }
}
},
"binaries-not-present": {
"level": "error",
"rule": {
"type": "file-type-exclusion",
"options": { "type": ["**/*.exe", "**/*.dll", "!node_modules/**"] }
}
},
"javascript-package-metadata-exists": {
"level": "error",
"where": ["language=javascript"],
"rule": {
"type": "file-existence",
"options": { "globsAny": ["package.json"] }
}
}
}
} The equivalent .alint.yml:
version: 1
extends:
# Covers license / readme / security / code-of-conduct, plus
# non-stub variants and merge-conflict + bidi-control checks.
- alint://bundled/oss-baseline@v1
# Covers javascript-package-metadata-exists (as node-package-json-exists),
# auto-gated by facts.has_node — no axiom configuration needed.
- alint://bundled/node@v1
rules:
- id: contributing-file-exists
kind: file_exists
paths:
- "CONTRIBUTING.md"
- "CONTRIBUTING"
- ".github/CONTRIBUTING.md"
- "docs/CONTRIBUTING.md"
level: error
message: "Add a CONTRIBUTING document so contributors know how to get started."
- id: readme-references-license
kind: file_content_matches
paths: ["README.md", "README", "README.rst"]
pattern: '(?i)license'
level: error
message: "README should mention the project license."
- id: no-tracked-binaries
kind: file_absent
paths:
include: ["**/*.exe", "**/*.dll"]
exclude: ["node_modules/**", "**/test*/**"]
level: error
message: "Don't commit pre-built binaries." What changed:
- The
axioms:block disappears. Repolinter'slinguistandpackagersbecame built-in facts; no Ruby gems required. - The OSS-baseline rules collapse to one
extends:line, and the bundled version is stricter (catches zero-byte LICENSE files and one-line "TODO" READMEs that Repolinter's existence check passed). - Per-language metadata is one
extends:line (node@v1) that adds the lockfile check and thenode_modules-not-tracked check on top. - Custom rules port directly: same regex / glob shape, one rule entry each.
Edge cases that don't map cleanly
file-no-broken-links HTTP checks
Repolinter's file-no-broken-links checks both
filesystem-relative links and absolute HTTP URLs. alint's
markdown_paths_resolve covers the filesystem half but
not HTTP, by design (network checks are flaky in CI). If you
relied on the HTTP variant, keep a separate CI step using
lychee or markdown-link-check, or shell
out from a command: rule.
license-detectable-by-licensee
alint doesn't try to identify which SPDX license a project is
under; it asserts shape. If you relied on
license=Apache-2.0 gating, extend
alint://bundled/compliance/apache-2@v1, which checks
that LICENSE contains the canonical Apache-2.0 text, that NOTICE
exists, and that source files have the Apache header. For full
SPDX detection across hundreds of licenses, fall back to a
command: rule shelling to licensee
itself (the same Ruby-toolchain dependency Repolinter had,
opt-in).
best-practices-badge-present
alint doesn't make HTTP requests; this is out of scope by design.
Run OpenSSF Scorecard
as a separate CI job; it covers the same surface and many
adjacent ones. alint's oss-baseline@v1 already
mirrors several Scorecard checks (Security-Policy non-stub,
Code-Review via CODEOWNERS) by design; the two compose well.
git-grep-commits / git-grep-log
alint's git_commit_message validates HEAD only,
which is good for per-PR Conventional Commits enforcement under
alint check --changed. Full history grep
("no commit in history contains 'fixup'") is not alint's job;
keep as a command: rule shelling to
git log --grep.
file-hashes-not-exist
No direct equivalent. The common use case (a vendored dependency
must not be the known-vulnerable version) maps cleanly to
file_content_forbidden against a known-bad
substring. Pure hash denylisting needs a command:
rule until a file_hash_not primitive lands
(candidate for v0.10+).
Step-by-step adoption
1. Install alint
# install.sh (Linux + macOS + Windows tarballs)
curl -sSL https://raw.githubusercontent.com/asamarts/alint/main/install.sh | bash
# Homebrew (macOS + Linuxbrew)
brew tap asamarts/alint && brew install alint
# npm (downloads the matching pre-built binary; no JS runtime)
npm install -g @asamarts/alint
# Docker (distroless multi-arch)
docker run --rm -v "$PWD:/repo" ghcr.io/asamarts/alint:latest check 2. Write the config
Start with the bundled OSS baseline and add per-ecosystem
rulesets. Each is a one-line extends:, gated by
facts.has_<ecosystem>, so listing one for an
ecosystem you don't have is a silent no-op:
# .alint.yml
version: 1
extends:
- alint://bundled/oss-baseline@v1
- alint://bundled/rust@v1
- alint://bundled/node@v1
- alint://bundled/python@v1
- alint://bundled/go@v1
- alint://bundled/java@v1 Port any custom Repolinter rules using the mapping table above.
3. Run alint check
Compare the output to your last Repolinter run. Every bundled rule id can be overridden locally:
extends:
- alint://bundled/oss-baseline@v1
rules:
# Elevate missing-README from warning (bundled default) to error.
- id: oss-readme-exists
level: error
# Disable trailing-whitespace on Markdown — the two-trailing-spaces
# hard-break is deliberate.
- id: oss-no-trailing-whitespace
level: off 4. Auto-fix what's mechanically fixable
alint fix --dry-run # preview the diff
alint fix # apply 5. Wire it into CI
For GitHub Actions, the canonical setup is the
asamarts/alint-action:
# .github/workflows/lint.yml
on: [push, pull_request]
permissions:
contents: read
jobs:
alint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: asamarts/alint-action@v1
with:
format: github # native GitHub Actions annotations
For non-GitHub CI, alint check --format <fmt>
covers SARIF (GitHub code-scanning), JUnit, GitLab Code Quality,
and the agent format with per-violation
agent_instruction strings.
6. Retire the Repolinter step
Once alint check passes (or fails for the same
reasons your Repolinter step did), remove the Repolinter step
from your CI and delete repolinter.json. Pin
alint's bundled-ruleset version (@v1) for
zero-surprise upgrades.
If your migration runs into something the mapping table didn't cover, open an issue. The mapping is maintained against real-world configs.