Measure
Continuous measurement in your pipeline
Add the ScaleQuality check to your CI so every build re-measures the repository, and, when you want it to, fails below the maturity line you set.
How it works
A diagnosis run inside the product is a photograph: the state of a repository at one moment. The CI check turns it into a film. On every build the CLI sends the checked-out repository to ScaleQuality, the engine re-measures it, and the result lands on that repository's timeline in the product.
The same step is also a gate. Give it a code-maturity cutoff and it exits non-zero below that number, which fails the job. Leave the cutoff off and it measures on every build without ever blocking a merge.
| Stage | What happens |
|---|---|
| Checkout | Your job checks out the repository with its full git history. The check reads the working tree from disk, so it has to run after the checkout step and from the repository root. |
| Upload | The CLI packs the working tree and the .git directory, skipping build output and dependency folders, and sends it to ScaleQuality over HTTPS. |
| Measure | The engine re-runs the code maturity analysis, security, reliability, maintainability and supply chain, plus the AI durability attribution that reads git blame. |
| Report | The score is written to the repository's timeline in the product. The CLI prints the result and exits 0, or 1 when the score sits below your cutoff. |
Before you start
What you need
- A ScaleQuality account on the Business plan or above. Continuous measurement is part of Business.
- A repository already connected to ScaleQuality, with at least one diagnosis run. That run is what gives you the project id.
- Permission to add secrets to the pipeline you are about to edit.
- Node.js 18 or later on the CI runner, so npx can fetch the check.
- A checkout with full git history in the job. AI durability is attributed with git blame, and a shallow clone truncates it.
Set it up
Four steps, around ten minutes. Steps 1 and 4 happen inside ScaleQuality, steps 2 and 3 inside your CI.
Create your SQ token
The token is what lets a pipeline report a measurement. It is scoped to your organization, so one token could serve every repository, but create one per pipeline: revoking a single pipeline's access should not stop the others from reporting.
- In ScaleQuality, open the repository's diagnosis.
- Choose Turn on continuous measurement.
- Press Generate token.
Tokens default to a 90-day expiry. To rotate one without a gap in coverage:
- Generate the new token and copy it.
- Update the CI secret in every pipeline that uses the old one.
- Revoke the old token under Settings, Security, CI tokens.
Revoking takes effect immediately: the next run of any pipeline still holding that token fails to report. Revoke last, never first.
Store the token as a CI secret
The snippets read the token from the environment as SQ_TOKEN and never carry the value itself. Add it to your CI's secret store under exactly that name.
| CI | Where to add it | Name |
|---|---|---|
| GitHub Actions | Settings, Secrets and variables, Actions, New repository secret. | SQ_TOKEN |
| GitLab CI | Settings, CI/CD, Variables. Tick Masked, and Protected if only protected branches run the job. | SQ_TOKEN |
| Bitbucket | Repository settings, Repository variables, with Secured ticked. | SQ_TOKEN |
| Azure Pipelines | Pipeline, Variables, with Keep this value secret ticked, or a variable group backed by Key Vault. | SQ_TOKEN |
| CircleCI | Project settings, Environment variables, or a context when several projects share the token. | SQ_TOKEN |
| Jenkins | Manage Jenkins, Credentials, Secret text, then bind it with withCredentials in the job. | SQ_TOKEN |
Add the check to your pipeline
Pick your CI below and copy the step. Paste your project id first and every snippet on this page fills it in; you will find it in the URL of the repository's diagnosis, and in the snippet the setup dialog shows.
# .github/workflows/scalequality.yml
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history: durability is measured with git blame
- run: npx @scalequality/check@0.4.0 --token "$SQ_TOKEN" --project <projectId> --host https://app.scalequality.io
env:
SQ_TOKEN: ${{ secrets.SQ_TOKEN }}The step has to run after the checkout and from the repository root. A job that runs it earlier uploads an empty folder and still produces a score.
Break the build below your line
The gate is a single number: the code maturity score, from 0 to 100, under which the build fails.
- Back in the setup dialog, tick fail builds.
- Drag the slider to the score you want to hold.
- Press Turn on.
Leave fail builds unticked and the check still measures every build and still writes the timeline, but it never blocks a merge and the exit code stays 0.
Measured test coverage
Nothing computes coverage by looking at a repository, and no tool does: coverage only exists once the tests actually run and the runner writes a report. What ScaleQuality does is import that report. What is different is that you do not have to tell us where it is.
Run your tests with coverage before the check step and the verdict shows line coverage, labelled MEASURED, naming the report it came from. Without a report the verdict answers a narrower question, and says which one.
Produce the report
Add coverage to the test command you already run in this job. Each of these writes to a path the check finds on its own.
| Stack | Add to your test step | Writes |
|---|---|---|
| Maven | mvn -B verify | target/site/jacoco/jacoco.xml |
| Gradle | ./gradlew test jacocoTestReport | build/reports/jacoco/test/jacocoTestReport.xml |
| Jest / Vitest | npm test -- --coverage | coverage/lcov.info |
| pytest | pytest --cov --cov-report=xml | coverage.xml |
| .NET | dotnet test --collect:"XPlat Code Coverage" | **/TestResults/**/coverage.cobertura.xml |
| Go | go test -coverprofile=coverage.out ./... | coverage.out |
| PHPUnit | vendor/bin/phpunit --coverage-clover build/logs/clover.xml | build/logs/clover.xml |
| cargo-llvm-cov | cargo llvm-cov --lcov --output-path lcov.info | lcov.info |
| SimpleCov | bundle exec rspec | coverage/lcov.info |
scalequality.properties
Optional, at the repository root, in the same shape as sonar-project.properties so nobody has to learn a second dialect. Unknown keys are ignored, so a file written for a newer engine never breaks an older one.
# Optional. Only needed when the defaults do not fit. # Where your coverage report lands, if it is not in the usual place. sq.coverage.reportPaths=target/site/jacoco/jacoco-aggregate.xml # Files that are not application modules, so they stop counting as untested. sq.exclusions=src/main/java/**/generated/**,**/*.pb.go # Test roots and class suffixes your house uses that nobody else does. sq.test.inclusions=qa sq.test.suffixes=Should,Fixture
| Key | Meaning |
|---|---|
sq.coverage.reportPaths | Comma-separated report paths. Set this only when discovery does not find yours. |
sq.exclusions | Generated code, migrations, DTOs. Excluded files stop counting as untested modules. |
sq.test.inclusions | Extra directories that hold tests, for a layout outside the usual test/ and src/test/. |
sq.test.suffixes | Class suffixes that mark a test in your house style, on top of Test, Tests, IT and Spec. |
CLI reference
The check is published on npm as @scalequality/check. The snippets pin a version on purpose: a gate that can break a build must not change engine between two runs of the same commit.
npx @scalequality/check@0.4.0 --help
Options
| Flag | Env | Default | Meaning |
|---|---|---|---|
--token | SQ_TOKEN | Required | Your SQ token. |
--project | SQ_PROJECT | Required | The ScaleQuality project id. |
--host | SQ_HOST | app.scalequality.io | API host. Only change it if you are pointing at a different ScaleQuality environment. |
--image | SQ_IMAGE | off | Also scan the container image this job just built, for example myorg/api:sha. Needs trivy on PATH; without it the image scan is skipped, it says so in the log, and the build continues. |
--timeout | — | 600 | Seconds to wait for the measurement before giving up. |
--json | — | off | Print the raw result as JSON instead of the human summary. |
--help | — | — | Print the options and exit. |
Exit codes
| Exit code | Meaning |
|---|---|
0 | The measurement passed, or the check is in track-only mode. |
1 | The score is below your cutoff. The job fails. |
2 | The check could not run: missing arguments, an invalid token, or the API was unreachable. |
Exit code 2 is a broken step, not a quality verdict. Treat it as an outage of the check rather than as a failing gate.
Troubleshooting
The failures below are the ones that show up in real pipelines, roughly in the order they tend to happen.
| What you see | What it means | Fix |
|---|---|---|
| ScaleQuality: --token and --project are required | The secret never reached the step. On GitHub Actions this is almost always a missing env block on the run step. | Map the secret onto the step explicitly, exactly as the snippet does with its env block. |
| 401 Unauthorized | The token expired, was revoked, or belongs to another organization. | Issue a new token in the setup dialog, update the CI secret, then revoke the old one, in that order. |
| 403, or a lock in the setup dialog | Continuous measurement is part of the Business plan and the organization sits below it. | Change the plan and reopen the dialog. The token and the snippet are unaffected. |
| AI durability much lower than the product shows | The job used a shallow clone, so git blame cannot see the commits the attribution needs. | Set full history on the checkout: fetch-depth 0, GIT_DEPTH 0, clone depth full, or fetchDepth 0, depending on the CI. |
| A score came back but the repository looks empty | The check ran before the checkout, so it uploaded an empty working directory. | Move the step after the checkout and make sure it runs from the repository root. |
| The step times out | A large repository can take longer than the default 600 seconds to measure. | Raise --timeout. Start at 1200 and bring it back down once you know the real duration. |
| Container findings are missing after passing --image | trivy is not on PATH in that job, so the image scan was skipped. The log says so. | Install trivy in the job before the check, or drop --image if you do not scan images there. |
| The pipeline passes but nothing appears on the timeline | The project id points at a different repository than the one the job checked out. | Compare the id in the step with the one in the URL of that repository's diagnosis. |