Skip to main content

quality-gate

A ratchet quality gate. It reads what your linters, type checkers, test runners and scanners already wrote, compares every metric to a committed baseline, and fails the build if any of them got worse.

Not a threshold gate. There is no number you have to reach before it is useful — the baseline starts wherever your code is today, and the only rule is that it may not move backwards. Improvements are adopted automatically after merge, so the bar rises on its own and never falls.

## Ratchet Quality Gate

| Package | Metric | Baseline | Current | Δ | Policy | Status |
|---|---|---:|---:|---:|:---:|:---:|
| `api` | coverage_lines_pct | 89.09 | 88.22 | -0.87 | ↑ ±0.1 | 🔴 FAIL |
| `api` | complexity_violations | 108 | 105 | -3 | ↓ | 🟢 improved |
| `api` | mypy_errors | 0 | 0 | +0 | ↓ | ⚪ ok |
| `(global)` | secret_findings | 0 | 0 | +0 | =0 | ⚪ ok |

**FAIL** — 1 regression(s):
- `api` / **coverage_lines_pct** 89.09 → 88.22

Policy is the rule the row was scored by, not a second opinion about it: and say which way is good, =0 marks a metric that may only ever be zero, and a ± appears only where the metric declares a tolerance — so a row that slipped inside its margin can be told apart from one the gate failed to notice.

Why this repo exists

Five repos were running five copies of the same 400–1500 line script. They had diverged: different exclusion lists, different report paths, one repo carrying a one-line bug fix that never reached the other four because there was no mechanism for it to travel by. Comparing them, almost every difference was configuration wearing the costume of code.

So the program lives here once, and everything that legitimately differs per repo lives in that repo's quality-gate.toml.

Install

uvx ratchet-gate@0.3.0 check

No install step in CI, no vendored copy to keep in sync, and the pin says exactly which version scored a given run.

The distribution is ratchet-gate; the command is available as both ratchet-gate and quality-gate, so an adopting workflow can keep whichever name it already says.

From a machine on the LAN you can install straight from the source of truth instead, which is what scripts/verify-against-repo.sh does when checking an unreleased commit:

uvx --from git+https://git.marim.dev/mateuscmarim/quality-gate@v0.3.0 quality-gate check

git.marim.dev has no public DNS record, so that form works from self-hosted runners and nowhere else. PyPI is the form to put in a workflow. See RELEASING.md.

Use

quality-gate check                              # score this run, exit 1 on regression
quality-gate check --baseline-ref origin/main   # score against the stricter baseline
quality-gate check --measured api,worker        # only these packages ran
quality-gate promote --monotonic                # adopt improvements only (post-merge)
quality-gate stage                              # multi-module Gradle repos only, see below
quality-gate collect-duplication                # run jscpd, write jscpd.json for the parser above

Point it at a directory of tool output — .quality/<package>/coverage.json, ruff.json, mypy.txt, and so on — and a quality-baseline.json.

Configure

quality-gate.toml at the repo root. Every key is optional; a repo whose layout matches the defaults needs no file at all.

[gate]
artifacts_dir = ".quality"
baseline_path = "quality-baseline.json"
non_ratcheted_dirs = ["tests", "scripts", "notebooks"]
default_baseline_ref = "origin/main"

non_ratcheted_dirs is the one most repos change. Findings in those directories stay in the uploaded artifacts — they just do not gate. One-off analysis scripts, notebook data collectors and test helpers are read top-to-bottom and run by hand; a 200-line main() is the right shape for what they are.

Multi-module Gradle repos

Kover, detekt and Android Lint report per Gradle module, and a ratchet package is usually several modules. stage folds them into the one file per group the parsers read, so tell it which modules belong to which group:

[gate.kotlin]
detekt_config = "config/detekt.yml"          # read to tell complexity from formatting
kotlinc_log = "build/kotlinc-warnings.log"   # one repo-wide --console=plain capture

[gate.kotlin.groups]
core = ["core/network", "core/database"]
app = ["app"]

Then uvx --from 'ratchet-gate[kotlin]==0.3.0' ratchet-gate stage before check. The extra carries PyYAML, which nothing but the detekt config reader needs.

The map lives in your repo rather than in this package because your workflow's change-detection step and your own test suite read it too — a TOML table serves all three.

Two things stage refuses to do, both of which used to be silent passes: it will not stage a zero for a module that produced no report, and check will not score a staged snapshot older than the reports it came from — including one left behind by a ./gradlew clean, or by a different version of the gate.

Two things it will not do

It will not let a branch set its own bar. Every number the gate enforces comes out of a tracked file on the branch being scored, so the same commit that adds 47 lint errors can raise lint_errors to 47 and pass. That needs no malice: promote without --monotonic sets baseline = current for every metric, so a branch that runs it locally to seed one new row rewrites all the others. Pass --baseline-ref origin/main in CI and each metric is scored against the stricter of the two baselines.

It will not go green when it could not measure something. A missing gitleaks.json is not a clean secret scan; a crashed bandit is not zero findings; a jscpd report that never got written is not zero duplication. Every one of those is an error that exits 1. All three used to be written the other way round, and each one silently disabled the check it was reporting on.

Supported

Language Tools
Python coverage.py, ruff (lint + complexity), mypy, bandit
JavaScript / TypeScript vitest, biome, tsc, npm audit
Kotlin / Android Kover, detekt, Android Lint, kotlinc (via stage)
Repo-wide jscpd, gitleaks, pip-audit, npm audit

Developing

uv venv && uv pip install -e '.[dev]'
.venv/bin/python -m pytest        # includes the equivalence suite
.venv/bin/python -m mypy          # --strict, src and tests

The suite runs the implementations this package replaces side by side with this one over identical inputs and asserts their stdout, stderr, exit code and written files match exactly. That is what lets a repo switch over without its gate changing verdict on the day it does. Every intended departure is listed in DIVERGENCES.md; anything else is a bug.

To check against a repo's real reports rather than synthetic ones:

scripts/verify-against-repo.sh ../../trainwithme/nasa nasa-server

Design notes live in the Quality Gate project on mddocs.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ratchet_gate-0.5.1.tar.gz (163.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ratchet_gate-0.5.1-py3-none-any.whl (49.3 kB view details)

Uploaded Python 3

File details

Details for the file ratchet_gate-0.5.1.tar.gz.

File metadata

  • Download URL: ratchet_gate-0.5.1.tar.gz
  • Upload date:
  • Size: 163.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for ratchet_gate-0.5.1.tar.gz
Algorithm Hash digest
SHA256 0f293cdaefe14f3289528b3ee68c8a3baaf825245bb3255ee2480132f62714a5
MD5 1c9a2c20756a9dcb097f4b286da497d8
BLAKE2b-256 f674101bf4bf019c26c4f50be6ffa32ed764531fe144ab8f5e876d17689e5514

See more details on using hashes here.

File details

Details for the file ratchet_gate-0.5.1-py3-none-any.whl.

File metadata

  • Download URL: ratchet_gate-0.5.1-py3-none-any.whl
  • Upload date:
  • Size: 49.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.12.7 {"installer":{"name":"uv","version":"0.12.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for ratchet_gate-0.5.1-py3-none-any.whl
Algorithm Hash digest
SHA256 aacb6a6dc97f7ea40c936bd5a808e67254335f3d2f0c8d82c20426817a700300
MD5 3bc4028d91177c033c99fffff1ceb90a
BLAKE2b-256 4e8fee2d83115ca5a27f844150f7f12e8bc922648a26be4aa5dc00c560c837b8

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.1 This release

2 files

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page