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.

StageWhat happens
CheckoutYour 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.
UploadThe CLI packs the working tree and the .git directory, skipping build output and dependency folders, and sends it to ScaleQuality over HTTPS.
MeasureThe engine re-runs the code maturity analysis, security, reliability, maintainability and supply chain, plus the AI durability attribution that reads git blame.
ReportThe 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.
Note. If the setup dialog shows a lock instead of a token, the organization is on a plan below Business. Everything on this page still applies once the plan changes; nothing about the snippet is different.

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.

  1. In ScaleQuality, open the repository's diagnosis.
  2. Choose Turn on continuous measurement.
  3. Press Generate token.
Important. The full secret is shown once, at creation. Copy it before closing the dialog. Afterwards only the prefix is visible, and a lost token is replaced, never recovered.

Tokens default to a 90-day expiry. To rotate one without a gap in coverage:

  1. Generate the new token and copy it.
  2. Update the CI secret in every pipeline that uses the old one.
  3. 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.

CIWhere to add itName
GitHub ActionsSettings, Secrets and variables, Actions, New repository secret.SQ_TOKEN
GitLab CISettings, CI/CD, Variables. Tick Masked, and Protected if only protected branches run the job.SQ_TOKEN
BitbucketRepository settings, Repository variables, with Secured ticked.SQ_TOKEN
Azure PipelinesPipeline, Variables, with Keep this value secret ticked, or a variable group backed by Key Vault.SQ_TOKEN
CircleCIProject settings, Environment variables, or a context when several projects share the token.SQ_TOKEN
JenkinsManage Jenkins, Credentials, Secret text, then bind it with withCredentials in the job.SQ_TOKEN
Important. Never commit the token, and never paste it into the YAML. A token that reaches a build log is a token you have to rotate, and build logs are usually readable by more people than the repository is.

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 Actions
# .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 }}
Full git history is required. Every snippet checks the repository out with full history: fetch-depth 0 on GitHub Actions, GIT_DEPTH 0 on GitLab, clone depth full on Bitbucket, fetchDepth 0 on Azure Pipelines. AI durability is attributed with git blame, and a shallow clone hides the commits that attribution needs. With a shallow clone the build still passes and the number simply comes out wrong, which is the worse failure.

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.

  1. Back in the setup dialog, tick fail builds.
  2. Drag the slider to the score you want to hold.
  3. 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.

Note. Run track-only for a week first, look at where the repository actually sits, then set the line at or just below today's score. A line above the current score fails every build from day one, which teaches the team to bypass the gate rather than to raise the score.

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.

StackAdd to your test stepWrites
Mavenmvn -B verifytarget/site/jacoco/jacoco.xml
Gradle./gradlew test jacocoTestReportbuild/reports/jacoco/test/jacocoTestReport.xml
Jest / Vitestnpm test -- --coveragecoverage/lcov.info
pytestpytest --cov --cov-report=xmlcoverage.xml
.NETdotnet test --collect:"XPlat Code Coverage"**/TestResults/**/coverage.cobertura.xml
Gogo test -coverprofile=coverage.out ./...coverage.out
PHPUnitvendor/bin/phpunit --coverage-clover build/logs/clover.xmlbuild/logs/clover.xml
cargo-llvm-covcargo llvm-cov --lcov --output-path lcov.infolcov.info
SimpleCovbundle exec rspeccoverage/lcov.info
Note. The check looks in the default output directory of every build tool above, and picks up a report anywhere in the tree by name. A multi-module build is summed across its modules. You only need the file below when your report lands somewhere unusual.

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.

scalequality.properties
# 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
KeyMeaning
sq.coverage.reportPathsComma-separated report paths. Set this only when discovery does not find yours.
sq.exclusionsGenerated code, migrations, DTOs. Excluded files stop counting as untested modules.
sq.test.inclusionsExtra directories that hold tests, for a layout outside the usual test/ and src/test/.
sq.test.suffixesClass suffixes that mark a test in your house style, on top of Test, Tests, IT and Spec.
Without a report, the number answers a different question. The verdict then shows the share of source modules that have a test file, labelled as such, and never as a line percentage. It is a real signal about test structure, and it is not line coverage: one test class exercising five production classes reads as one covered module out of five. A report removes the ambiguity.

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

FlagEnvDefaultMeaning
--tokenSQ_TOKENRequiredYour SQ token.
--projectSQ_PROJECTRequiredThe ScaleQuality project id.
--hostSQ_HOSTapp.scalequality.ioAPI host. Only change it if you are pointing at a different ScaleQuality environment.
--imageSQ_IMAGEoffAlso 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.
--timeout600Seconds to wait for the measurement before giving up.
--jsonoffPrint the raw result as JSON instead of the human summary.
--helpPrint the options and exit.

Exit codes

Exit codeMeaning
0The measurement passed, or the check is in track-only mode.
1The score is below your cutoff. The job fails.
2The 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 seeWhat it meansFix
ScaleQuality: --token and --project are requiredThe 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 UnauthorizedThe 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 dialogContinuous 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 showsThe 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 emptyThe 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 outA 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 --imagetrivy 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 timelineThe 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.