npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@theglitchking/gimme-the-lint

v2.7.0

Published

Polyglot progressive linting AND contract checking for monorepos — guards the whole chain: Postgres ⇄ SQLAlchemy models ⇄ Pydantic schemas → openapi.json → generated frontend types. Adapter-driven baselines, per-app drift detection. ESLint, Biome, Ruff, g

Downloads

253

Readme

gimme-the-lint

npm version License: MIT GitHub Action


Summary

Most projects use linters to catch mistakes and keep code clean. The problem is that when you add a linter to a project that already has a lot of code, the linter finds hundreds — sometimes thousands — of old problems. Turning linting on would block every commit until someone fixes all of it, so teams just never do it. gimme-the-lint solves this by remembering the old problems and only blocking your work when you create a new one. Your team cleans up the old stuff at its own pace; meanwhile no new mess gets in.

v2.0 generalized that idea to any linter and any language — a progressive-lint engine with a pluggable adapter per tool, across JavaScript/TypeScript, Python, Go, Rust, Terraform and Ansible, in any monorepo shape.

v2.6 applies the same bargain to a different question. Not "is this code well-formed?" but "does your data model agree with the schemas that expose it?"

Because that is where the expensive bugs live, and they are all silent:

A user fills in twelve fields. Four are saved. The API returns 201.

Nobody clicks Save on a project and expects every line item to be reset to pending and its notes wiped. The endpoint returned 200.

A column is typed str in the response and JSON in the database. Harmless — until the first correct value is written, and every read 500s.

None of that is a lint error. Nothing is malformed, nothing crashes, and the response says success. gimme-the-lint now catches it before it ships, on the same "only-new-violations-block" terms as everything else — with one exception: a bug that is already breaking production is never grandfathered.


How it works

gimme-the-lint creates baselines — snapshots of every existing violation, stored per app under .gtl/. On each run it lints, then asks one question of every violation: is this new, or was it already baselined? Only new violations block.

The trick is the fingerprint. Each violation is identified by file + rule + message — deliberately not by line number. So a baselined violation survives code moving up or down a file; only a genuinely new problem is ever flagged. (In v1 this job was outsourced to the third-party lint-to-the-future; v2 owns it, which is what makes every linter equal.)

Each app is bound to the linters its package manifest implies — package.json → ESLint, pyproject.toml → Ruff, go.mod → golangci-lint, Cargo.toml → Clippy, biome.json → Biome, ansible.cfg / galaxy.yml → ansible-lint. Terraform has no manifest, so a directory of *.tf / *.tofu files binds to tflint by extension. Drift detection runs per app, so a config or linter-version change in one app never churns the baselines of another.


Features

  • Progressive linting — only new violations block; existing ones are baselined
  • In-house diff engine — line/column-independent fingerprints survive code shifts
  • Pluggable linter adapters — the choice of linter is config, not a hardcode
  • Polyglot — JavaScript/TypeScript, Python, Go, Rust, Terraform, Ansible out of the box
  • Per-app model — auto-discovers every package in a monorepo; no frontend/
    • backend/ assumption
  • Per-app drift detection — app add/remove, config change, linter version, age
  • Best-practice configs shippedinstall seeds each app with a curated, security-aware config for its linter (create-if-absent — never clobbers yours)
  • Security linting built in — gitleaks for secrets across every codebase, plus per-language security rules (gosec, Ruff S, eslint-plugin-security)
  • Idempotent skips — an app with code but no installed linter is warn-skipped (never blocks) — or fails loudly under --strict
  • Offline install — air-gapped mode for regulated environments
  • Greenfield mode — "strict from day one" with empty baselines
  • Git hooks, CLI, GitHub Action, Claude Code plugin — one engine, four front doors
  • LLM-optimized output — failures tell Claude Code to auto-fix without asking

Supported linters

