Browse the docs

Merge readiness suite

A free, reusable GitHub workflow that answers one question for every pull request, is it ready to merge?

The merge readiness suite runs your repository’s own checks as separate jobs (debug logging, leaked secrets, lint, types, tests, build, and pull request policy) and publishes one combined check, QualityGate / merge-readiness. It needs no API key, sends nothing to QualityGate, and never merges anything.

It complements the preview scan: the scan checks what users will see, the suite checks the code and the review.

Add it

# .github/workflows/quality.yml
name: Quality

on: pull_request

jobs:
  qualitygate:
    name: QualityGate
    uses: qualitygate/scan-action/.github/workflows/quality-check.yml@v1
    with:
      quality-config: .qualitygate.yml

Each module is its own job with its own check and re-run button: QualityGate / config, debug-logs, secrets, lint, typecheck, tests, build, pr-policy, and merge-readiness. Re-running one module re-runs merge readiness with it.

Make QualityGate / merge-readiness the required status check in your branch protection rules. It is the only one you need to require.

Defaults

Without a configuration file:

  • debug logging, secret scanning, and pull request policy are on;
  • lint, typecheck, tests, and build run when package.json has exactly one matching script: lint, typecheck or type-check, test, build.

The jobs use Node.js 24 by default; pass node-version under with: to change it.

Configuration

Everything is optional. A complete .qualitygate.yml:

qualityCheck:
  checks:
    debugLogs: true
    secrets: true
    lint: true
    typecheck: true
    tests: true
    build: true
    prPolicy: true
  commands:
    install: pnpm install --frozen-lockfile
    lint: pnpm lint
    typecheck: pnpm typecheck
    test: pnpm test
    build: pnpm build
  approvals:
    minimum: 1
  forbiddenDebug:
    include: ['**/*.{js,jsx,ts,tsx}']
    exclude: ['**/*.test.*', '**/*.spec.*', '**/fixtures/**', '**/generated/**']
    patterns: [console.log, console.debug, console.info, debugger]
  secrets:
    scope: diff
    configPath: .gitleaks.toml
    baselinePath: .gitleaks-baseline.json
    gitleaksVersion: 8.28.0
  requiredChecks:
    - Preview deployment
  mergeReadiness:
    timeoutMinutes: 15

An invalid file fails QualityGate / config with the exact field, before any of your commands run.

Modules

debug-logs fails when a line the pull request adds to production JS or TS source contains a forbidden pattern. Comments, strings, template text, and regular expressions do not count, and neither do tests, fixtures, generated, vendored, or documentation files. console.warn and console.error are allowed unless you list them.

secrets runs gitleaks over the commits in the pull request and fails when one adds a credential. It reports the rule, file, line, commit, and fingerprint, never the value, and nothing leaves the runner. scope: full scans the whole history instead. The gitleaks config, ignore file, and baseline are read from the base branch, and inline gitleaks:allow comments are ignored, so a pull request cannot allowlist its own leak. The fix for a finding is to rotate the credential.

lint, typecheck, tests, and build install dependencies, run their command once, and fail on a non-zero exit code, showing the last lines of output (with secrets masked) in the job summary. A module that is on but has no command is skipped, never passed.

pr-policy fails on a draft, a merge conflict, an outstanding change request, or fewer approvals than approvals.minimum (default 0), and names the unmet condition.

merge-readiness always runs. It reports Eligible for merge: yes when every enabled module and every check named in requiredChecks passed and there are no conflicts. While a required check is still running it waits, up to mergeReadiness.timeoutMinutes; if time runs out it reports pending and fails, and a re-run picks up the finished checks. Use requiredChecks to fold other checks, such as your preview deployment or the QualityGate scan job, into the one required status.

Forks

For pull requests from forks, the configuration and package.json are read from the base branch, so a fork cannot choose which commands run. The jobs get read-only access and no secrets.