Linting & Code Quality Tools
This project uses multiple linters to maintain code quality across Ruby, ERB, JavaScript, and SCSS.
Where checks run
Section titled “Where checks run”Deterministic checks are first-party gates; semantic review is local and on demand.
As of May 2026 the per-PR workflow files
.github/workflows/pronto.yml and .github/workflows/yard-lint.yml were
retired in favor of bin/ci (local + PR via
.github/workflows/ci.yml). script/reek_diff
runs Reek over changed Ruby sources and reports only smells anchored to
added lines. RuboCop, ESLint, and Stylelint use the same green-on-master model
through script/quality_diff, which also owns
ShellCheck and Hadolint. Brakeman compares high-confidence results with a
committed debt baseline, and Gitleaks scans only authored added lines. The local
bin/pr-ready flow runs these gates before spending local
subscription capacity on semantic review.
Ownership matrix (source of truth)
Section titled “Ownership matrix (source of truth)”| Check | Hard gate (bin/ci / ci.yml) |
Local semantic review | Why |
|---|---|---|---|
| Reek (diff) | ✅ | — | Project smell ratchet |
| yard-lint (touched files) | ✅ | — | Project .yard-lint.yml is the real gate |
| bundle-audit | ✅ | — | CVE scan of Gemfile.lock; Dependabot covers Ruby and JavaScript alerts |
| Zeitwerk | ✅ (local + CI test boot) | — | The keyed CI test process eager-loads once and checks the loaded graph |
| FA-icon ERB cops | ✅ (pure Ruby; local + ci.yml) |
— | Diff-scoped without booting Rails or reading the master key |
| JavaScript behavior tests | ✅ (node:test; local + ci.yml) |
— | Dependency-free browser-logic regressions |
| Minitest | ✅ (ci-suite.yml self-hosted; local bin/ci) |
— | Behavior |
| DatabaseConsistency | ✅ (once per full run, after the test database is migrated) | — | Curated model/schema mismatch ratchet |
| Changed-line coverage | ✅ (90% executable additions under app/, lib/, script/) |
— | Prevent untested branches without imposing a legacy global target |
| RuboCop / ESLint / Stylelint | ✅ (added lines) | — | Existing language/style debt cannot grow |
| ShellCheck / Hadolint | ✅ (added lines) | — | First-party replacement for CodeRabbit’s shell/container tools |
| Gitleaks | ✅ (authored added lines) | — | Redacted committed-secret detection; replaces duplicate CR Gitleaks/TruffleHog scans |
| Brakeman | ✅ (high-confidence delta) | — | Existing findings live in config/brakeman.baseline.json |
| Semantic AI review | ❌ | ✅ (script/multi_llm_review.sh) |
Local panel (bin/setup --reviewers); skipped when none |
Do not replace the ratchet wrappers with raw repository-wide linter or Brakeman exit statuses: those would block on legacy debt. Keep deterministic checks in the first-party lanes and subscription-authenticated model CLIs local.
| Where | What runs | Gate |
|---|---|---|
| Lefthook pre-commit | Added-line RuboCop/ESLint/Stylelint | Blocks only the staged change |
| Lefthook pre-push | Pushed-file Reek/YARD · directly related Minitest/JavaScript tests · conditional lockfile/workflow checks | Fast local feedback, bounded to files being pushed |
bin/ci (local, on demand) |
All deterministic checks · JavaScript behavior · full Minitest | Full local verification |
.github/workflows/ci.yml (PR + master) |
Consolidated bin/ci --quick (including diff-scoped FA-icon cops) · production webpack build |
Self-hosted first-party check |
.github/workflows/ci-suite.yml (PR + master) |
Two isolated Minitest shards · Zeitwerk during the eager-loaded test boot · DatabaseConsistency once on shard 1 after migrations · aggregate 90% changed-line coverage | Self-hosted Linux check |
bin/pr-ready (local, on demand) |
bin/ci --coverage, then the local semantic-review panel (or skip) |
Human-triggered readiness review; never CI |
design-md-lint.yml |
Custom design-contract validator (when DESIGN.*.md is touched) |
PR check |
yard-docs.yml / docs portal |
Generates + deploys YARD / Starlight docs | On push to master |
Documentation-only pull requests keep the required Full suite check, but
take a successful fast path before runner secrets, coverage preparation,
Docker image builds, databases, or Rails tests are started. The fail-safe
classifier in script/ci_suite_scope permits
only Markdown files, paths under doc/, docs/, docs-site/, and .agents/,
the .claude/skills symlink itself, and skills-lock.json; an empty, unknown,
runtime, test, dependency, database, or workflow change runs the full suite.
Manual workflow dispatches also always request the full suite.
bin/setup installs the committed
lefthook.yml
for the whole clone. Worktrees share the dispatcher. Pre-commit stays fast and staged.
Pre-push passes Lefthook’s pushed-file list to script/pre_push, which runs only
Reek/YARD for those Ruby files, changed or conventionally related tests, and
conditional lockfile/workflow checks. It deliberately omits Zeitwerk, Brakeman,
Webpack, coverage, and the full suites. GitHub remains authoritative for those
broader checks. Lefthook is the sole hook manager; do not layer Overcommit on
top of it. Installation, per-worktree environment refresh, and one-command
bypasses are documented under Local Git hooks
(Lefthook).
Recommended verification flow
Section titled “Recommended verification flow”Verification should be proportional to the change. Developers are expected to use judgment: the goal is useful evidence, not repeating every expensive check for a change that cannot exercise it. GitHub’s required checks remain the merge gate when a local step is intentionally skipped.
| Change | Recommended local verification | Notes |
|---|---|---|
| Ruby behavior, data, migrations, security, concurrency, or broad refactor | Focused tests with COVERAGE=1, then bin/pr-ready |
Runs the full suite, changed-line coverage, and semantic review. Use Linux parity where AGENTS.md requires it. |
| Model/schema contract change (associations, enums, nullability, tables, or implicit ordering) | Run affected tests and script/check_new_migrations.sh when migrations changed, then full bin/pr-ready |
The full runner invokes the curated DatabaseConsistency set after Minitest has migrated the test database. Use RAILS_ENV=test mise exec -- bundle exec database_consistency for a focused reproduction. |
| Non-Ruby code or configuration with meaningful behavior | Focused tests, then bin/pr-ready --quick if the full Ruby suite cannot add confidence |
--quick keeps deterministic checks and semantic review but skips Minitest and changed-line coverage. Run the relevant JavaScript, build, shell, Terraform, or other focused command. |
| Documentation-only change | Relevant formatter, validator, link, or render check; otherwise the normal commit/push hooks are sufficient | A developer may skip bin/pr-ready. GitHub recognizes documentation-only PRs and gives the required full-suite status a fast path. |
| Tiny typo or copy-only correction | Inspect the diff and use the normal commit/push hooks | A developer may skip bin/pr-ready when application behavior cannot change. Run a render or focused UI check if markup or interpolation changed. |
The ordinary sequence for a behavior change is:
implement → focused tests → bin/pr-ready → commit → push/open PR → watch required GitHub checks → merge when greenFor a model/schema contract change, insert the migration harness before
bin/pr-ready. Do not run DatabaseConsistency against an unmigrated database:
implement → focused tests → check_new_migrations (when applicable) → bin/pr-ready (Minitest migrates DB, then DatabaseConsistency runs) → commit → push/open PR → watch checks → merge when greenbin/pr-ready can inspect committed, staged, unstaged, and untracked changes,
so the normal run belongs before the commit. Use bin/pr-ready --pr NUMBER to
review the exact head of an existing PR in an isolated worktree; this is useful
for someone else’s PR or when the pushed head specifically needs revalidation,
not a mandatory second run after a successful local check.
Speeding up or narrowing local checks
Section titled “Speeding up or narrowing local checks”Prefer a narrower command over a blanket bypass:
# Skip the full Minitest suite and changed-line coverage, but retain static,# security, JavaScript, and semantic review.bin/pr-ready --quick
# Run deterministic checks without semantic model review.bin/ci --quick
# Run only the test pipeline, optionally with changed-line coverage.bin/ci --tests-onlybin/ci --tests-only --coverage
# Run one affected test directly.COVERAGE=1 mise exec -- bin/rails test test/path/to/affected_test.rbThe lower-level bin/ci environment overrides are escape hatches for a
deliberately narrowed diagnostic run, not substitutes for relevant
verification:
CI_SKIP_BOOT_CHECKS=1 bin/ci --quick # omit the Rails Zeitwerk bootCI_SKIP_QUALITY=1 bin/ci --tests-only # omit linters and BrakemanCI_SKIP_BUNDLE_AUDIT=1 bin/ci --quick # omit the dependency advisory scanIf a skipped category is relevant to the change, run it separately or let the required GitHub job complete before calling the PR ready. Always say which verification was skipped and why.
Commit and push hooks are intentionally localized and usually faster than
bin/pr-ready. Let them run by default. Developers can bypass Lefthook for one
command when the check is irrelevant; see
Bypass a hook once. Do not bypass a
hook to hide a failure. Fix the failure, or record why the check is irrelevant
and ensure the equivalent GitHub check is green.
bin/ci — the local runner
Section titled “bin/ci — the local runner”A single entrypoint that runs every static check, the JavaScript behavior tests, and the full Minitest suite in order, then reports a final pass/fail summary. ~6-8 minutes for a typical change (linters and JavaScript tests fast, Minitest ~5 min).
bin/ci # full pipeline (run locally before opening a PR)bin/ci --quick # skip Minitest; still run fast JavaScript behavior testsbin/ci --tests-only # skip linters; run JavaScript behavior tests + Minitestbin/ci --coverage # full pipeline + exact 90% changed-line coverage gatebin/ci --since=HEAD~1 # tighten the diff window for diff-only checksbin/ci --help # full option referencebin/setup --reviewers # pick this machine's default panel (or none)bin/pr-ready # full CI + diff coverage, then the local panelbin/pr-ready --quick --deep # quick deterministic pass, then all four modelsbin/pr-ready --only none # deterministic checks only, this runbin/pr-ready --timeout 300 # bound each provider invocation to five minutes
# Reproduce one Linux PR shard, including its isolated pg + Redis stackTEST_SHARD_INDEX=1 TEST_SHARD_TOTAL=2 CI_DOCKER_TESTS=1 bin/ci --tests-onlyHard-gate steps:
- Zeitwerk (
bin/rails zeitwerk:check) — skipped whenCI_SKIP_BOOT_CHECKS=1; the CI test boot performs the equivalent eager-load check - Reek on the diff (
script/reek_diffvsorigin/masterby default) - RuboCop / ESLint / Stylelint / ShellCheck / Hadolint on added lines (
script/quality_diff) - Gitleaks on authored added lines (
script/gitleaks_diff; findings are redacted) - yard-lint on touched Ruby files (
app/**/*.rb,lib/**/*.rb) - FA-icon/ERB cops — pure Ruby and diff-scoped; still runs when
CI_SKIP_BOOT_CHECKS=1 - bundle-audit (Gemfile.lock CVE scan)
- Brakeman high-confidence delta (
script/brakeman_delta) - JavaScript behavior tests (
node --test test/javascript/*.test.js) - Minitest full suite (skipped with
--quick) - Changed-line coverage (
--coverage) — at least 90% of executable Ruby additions underapp/,lib/, andscript/; fullbin/pr-readyenables it
Steps don’t short-circuit — every step runs even if an earlier one failed, so you see the full picture in one run.
The FA-icon/ERB step loads lib/tasks/lint_erb.rake directly with Rake’s
--rakefile option. It does not load the application’s default Rakefile,
initialize Rails, connect to a database, or read the master key.
JavaScript behavior tests use Node’s built-in runner and require no dependency
install. Local runs use the Node version pinned by mise; ci.yml provisions
the matching Node release before invoking bin/ci --quick.
The Minitest step runs on the host — no Docker required. parallelize(with: :processes) is fork-safe on native macOS/arm64 (PGGSSENCMODE=disable in
test/test_helper.rb), and the dev Postgres carries CI’s
max_locks_per_transaction=1024 (docker-compose.yml). The self-hosted GitHub
runner still runs the suite in the Linux CI container (ci-suite.yml) as the
source of truth; to reproduce that container locally, set CI_DOCKER_TESTS=1.
Pull-request test shards
Section titled “Pull-request test shards”When the pull-request scope requires Rails tests, the required Full suite
check runs the non-system Minitest inventory as two stable path-hash shards.
Documentation-only changes retain the fast path described above and start no
Rails tests. Each test file belongs to exactly one shard, and adding a file does
not reshuffle existing files. Both jobs retain four Rails process workers and
run on different snapshot-reset VMs with separately named Compose projects,
PostgreSQL containers, Redis containers, databases, and test worker databases.
No test database or service state is shared between shards or pull requests.
For an ordinary application change, each job bind-mounts the checked-out source
over the trusted snapshot’s heatwave-test:baseline image and skips the image
build. A change to Ruby, gem, JavaScript dependency, Docker, Compose, or
PostgreSQL-image inputs—or a missing or bundle-incompatible baseline—falls back
to isolated run-scoped image builds. This keeps the dependency/runtime trust
boundary while avoiding two duplicate source-copy builds on the pull-request
critical path.
When a shard fails, its job summary includes the exact shard number. Reproduce that same Linux selection locally with:
TEST_SHARD_INDEX=1 TEST_SHARD_TOTAL=2 CI_DOCKER_TESTS=1 bin/ci --tests-onlyUse index 2 for the other half. script/test_shard
owns the deterministic selection; explicit test paths and shard variables are
mutually exclusive so a partial invocation cannot silently omit tests.
Each successful shard uploads its Minitest log. When changed-line coverage
applies, it also uploads its SimpleCov result set; failed shards upload a
separate attempt-scoped diagnostics artifact. Shard 1 owns the required Full suite check and stays on its existing runner until shard 2’s report arrives.
It verifies both statuses, combines their coverage hits, and applies the single
90% changed-line gate without scheduling a third job. This measures the union
of the suite rather than judging either half in isolation. Reports expire after
seven days and contain no database or application secrets. Stable successful
report names let GitHub’s Re-run failed jobs command reuse a successful
sibling report and replace only the rerun job’s report.
The 2026-08-11 allocation baseline was deliberately close on three static proxies for runtime:
| Shard | Files | test declarations |
Bytes |
|---|---|---|---|
| 1 | 840 | 7,441 | 4,689,092 |
| 2 | 811 | 7,327 | 4,708,926 |
Hashing cannot promise equal runtime as tests evolve, so each shard summary
retains Minitest’s Finished in duration and run count. Review the median of
comparable pull-request runs rather than reacting to one noisy job. If one
shard is consistently more than 20% slower across ten full-suite runs, replace
the static proxy with a checked-in timing manifest and deterministic weighted
allocation. Do not add a third shard merely to hide imbalance: that consumes
the pool slot reserved for concurrent pull requests and adds another isolated
database setup.
The repository has pre-existing lint and Brakeman debt, so full scans are not usable as binary gates. The first-party runners ratchet instead:
script/quality_diffreports only findings whose location intersects an added line. Existing findings elsewhere in a touched file do not fail.script/brakeman_deltascans the whole Rails graph and compares only high-confidence warnings withconfig/brakeman.baseline.json. New warnings fail. Fixed warnings also fail with a request to shrink the baseline, so they cannot silently return later.
After fixing an existing Brakeman warning, regenerate the baseline, confirm the diff only removes the intended fingerprint, and re-run the delta:
mise exec -- bundle exec brakeman --quiet --no-pager -w 3 \ --no-exit-on-warn --format json --output /tmp/brakeman.jsonjq '{warnings: .warnings}' /tmp/brakeman.json > config/brakeman.baseline.jsonmise exec -- bundle exec ruby script/brakeman_delta origin/masterA verified Brakeman false positive may be waived in the automatically loaded
config/brakeman.ignore file (mise exec -- bundle exec brakeman -I). Every
waiver needs a reviewable note explaining why the result is safe; true positives
must be fixed rather than moved into either the ignore file or debt baseline.
Changed-line coverage is calculated by the full, non-system Minitest suite.
Only executable added lines in Ruby sources under app/, lib/, and script/
count, and 90% must be covered; the global historical percentage is reported
but does not gate.
The browser system suite runs nightly and on demand in system-tests.yml, on
the same snapshot-reset pool but intentionally outside the PR critical path.
The 2026-08-10 scheduled run completed successfully in about five minutes.
Quick Reference
Section titled “Quick Reference”# Ruby/Railsmise exec -- bundle exec rubocop # Ruby style & best practicesmise exec -- bundle exec rubocop --autocorrect-all # Auto-fix Ruby issuesmise exec -- bundle exec brakeman # Security vulnerabilitiesmise exec -- bundle exec bundler-audit check --update # Dependency vulnerabilitiesmise exec -- bundle exec database_consistency # Model/DB consistency (eager_loads app; needs clean boot)mise exec -- bundle exec ruby script/reek_diff origin/master # Reek on added linesmise exec -- ruby script/deps_impact --out tmp/deps-impact.md # outdated gems/npm + call-site buckets
# ERB Templates (Herb — HTML-aware)mise exec -- yarn lint:erb # Lint views and componentsmise exec -- yarn lint:erb:fix # Auto-fix ERB issuesmise exec -- yarn herb-lint app/views # Lint specific directory
# JavaScriptmise exec -- yarn lint:js # ESLint for JS/JSXmise exec -- yarn eslint . --fix # Auto-fix JS issues
# CSS/SCSSmise exec -- yarn lint:css # Stylelint for SCSSmise exec -- yarn lint:css:fix # Auto-fix SCSS issues
# All JS + CSSmise exec -- yarn lint # Run both JS and CSS linters
# Shell, containers, and secretsmise exec -- shellcheck path/to/script.shmise exec -- hadolint Dockerfilemise exec -- ruby script/gitleaks_diff origin/master | \ mise exec -- gitleaks stdin --no-banner --no-color --redact --verboseThe added-line secret scan fails closed for changed text files over 5 MiB; split or explicitly audit an oversized file instead of silently bypassing it.
Ruby Linters
Section titled “Ruby Linters”Rubocop (Core)
Section titled “Rubocop (Core)”- Config:
.rubocop.yml - Purpose: Ruby style, syntax, and best practices
Plugins included:
| Plugin | Purpose |
|---|---|
rubocop-rails |
Rails-specific cops |
rubocop-performance |
Performance optimizations |
rubocop-minitest |
Minitest best practices |
rubocop-capybara |
Capybara test syntax |
rubocop-factory_bot |
FactoryBot syntax |
rubocop-yard |
YARD documentation |
Brakeman
Section titled “Brakeman”- Purpose: Static security analysis
- Run:
bundle exec brakeman
Bundler Audit
Section titled “Bundler Audit”- Purpose: Check for known vulnerabilities in dependencies
- Run:
bundle exec bundler-audit check --update - Suppressions:
.bundler-audit.ymllists GHSAs we’ve intentionally deferred. Each entry carries an exposure analysis and a re-evaluate date — when you see a new advisory in CI output, either upgrade or add a justified suppression with the same shape; never silence one with a bare GHSA id and no comment.
Reek diff runner
Section titled “Reek diff runner”- Config:
.reek.yml;script/reek_diffowns Git diff filtering. - Purpose: Runs Reek on changed Ruby sources and reports only smells whose reported line intersects an added line between the working tree and the selected base. This preserves the useful part of Pronto without its GitLab, Rugged, Octokit, and runner-plugin dependency tree.
- Why diff-only at all? A full
reek/rubycriticreport surfaces a large pre-existing backlog that nobody is going to fix mid-feature. The runner narrows feedback to things the branch touched. - ⚠️ “Diff-only” is per-line, and method-level smells anchor to the nearest
changed line inside the method. A one-line edit to an already-oversized
method can therefore fail the hard gate on a method you only touched. Options,
in order of preference: extract the lines you touched into a small private
method; move the edit outside the offending method; or, if the smell is
genuinely pre-existing and out of scope, add a narrowly scoped
:reek:comment on the method with a reason. Do not widen.reek.ymlexclusions merely to get green. - The comparison includes committed, staged, unstaged, and untracked Ruby changes, so a local run checks the exact working tree that will be committed.
Local usage:
mise exec -- bundle exec ruby script/reek_diff origin/mastermise exec -- bundle exec ruby script/reek_diff HEAD~3mise exec -- bundle exec reek path/to/file.rbmise exec -- bundle exec rubocop path/to/file.rbmise exec -- bundle exec brakeman --quiet --no-pager -w 3CI: bin/ci --quick (via .github/workflows/ci.yml) invokes the runner.
There is no separate .github/workflows/pronto.yml.
Disabling on a single line: use Reek’s underlying pragma
(# :reek:SmellName). Tune project-wide behavior in .reek.yml.
Dependency impact (script/deps_impact)
Section titled “Dependency impact (script/deps_impact)”Read-only freshness report for direct Gemfile gems and package.json
dependencies (app + docs-site). Each package is bucketed:
| Bucket | Meaning |
|---|---|
| safe | Patch, or a minor with no Heatwave call sites. Constraint already allows the latest. Eligible for one grouped lockfile PR. |
| review | Rails / intl-tel-input / exact-pin patches / minors that we actually call. Own PR + bin/pr-ready --deep. |
| stop | Major, git-sourced, known ceiling (sidekiq, pg_party, parallel, playwright, webpack, sass-loader), or a range that blocks the latest. Task doc only. |
The script does not apply updates, commit, or open a pull request. Dependabot still opens the weekly non-major PRs; use this before a deliberate sweep, or to write the impact brief those PRs otherwise lack.
mise exec -- ruby script/deps_impact --out tmp/deps-impact.mdmise exec -- ruby script/deps_impact --ecosystem gems --jsonAfter applying only the safe set on a worktree: bin/pr-ready, then
gh pr create --draft -F tmp/deps-impact.md.
Database Consistency
Section titled “Database Consistency”- Config:
.database_consistency.yml - Purpose: Curated, default-deny model/schema safety ratchet. The full CI
suite runs
CaseSensitiveUniqueValidationChecker,EnumTypeChecker,ImplicitOrderingChecker,MissingAssociationClassChecker,MissingTableChecker, andPolymorphicAssociationNullabilityCheckeronce, after Minitest has prepared and migrated the test database. A fully enabled run reports thousands of mostly legacy or policy-dependent findings; seedoc/tasks/202608141706_DATABASE_CONSISTENCY_ROLLOUT.mdfor the staged audit. - Autofix:
bundle exec database_consistency -f(review all changes) - Note: Needs a clean boot (
eager_load) and a fully migrated database. Never run unrestricted autofix; scope it to a reviewed checker and inspect every generated model or migration change.
ERB Linting (Herb)
Section titled “ERB Linting (Herb)”- Config:
.herb.yml - Purpose: HTML-aware ERB parser with intelligent linting
- Package:
@herb-tools/linter(npm)
Key features:
- Understands HTML structure and context
- Detects unclosed tags, invalid nesting
- Smart indentation awareness
- VS Code extension: Herb LSP (
marcoroth.herb-lsp)
Run:
mise exec -- yarn lint:erb # Lint views and componentsmise exec -- yarn lint:erb:fix # Auto-fix issuesmise exec -- yarn herb-lint --simple # Quick summary output(The Ruby erb_lint / better_html stack was removed; Herb covers ERB linting for this project.)
JavaScript Linting
Section titled “JavaScript Linting”ESLint
Section titled “ESLint”- Config:
eslint.config.mjs - Purpose: JavaScript/JSX linting
Plugins:
- React
- React Hooks
- JSX Accessibility
- Import
CSS/SCSS Linting
Section titled “CSS/SCSS Linting”Stylelint
Section titled “Stylelint”- Config:
.stylelintrc.json - Purpose: SCSS/CSS linting
Rules focus on:
- Proper SCSS syntax
- Nesting depth limits
- Property ordering (relaxed)
- No vendor prefix warnings
CI/CD Integration
Section titled “CI/CD Integration”Add to your CI pipeline:
# Example GitHub Actions- name: Ruby Linting run: | bundle exec rubocop --parallel bundle exec brakeman --no-pager
- name: Security Audit run: bundle exec bundler-audit check --update
- name: ERB Linting run: yarn lint:erb
- name: JS Linting run: yarn lint:js
- name: CSS Linting run: yarn lint:cssPre-commit Hook (Optional)
Section titled “Pre-commit Hook (Optional)”Add to .husky/pre-commit:
#!/bin/sh. "$(dirname "$0")/_/husky.sh"
# Run quick lints on staged files onlyyarn pretty-quick --stagedbundle exec rubocop --force-exclusion $(git diff --cached --name-only --diff-filter=ACM | grep '\.rb$' | tr '\n' ' ')Disabling Rules
Section titled “Disabling Rules”Rubocop
Section titled “Rubocop”# rubocop:disable Metrics/AbcSizedef complex_method # ...end# rubocop:enable Metrics/AbcSizeESLint
Section titled “ESLint”// eslint-disable-next-line no-consoleconsole.log('debug');Stylelint
Section titled “Stylelint”/* stylelint-disable-next-line selector-class-pattern */.legacyClassName { }