| Language | Linter | Bound by | |----------|--------|----------| | JavaScript / TypeScript | eslint | package.json | | JavaScript / TypeScript | biome | biome.json (supersedes ESLint) | | Python | ruff | pyproject.toml, requirements.txt, setup.py | | Go | golangci-lint | go.mod | | Rust | clippy (cargo clippy) | Cargo.toml | | Terraform / OpenTofu | tflint | *.tf / *.tofu files (no manifest) | | Ansible | ansible-lint | ansible.cfg, galaxy.yml | | SQL migrations | squawk | a migrations/ directory (any language) | | Protobuf | buf, buf-breaking | *.proto files | | OpenAPI / AsyncAPI | spectral | openapi.yaml, asyncapi.yaml |

Contract checks (v2.6)

Beyond "is this code well-formed": does your data model agree with the schemas that expose it?

| Check | Asks | Runs on | |-------|------|---------| | contract | Does every column your model has actually reach a client — and come back? | push | | openapi | Does your published API contract still describe what your code serves? | push | | codegen-drift | Do your frontend's types still match the API they're typed against? | commit | | squawk | Will this migration take a table-locking hold on production? | commit | | buf-breaking | Did this commit break somebody's protobuf client? | push | | alembic-check | Did you change a model and forget to generate a migration? | CI only |

The whole chain, guarded

   Postgres columns
        ⇕   alembic-check
   SQLAlchemy models
        ⇕   17 contract rules
   Pydantic schemas
        ↓   materialize → openapi.json
   openapi.json
        ↓   openapi-typescript → api-types.ts
   frontend types

Drift lives wherever two artifacts must agree. It cannot exist where one is derived. So the bottom two rungs are derived, and the class of bug that lived there is now inexpressible rather than merely tested-for.

The bug that motivated it: a component read prospect.zip; the API returns zip_code. Backend correct, contract check green, lockfile fresh, database row right — and a user saw a blank field for a full release cycle, because undefined renders as nothing, and nothing looks exactly like data that was never saved.

Seventeen contract rules, each one standing on a specific production bug — the incident is recorded with the rule and printed by gtl-contract rules. A rule whose reason is written down is a rule nobody deletes in a hurry.

gimme-the-lint materialize   # write down the API contract FastAPI only computes at runtime
gimme-the-lint verify        # the checks that need a database (CI only — never a git hook)

Debt is grandfathered; defects are not. A missing column on a write schema is debt — baseline it, fix it at your own pace. A response field that 500s every read is a defect: it cannot be baselined, because grandfathering it means writing down "we accept that this endpoint is broken." You can still except it — in config, with a mandatory reason. The friction is the feature.

Start with .documentation/api/contract-guide.md.

Shipped lint configs

install seeds every discovered app with a best-practice ("recommended" tier) config for its linter — created only if absent, so your own config is never overwritten. Each ships a sensible default rule set and a single lever to dial strictness up or down:

| Codebase | Linter | Default rules (recommended tier) | Strictness lever | |----------|--------|----------------------------------|------------------| | JS/TS | ESLint | @eslint/js + React recommended, import-architecture guards, security plugins, Prettier-compatible | rules block in eslint.config.js | | JS/TS | Biome | recommended set + full security group + console/complexity rules | rule levels in biome.json | | Python | Ruff | pyflakes / pycodestyle / isort / bugbear / pyupgrade + S security + comprehensions / simplify | select / ignore in pyproject.toml | | Go | golangci-lint | standard set + correctness & quality linters + gosec | linters.enable in .golangci.yml | | Rust | Clippy | pedantic + cargo at warn, noisy lints allowed back | [lints.clippy] levels in Cargo.toml | | Terraform | tflint | bundled terraform ruleset, recommended preset | preset in .tflint.hcl (recommendedall) | | Ansible | ansible-lint | moderate profile | profile in .ansible-lint (minproduction) | | Secrets (all) | gitleaks | default ruleset + key / password rules — always blocks | [allowlist] in .gitleaks.toml |

