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.jsonhas exactly one matching script:lint,typecheckortype-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.