Every shipped config carries a security layer. gitleaks scans every file in every codebase for secrets (passwords, SSL/private keys, tokens) and always blocks — secrets are never baselined. Each linter adds language-specific security rules on top (gosec, Ruff S / flake8-bandit, eslint-plugin-security, Biome's security group), which follow normal progressive baselining.

To go stricter, pull the lever in the table above — because violations are progressively baselined, raising strictness never blocks existing code, only new code is held to the higher bar. Full per-codebase detail — every default rule and how to adjust it — is in .documentation/standards/lint-rules-guide.md.


Quick Start

Install

# Local (recommended — every teammate gets it on clone)
npm install --save-dev @theglitchking/gimme-the-lint
npx gimme-the-lint install

# Global
npm install -g @theglitchking/gimme-the-lint
gimme-the-lint install

# Claude Code plugin
/plugin install TheGlitchKing/gimme-the-lint

First-time setup on an existing project

npx gimme-the-lint install      # writes configs + git hooks
npx gimme-the-lint baseline     # captures existing violations as baselines
npx gimme-the-lint dashboard    # see what is baselined and any drift

From here, every commit is linted — but only your new code is held to the rules. Commit the .gtl/ directory so the whole team shares the baseline.

Day to day

The pre-commit hook fires on git commit. If you introduced a new violation it blocks the commit and shows exactly what to fix:

gimme-the-lint check --fix      # auto-fix what the linter can
git add -A && git commit -m "…" # retry

CLI

| Command | Description | |---------|-------------| | gimme-the-lint install | Write configs and set up the project | | gimme-the-lint install --offline | Air-gapped install — no npm/pip fetches | | gimme-the-lint install --no-baseline | Greenfield — empty baselines, strict from day one | | gimme-the-lint baseline | Capture/refresh baselines for every app | | gimme-the-lint baseline --empty | Write empty baselines (greenfield) | | gimme-the-lint check | Lint files staged for commit | | gimme-the-lint check --all | Lint every app, not just staged changes | | gimme-the-lint check --fix | Auto-fix where the linter supports it | | gimme-the-lint check --strict | Fail if a linter is missing for present code | | gimme-the-lint check --stage=push | Also run the slower whole-app checks (the contract engine) | | gimme-the-lint materialize | Write down the API contract your code computes at runtime | | gimme-the-lint verify | Run the checks that need a database (CI only — never a git hook) | | gimme-the-lint dashboard | Per-app baseline status + drift | | gimme-the-lint migrate | Migrate a v1 (.lttf) project to the v2 .gtl/ layout | | gimme-the-lint hooks | Install pre-commit and pre-push git hooks | | gimme-the-lint status | Overall plugin status | | gimme-the-lint uninstall | Remove hooks and config |

Upgrading from v2.5? Re-run gimme-the-lint hooks, or the new checks silently never fire. See .documentation/procedures/upgrade-guide.md — it carries the full error catalog.


Configuration

Zero config is the default — apps and their linters are auto-detected. To override, add a config file. The canonical location is .gtl/config.js (it travels with the committed .gtl/ baselines); a repo-root gimme-the-lint.config.js is also read, for back-compatibility. install and migrate write new configs to .gtl/:

module.exports = {
  // Explicit per-app linter binding (omit `apps` entirely to auto-detect).
  apps: {
    'apps/orders-api':    { linters: ['eslint'] },
    'apps/orders-worker': { linters: ['ruff'] },
    'apps/billing-events':{ linters: ['golangci-lint'] },
    'apps/audit-stream':  { linters: ['clippy'] },
  },
  // Directories to skip (template/scaffold dirs are skipped by convention).
  skipPatterns: ['_template-*', '__template__'],
};

Polyglot monorepos

A modern monorepo is not one frontend and one backend:

apps/
├── orders-api/        package.json   → eslint
├── orders-worker/     pyproject.toml → ruff
├── billing-events/    go.mod         → golangci-lint
└── audit-stream/      Cargo.toml     → clippy

gimme-the-lint baseline discovers all four, binds each to its linter, and writes .gtl/apps/<app>/baseline.json per app. Workspace files (pnpm-workspace.yaml, nx.json, lerna.json) need no special handling — each package carries its own manifest, so discovery just works.

I use Biome — can I use this?

Yes. Drop a biome.json in an app and gimme-the-lint binds that app to Biome instead of ESLint — no running both, no doubled CI time, no config conflict. Biome's JSON reporter is parsed like any other adapter. (Biome locates diagnostics by byte span, not line number; that is fine — fingerprints exclude position by design.) The linter is config, not a hardcode: ESLint, Biome, Ruff, golangci-lint and Clippy are all just adapters.

Idempotent skips

A language is never a hard prerequisite:

  • No code for a language → silent no-op.
  • Code present, linter not installed → loud ⚠ SKIPPED warning; the commit still goes through, and the gap is recorded in the manifest.
  • Under --strict (and in --offline installs) that same case fails loudly — a silent skip there would hide a provisioning bug.

Adoption modes

Air-gapped / regulated environmentsinstall --offline performs no network fetches, assumes the linter toolchain is provisioned by your image, and fails loudly if a present language has no linter:

gimme-the-lint install --offline

Greenfield / new repos — there is no legacy debt to grandfather, so init --no-baseline writes empty baselines and installs hooks: every violation counts as new, "strict from day one":

gimme-the-lint init --no-baseline

Migrating from v1

v2 changes the baseline layout (.lttf/ + .lttf-ruff/.gtl/), the baseline format, and the config schema. One command handles it:

gimme-the-lint migrate

It backs the legacy directories up under .gtl/legacy-backup/<timestamp>/, then re-baselines from the current code into the v2 layout. check also detects an un-migrated v1 project and prints the same hint. See CHANGELOG.md for the full list of breaking changes.


Claude Code

| Command | Description | |---------|-------------| | /lint | Run progressive linting on the project | | /lint:status | Show the dashboard (per-app baselines + drift) | | /lint:baseline | Create or refresh baselines |

When a commit Claude makes is blocked by the pre-commit hook, the hook output includes LLM instructions: Claude auto-runs check --fix, re-stages, and retries — only asking you if violations remain after auto-fix.

GitHub Action

- uses: TheGlitchKing/[email protected]
  with:
    mode: full          # 'full' or 'progressive'
    fix: false
    strict: false
    verify: false       # also run the checks that need a database (CI only)
    comment-on-pr: true

A ready-to-copy workflow lives at templates/lint.workflow.template.yml.

Pin a version. A floating @v2 tag is only safe if it exists — and until v2.6.0 it did not, so every workflow copied from the old template failed with unable to find version v2. @v2 now exists and moves with each 2.x release; if you would rather not track a moving tag, pin the exact one as above.


Architecture

lib/
├── violation.js        NormalizedViolation — the linter-agnostic currency
├── fingerprint.js      line/column-independent violation identity
├── diff-engine.js      pure diff: new vs baselined vs fixed
├── baseline-store.js   one baseline.json format for every linter
├── adapters/           one adapter per linter (eslint, biome, ruff,
│                       golangci-lint, clippy, tflint, ansible-lint)
│                       + the base contract
├── project-model.js    discovers apps + binds them to linters
├── units.js            resolves apps → {dir, linters, baseline path}
├── check.js            runCheck: lint → diff → report
├── baseline.js         runBaseline: capture violations into .gtl/
├── gtl-manifest.js     global .gtl/manifest.json
├── drift.js            per-app drift detection
├── toolchain.js        per-language linter availability
├── migrate.js          v1 → v2 migration
└── dashboard.js, report.js, installer.js, …

The engine is pure and fully unit-tested; adapters wrap real linters; the CLI, git hooks, GitHub Action and Claude Code plugin are thin front doors over it.

Requirements

  • Node.js >= 20
  • Git (for hooks and staged-file detection)
  • A linter for each language you use (eslint/biome, ruff, golangci-lint, clippy, tflint, ansible-lint) — any language whose linter is absent is simply skipped

License

MIT — see LICENSE