Output Formats¶
Qualimetrix supports 12 output formats (including the deprecated
text-verbose). Choose the one that fits your workflow.
summary (default)¶
Health-oriented overview showing project health scores, worst offenders, and violation summary. This is the default CLI output designed for quick project assessment.
When to use: Local development, quick project health overview.
Key features:
- An overall health bar plus one bar per dimension (complexity, cohesion, coupling, typing, maintainability), each followed by a
↳decomposition breakdown of the metrics behind that score - Scores as percentages, not raw point values
- One-line
Worst namespaces/Worst classeslists, each entry prefixed by its score, with a trailing+N more (use ...)when the list was truncated - A
Top issues by impactsection: a ranked list of the highest-impact individual violations, each with severity, impact score, file, estimated fix time, rule channel, message, and symbol - Violation count with tech debt estimate (including debt density per 1K LOC)
- Multiple contextual
Hints:for next steps
Example output:
Qualimetrix 0.26.0 — 62 files analyzed, 0.8s
Analysis complete: 62 analyzed, 0 generated file(s) excluded.
Health █████████████████████░░░░░░░░░ 71.4% Fair
Complexity ████████████████████████░░░░░░ 79.2% Good
↳ Cyclomatic (avg): 3.1 (target: below 4) — manageable branching
↳ Cognitive (avg): 2.4 (target: below 5) — straightforward control flow
↳ Cyclomatic: 14 (target: below 4) — too many code paths
↳ Cognitive: 18 (target: below 5) — deeply nested, hard to follow
Cohesion ████████████████░░░░░░░░░░░░░░ 54.8% Poor
↳ TCC: 0.4 (target: above 0.5) — methods rarely share fields
↳ LCOM4: 3 (target: 1 or less) — class splits into unrelated clusters
Coupling ███████████████████░░░░░░░░░░░ 63.1% Fair
↳ Ce (avg): 4.7 (target: below 3) — elevated outgoing coupling
↳ Ce pkg (avg): 1.8 (target: below 1) — wide package dependencies
↳ Distance: 0.52 (target: below 0.3) — poor balance of abstraction and stability
Typing ███████████████████████████░░░ 91.3% Excellent
↳ Parameter types: 91 (target: 100%) — 251 of 275 typed (91.3%)
↳ Return types: 88 (target: 100%) — 231 of 262 typed (88.2%)
↳ Property types: 95 (target: 100%) — 118 of 124 typed (95.2%)
Maintainability █████████████████████████░░░░░ 82.0% Good
↳ MI (avg): 71.4 (target: above 65) — code is maintainable
↳ MI (p5): 52.6 (target: above 50) — even worst methods are maintainable
↳ MI: 41.8 (target: above 65) — code is hard to change safely
* Labels reflect per-dimension scales (e.g., Typing requires >80% for Acceptable)
Worst namespaces
48.2 App\Billing\Invoice (6 classes, 11 violations, 3.8/100 LOC)
55.9 App\Service\Order (4 classes, 7 violations, 2.1/100 LOC)
61.3 App\Repository (9 classes, 5 violations, 0.9/100 LOC)
+5 more (use --format=html or --format-opt=top=8)
Worst classes
38.4 App\Billing\Invoice\InvoiceCalculator — low cohesion
45.1 App\Service\Order\OrderService — high coupling
52.7 App\Repository\OrderRepository
+9 more (use --format=html or --format-opt=top=10)
Top issues by impact
1. [ERR] 4.12 src/Billing/Invoice/InvoiceCalculator.php [45min]
complexity.cognitive: Cognitive complexity: 24 (threshold: 15). Top: nested if +4 L88, nested foreach +3 L74, nested if +2 L91 — deeply nested, hard to follow (InvoiceCalculator::recalculate)
2. [ERR] 3.65 src/Service/Order/OrderService.php [30min]
coupling.cbo: CBO is 21 (threshold: 15) — too many collaborators (OrderService)
3. [WRN] 2.90 src/Repository/OrderRepository.php [20min]
complexity.ccn: Cyclomatic complexity: 13 (threshold: 10) — too many code paths (OrderRepository::findByCriteria)
82 violations (19 errors, 63 warnings) | Tech debt: 6h 20min (54.3 min/kLOC to fix)
Hints: --detail to see violations (top 200) | --namespace='App\Billing\Invoice' to drill down | --format=html -o report.html for full report
See CLI Options for the flag that controls how many Top issues by impact entries are shown.
Drill-down with --namespace and --class:
# Show violations for a specific namespace subtree
bin/qmx check src/ --namespace=App\\Service
# Show violations for a specific class
bin/qmx check src/ --class=App\\Service\\UserService
Detail mode with --detail:
# Append grouped violation list (default limit: 200)
bin/qmx check src/ --detail
# Show all violations (no limit)
bin/qmx check src/ --detail=all
# Custom limit
bin/qmx check src/ --detail=50
Note
--detail is auto-enabled when using --namespace or --class. It also works with --format=text to append a grouped violation list after the one-line-per-violation output.
text¶
Compact, one-line-per-violation output. Compatible with GCC/Clang error format, so violations are clickable in most terminals and IDEs.
When to use: Local development, quick checks, piping to grep or wc.
Example output:
src/Service/UserService.php:42: warning[code-smell.error-suppression]: Error suppression (@) on find() - handle errors explicitly
src/Service/UserService.php: warning[complexity.ccn]: Cyclomatic complexity is 15, exceeds threshold of 10. Consider extracting methods or simplifying conditions (UserService::calculate)
src/Repository/OrderRepository.php: warning[coupling.class-rank]: ClassRank is 0.041, exceeds threshold of 0.037 (scaled for 45 classes). This class is a critical hub — changes have wide impact (OrderRepository)
0 error(s), 3 warning(s) in 45 file(s)
Format: there are three line shapes, depending on what the finding is about.
- A violation pinned to a specific statement carries a line number:
file:line: severity[violationCode]: message (symbol). - A class- or method-level finding whose rule judges the whole declaration rather than one statement — for example
complexity.ccn,complexity.wmc,coupling.class-rank— omits the line segment instead:file: severity[violationCode]: message (symbol), even though the same finding carries alinein--format=json. - A project-level finding (no owning file at all — e.g. an
architecture.unreachable-layerfinding) drops the file segment too:[project]: severity[violationCode]: message, with no trailing(symbol). On this project's own self-analysis this third form is common, not an edge case:bin/qmx check src/Analysis/Evidence/Complexity --format=textprints project-level lines for a majority of the output.
text-verbose¶
Deprecated
text-verbose is deprecated. Use --format=text --detail instead, which provides the same grouped, multi-line violation output alongside the compact one-line format.
json¶
Machine-readable JSON output. Summary-oriented format with health scores, worst offenders, and all violations.
When to use: Custom scripts, dashboards, programmatic processing.
Top-level keys: meta, summary, coverage, health, worstNamespaces, worstClasses, topIssues, violations, violationsMeta, plus violationGroups when --group-by is passed — without it, the key is absent entirely, not an empty object.
Example output:
{
"meta": {
"version": "1.0.0",
"package": "qmx",
"timestamp": "2025-01-15T10:30:00+00:00"
},
"summary": {
"filesAnalyzed": 45,
"filesSkipped": 0,
"duration": 1.234,
"violationCount": 3,
"errorCount": 2,
"warningCount": 1,
"infoCount": 0,
"techDebtMinutes": 270,
"debtPer1kLoc": 2.1
},
"health": {
"complexity": {
"score": 78.0,
"label": "Excellent",
"threshold": {"warning": 50, "error": 25},
"coverage": {
"state": "measured",
"measured": 2263,
"eligible": 2263,
"ratio": 1.0,
"unit": "callables",
"basis": "complexity.ccn.count",
"reason": null
},
"decomposition": [
{
"metric": "complexity.ccn.sum",
"humanName": "Cyclomatic complexity",
"value": 412,
"good": true,
"direction": "lower-is-better"
}
],
"worstContributors": [
{
"symbolPath": "App\\Service\\UserService",
"className": "App\\Service\\UserService",
"metrics": {"complexity.ccn.sum": 96}
}
]
},
"overall": {
"score": 72.0,
"label": "Fair",
"threshold": {"warning": 50, "error": 25},
"coverage": {
"state": "not-applicable",
"measured": null,
"eligible": null,
"ratio": null,
"unit": null,
"basis": null,
"reason": "health.overall composes the other dimensions; each of them publishes its own coverage"
},
"decomposition": [],
"worstContributors": []
}
},
"worstNamespaces": [
{
"symbolPath": "App\\Service",
"healthOverall": 52.0,
"label": "Poor",
"reason": "high coupling",
"violationCount": 15,
"size.class-count": 8,
"healthScores": {}
}
],
"worstClasses": [
{
"symbolPath": "App\\Service\\UserService",
"healthOverall": 45.0,
"label": "Poor",
"reason": "low cohesion",
"violationCount": 8,
"file": "src/Service/UserService.php",
"metrics": {},
"healthScores": {}
}
],
"topIssues": [
{
"rank": 1,
"file": "src/Service/UserService.php",
"line": 42,
"symbol": "App\\Service\\UserService::calculate",
"rule": "complexity.ccn",
"severity": "error",
"message": "Cyclomatic complexity: 15 (threshold: 10) — too many code paths",
"impactScore": 3.71,
"coupling.class-rank": 0.1237,
"debtMinutes": 30
}
],
"violations": [
{
"file": "src/Service/UserService.php",
"line": 42,
"subject": "declaration:callable:App\\Service\\UserService::calculate@src/Service/UserService.php",
"symbol": "App\\Service\\UserService::calculate",
"channel": "complexity.ccn",
"occurrence": null,
"edge": null,
"namespace": "App\\Service",
"rule": "complexity.ccn",
"code": "complexity.ccn",
"severity": "error",
"message": "Cyclomatic complexity: 15 (threshold: 10) — too many code paths",
"recommendation": null,
"metricValue": 15,
"threshold": 10,
"techDebtMinutes": 30,
"acceptedLevel": null
}
],
"violationsMeta": {
"total": 3,
"shown": 3,
"limit": null,
"truncated": false,
"byRule": {
"complexity.ccn": 2,
"coupling.cbo": 1
}
},
"violationGroups": {}
}
The worstNamespaces and worstClasses entries include a violationDensity field -- violations per 100 lines of code -- providing a size-normalized view of code quality.
topIssues is the same ranked list the summary format prints as "Top issues
by impact"; no other format renders it. Each entry names the rule-specific
impactScore used for ranking and the estimated debtMinutes. The
coupling.class-rank key is always present, but its value is null unless the
rule producing the issue reads a coupling-hub signal; file and line are
nullable too, because a project-level finding has no source position.
Each violation carries acceptedLevel: the baseline ceiling this finding was
measured against, or null whenever the finding has no accepted level of its
own. That covers two distinct cases the value cannot tell apart: no baseline
is configured for the run at all, or a baseline is configured but this
particular finding is new and the baseline never judged it. Neither the JSON
payload nor acceptedLevel itself exposes which case applies.
When it is not null, acceptedLevel is an object — a finding whose own
identity group exceeded its accepted level is reported as a breach and carries
{"shape": "occurrence", "describe": "2 occurrences", "count": 2}. shape
names what the ceiling counts, count is the accepted number, and describe
is that number in words. A breach is also raised to error severity,
whatever the rule would otherwise have reported.
violationsMeta
also reports shown — the number of violations actually included in this
payload, which can be lower than total when --format-opt=violations=N
truncates the list.
For machine identity, use channel + subject + optional occurrence + optional
edge. symbol is the logical display projection; source line, message, and
display order are not stable identity. subject distinguishes exact
declarations from logical and aggregate subjects, occurrence distinguishes
semantic evidence within one channel, and edge contains a required dependency
target plus an optional reference type. An untyped edge is
{"target": "class:App\\Dependency"}; a typed edge is
{"type": "new", "target": "class:App\\Dependency"}. Formatter fingerprints
use the same tuple, so target-only edges differ by target and from a typed edge
to the same target. Existing no-edge and fully typed fingerprints are
unchanged.
When using --group-by=class or --group-by=namespace, violations are organized into a violationGroups object. Each group is {count, violations} — a violation count and the violations array; it does not carry its own errorCount, warningCount, or violationDensity.
The group keys are not always a class FQCN or namespace. For --group-by=class: the key is the class FQCN for a class-scoped finding, the file path for a file-level finding with no class context, and an empty string "" for a project-level finding (which has neither a class nor a file). For --group-by=namespace: the key is the namespace for a namespaced class, <global> for a class with no namespace, and __PROJECT__ for a project-level finding.
Options:
# Limit violations in output (default: all)
bin/qmx check src/ --format=json --format-opt=violations=50
# Control number of worst offenders (default: 10)
bin/qmx check src/ --format=json --format-opt=top=20
# Group violations by class or namespace
bin/qmx check src/ --format=json --group-by=class
bin/qmx check src/ --format=json --group-by=namespace
CI usage:
metrics¶
Raw metric values for every symbol (file, class, namespace, method, function, project). Unlike json which outputs violations, metrics exports the underlying metric data that rules evaluate.
When to use: Custom dashboards, trend analysis, data science pipelines, or building your own quality gates on raw metrics.
Top-level keys: version, toolVersion, package, timestamp, symbols[] (each with type: file/class/namespace/method/function/project, name, file, line, metrics: {...}), coverage, summary. There is no callable type; a single project entry aggregates project-wide statistical metrics (min/max/avg/p95 across all symbols) and has a null line.
Example output (abbreviated):
{
"version": "1.0.0",
"toolVersion": "0.26.0",
"package": "qmx",
"timestamp": "2025-01-15T10:30:00+00:00",
"symbols": [
{
"type": "file",
"name": "src/Service/UserService.php",
"file": "src/Service/UserService.php",
"line": 1,
"metrics": {
"size.loc": 150,
"size.lloc": 120,
"size.class-count": 1
}
},
{
"type": "class",
"name": "App\\Service\\UserService",
"file": "src/Service/UserService.php",
"line": 10,
"metrics": {
"size.method-count": 8,
"size.property-count": 3,
"cohesion.lcom": 2,
"complexity.wmc": 35,
"coupling.ca": 5,
"coupling.ce": 12,
"coupling.cbo": 17,
"coupling.instability": 0.71
}
},
{
"type": "method",
"name": "App\\Service\\UserService::calculate",
"file": "src/Service/UserService.php",
"line": 42,
"metrics": {
"complexity.ccn": 15,
"complexity.cognitive": 22,
"maintainability.halstead.volume": 384.5,
"size.loc": 35
}
}
],
"summary": {
"filesAnalyzed": 45,
"filesSkipped": 0,
"duration": 1.234,
"violations": 3,
"errors": 2,
"warnings": 1,
"info": 0
}
}
Usage:
Note
The metrics format exports all collected metrics, not just those that triggered violations. This makes it useful for tracking metric trends over time, even for code that passes all rules.
checkstyle¶
Checkstyle XML format. Widely supported by CI tools.
When to use: Jenkins, SonarQube, or any tool that accepts Checkstyle XML.
Checkstyle 3.0 XML: <file name="..."> with nested <error line="" severity="error|warning|info" message="" source="qmx.<rule>"/>.
Example output:
<?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="3.0">
<file name="src/Service/UserService.php">
<error line="42"
severity="error"
message="Cyclomatic complexity is 15, max allowed is 10"
source="qmx.complexity.ccn"/>
<error line="87"
severity="warning"
message="Class has 22 methods, max recommended is 20"
source="qmx.size.method-count"/>
</file>
</checkstyle>
CI usage (Jenkins):
sarif¶
SARIF (Static Analysis Results Interchange Format) 2.1.0. A standard for static analysis tools adopted by GitHub, Microsoft, and many IDE vendors.
When to use: GitHub Security tab, VS Code (with SARIF Viewer extension), JetBrains IDEs, Azure DevOps.
SARIF 2.1.0 spec — runs[].results[] entries with ruleId, ruleIndex (position of the rule in tool.driver.rules), level (error/warning/note), message.text, partialFingerprints.primaryLocationLineHash, and locations[].physicalLocation.{artifactLocation.{uri,uriBaseId}, region.{startLine,startColumn}}. locations is not present on every result: a project-level finding with no source position (e.g. architecture.unreachable-layer) has no locations array at all.
runs[].invocations[0] reports executionSuccessful (see the coverage table below), and runs[].originalUriBaseIds declares the %SRCROOT% base referenced by every artifactLocation.uriBaseId, resolving it to the analyzed project root as a file:// URI.
runs[].tool.driver describes the tool itself: name, version,
informationUri, and a rules[] catalogue that every result's ruleIndex
points into. Each rules entry is
{"id": "...", "name": "...", "shortDescription": {"text": "..."}, "fullDescription": {"text": "..."}, "helpUri": "...", "defaultConfiguration": {"level": "..."}}.
Each runs[].invocations[] entry is
{"executionSuccessful": true, "toolExecutionNotifications": []}, and every
notification in it is
{"descriptor": {"id": "..."}, "level": "...", "message": {"text": "..."}} —
this is where a file that failed to parse is reported. runs[].originalUriBaseIds
maps %SRCROOT% to {"uri": "file:///path/to/project/"}.
partialFingerprints.primaryLocationLineHash matters beyond spec completeness:
it is what GitHub's code-scanning alert de-duplication keys on across uploads,
so an alert stays the same tracked alert (not a new one) as long as its
fingerprint is stable between runs.
Example output (abbreviated):
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "Qualimetrix",
"version": "0.26.0",
"rules": [...]
}
},
"results": [
{
"ruleId": "complexity.ccn",
"ruleIndex": 0,
"level": "error",
"message": {
"text": "Cyclomatic complexity is 15, max allowed is 10"
},
"partialFingerprints": {
"primaryLocationLineHash": "complexity.ccn:declaration:callable:App\\Service\\UserService::calculate@src/Service/UserService.php:4e6e45ba70fb46d4"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "src/Service/UserService.php",
"uriBaseId": "%SRCROOT%"
},
"region": {
"startLine": 42,
"startColumn": 1
}
}
}
]
}
]
}
]
}
CI usage (GitHub Actions):
- name: Run Qualimetrix
run: bin/qmx check src/ --format=sarif --no-progress > results.sarif
- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarif
Results appear in the Security tab of your repository and as inline annotations on pull requests.
gitlab¶
GitLab Code Quality JSON format. Shows violations directly in Merge Request diffs.
When to use: GitLab CI/CD with Code Quality reports.
Array of objects with description, check_name, fingerprint, severity (critical/major/info), location.{path,lines.begin}. Severity mapping: error → critical, warning → major, info → info.
Example output (abbreviated):
[
{
"description": "Cyclomatic complexity is 15, max allowed is 10",
"check_name": "complexity.ccn",
"fingerprint": "a1b2c3d4e5f6...",
"severity": "critical",
"location": {
"path": "src/Service/UserService.php",
"lines": {
"begin": 42
}
}
}
]
CI usage (GitLab CI):
code_quality:
stage: test
script:
- bin/qmx check src/ --format=gitlab --no-progress > gl-code-quality-report.json
artifacts:
reports:
codequality: gl-code-quality-report.json
Violations appear inline in the Changes tab of your Merge Request.
github¶
GitHub Actions workflow command format. Produces inline annotations that appear directly in PR diffs when running in GitHub Actions.
When to use: GitHub Actions CI. Simpler setup than SARIF — no upload step needed.
Workflow command format: ::<level> file=<path>,line=<n>,title=<rule>::<message> (one line per violation). Mapping: warning → ::warning, error → ::error.
Only title= appears on every line. A project-level finding has no source
position, so it is annotated as ::<level> title=<rule>::<message> — without
file= and line=, which GitHub then shows against the workflow run rather
than against a line in the diff.
Example output:
::warning file=src/Service/UserService.php,line=87,title=size.method-count::Class has 22 methods, max recommended is 20
::error file=src/Service/UserService.php,line=42,title=complexity.ccn::Cyclomatic complexity is 15, max allowed is 10
CI usage (GitHub Actions):
Annotations appear directly on the changed lines in your pull request — no SARIF upload needed. Only errors cause a non-zero exit code by default.
Tip
Use --format=github for quick inline annotations. Use --format=sarif if you also want results in the GitHub Security tab.
health¶
Text table of health scores for terminal output. Shows each dimension with its score, status label, thresholds, and decomposition details.
When to use: Quick health check from CLI, AI agent workflows, pipeline diagnostics.
Key features:
- Tabular display of all health dimensions (complexity, cohesion, coupling, typing, maintainability)
- Status labels with color coding (green/yellow/red)
- Threshold visibility (warning and error levels)
- Decomposition breakdown for each dimension
- Supports
--namespaceand--classdrill-down
Worst contributors per dimension:
The health output includes worst contributors for each dimension -- the classes or namespaces that drag down each health score the most. Control the number of contributors shown with --format-opt=contributors=N (default: 3):
Usage:
html¶
Interactive treemap report with D3.js visualization. Generates a self-contained single HTML file with namespace/class hierarchy.
When to use: Project-wide visualization, stakeholder reports, team reviews.
Key features:
- Namespace/class hierarchy with LOC-proportional sizing
- Color-coded health scores per node
- Click to drill down into namespaces
- Detail panel with metrics, violations, and decomposition
- Self-contained single HTML file (no external dependencies)
Usage:
Example workflow:
# Generate and open the report
bin/qmx check src/ --format=html -o report.html
open report.html # macOS
xdg-open report.html # Linux
Note
The -o (output) flag is recommended with html format. Without it, HTML content is written to stdout.
suppressed¶
Machine-readable JSON composition of what a run held back from its report and
why. A separate format rather than a section of json: an ordinary check
payload never changes shape for a feature you did not ask for, no matter which
format you selected it with.
When to use: Auditing why an expected finding is missing, reviewing what a
qmx.yaml exclusion actually silences, spotting a dead suppress_paths/
suppress_namespaces entry (a typo'd path, a file the project deleted).
Capture is armed the same way by two independent routes — passing
--show-suppressed, or selecting --format=suppressed itself, including via
format: suppressed in qmx.yaml. Either route arms the same per-rule
exclusion capture, so the counts each surface reports for that mechanism never
disagree.
The two surfaces are not otherwise equivalent. --show-suppressed on
--format=text prints inline @qmx-ignore suppressions and per-rule
exclusions as prose. Global path-suppression and namespace-suppression appear
there only as -v counts, not per finding; baseline and git-scope
removals are not listed at all; and there is no text equivalent of
neverMatched. suppressed is the only surface that publishes all seven
mechanisms as individual findings.
The composition is a multiset, not a set of findings. One finding can be
removed by more than one mechanism — for example, a finding an inline
@qmx-ignore would suppress may already have been removed earlier by a
namespace exclusion. There are seven mechanisms: suppression (inline
@qmx-ignore/@qmx-ignore-file/@qmx-ignore-next-line), path-suppression and
namespace-suppression (global suppress_paths/suppress_namespaces),
baseline (the accepted-level ceiling), git-scope (--report=git:*
narrowing), and the two halves of the per-rule exclusion ledger configured
under rules: {<rule-name>: {...}} — rule-namespace-suppression and
rule-path-suppression. byMechanism counts entries per mechanism; because the
same finding can appear under more than one, those counts do not sum to
the number of distinct findings suppressed — the format's own note field
says so.
A separate neverMatched list reports configured suppressors that excluded
nothing this run: without it, a stale suppress_paths entry pointing at a
deleted file is indistinguishable from one that was never written.
Top-level keys: meta, note, mechanisms (all seven, always present),
byMechanism (count per mechanism, including zero), suppressed (the
multiset), neverMatched.
Example output (abbreviated, from this project's own self-analysis):
{
"meta": {
"version": "dev-main",
"package": "qmx",
"timestamp": "2026-08-29T09:14:02+00:00"
},
"note": "suppressed is a multiset of mechanism x finding, not a set of findings: one finding can appear under more than one mechanism, so byMechanism counts do not sum to the number of distinct findings suppressed.",
"mechanisms": [
"suppression",
"path-suppression",
"namespace-suppression",
"baseline",
"git-scope",
"rule-namespace-suppression",
"rule-path-suppression"
],
"byMechanism": {
"suppression": 12,
"path-suppression": 0,
"namespace-suppression": 0,
"baseline": 0,
"git-scope": 0,
"rule-namespace-suppression": 58,
"rule-path-suppression": 131
},
"suppressed": [
{
"mechanism": "suppression",
"suppressor": "src/Infrastructure/Ast/CachedFileParser.php:15",
"rule": "code-smell.empty-catch",
"channel": "code-smell.empty-catch",
"file": "src/Infrastructure/Ast/CachedFileParser.php",
"line": 73,
"symbol": "src/Infrastructure/Ast/CachedFileParser.php",
"severity": "error",
"message": "Log the exception or add a comment explaining why it is safe to ignore."
},
{
"mechanism": "rule-path-suppression",
"suppressor": "code-smell.constructor-overinjection",
"rule": "code-smell.constructor-overinjection",
"channel": "code-smell.constructor-overinjection",
"file": "src/Analysis/Run/Contract/Collection/SuccessfulFileProcessing.php",
"line": 28,
"symbol": "Qualimetrix\\Analysis\\Run\\Contract\\Collection\\SuccessfulFileProcessing::__construct",
"severity": "warning",
"message": "Constructor parameters: 8 (threshold: 8) — consider splitting responsibilities"
}
],
"neverMatched": [
{
"mechanism": "rule-path-suppression",
"suppressor": "coupling.cbo: src/Analysis/Evidence/Design/*Visitor.php"
}
]
}
For the two ledger mechanisms (rule-namespace-suppression,
rule-path-suppression), suppressor names the producer rule; for
path-suppression/namespace-suppression it is the matched configured pattern;
for suppression it is the directive's file:line; for baseline it is the
accepted entry's description; for git-scope it is the configured git
reference.
Usage:
Analysis coverage in every format¶
Every discovered PHP file is classified as analyzed, intentionally excluded as generated, or failed during parsing/processing. Generated exclusions are a complete run; any failure makes the analysis incomplete and the policy result non-authoritative. Zero discovered files still pass through the selected formatter instead of being replaced with command prose.
| Format | Coverage representation |
|---|---|
summary |
Human coverage sentence after the header |
text |
Human coverage sentence after the violation summary |
text-verbose |
Same projection as text --detail |
health |
Human coverage sentence after the header |
json |
Top-level coverage object: complete, discovered, analyzed, generatedExcluded, failed, failures[] |
metrics |
The same top-level coverage object as json |
sarif |
runs[0].invocations[0].executionSuccessful; failures in toolExecutionNotifications[] |
gitlab |
One blocker issue per failed file with check_name: analysis.<kind>; a complete empty run is [] |
checkstyle |
Failed files are errors under synthetic file [analysis], with source qmx.analysis.<kind> |
github |
One ::error annotation per failed file; complete zero-finding runs emit no annotation |
html |
Embedded coverage data; incomplete runs also show a visible warning banner |
suppressed |
Not represented — this format publishes a suppression composition, not a coverage object |
For json and metrics, each failures[] item has path, kind (parse or
processing), and message. Human formats distinguish no discovered files,
generated-only input, complete analysis, and incomplete analysis.
Comparison table¶
| Format | Readable | Machine | Grouping | CI Integration |
|---|---|---|---|---|
summary |
Best | No | Health scores, drill-down | Any (exit code) |
text |
Good | Parseable | --group-by |
Any (exit code) |
text-verbose |
Good | No | --group-by (default: file) |
Any (exit code) |
json |
No | Yes | Built-in (by file) | Custom scripts |
metrics |
No | Yes | Built-in (by symbol) | Custom scripts, dashboards |
checkstyle |
No | Yes | Built-in (by file) | Jenkins, SonarQube |
sarif |
No | Yes | Built-in | GitHub, VS Code, JetBrains |
gitlab |
No | Yes | Flat list | GitLab MR widget |
github |
No | No | Flat list | GitHub Actions annotations |
health |
Good | No | Health dimensions | Quick checks, CI |
html |
Interactive | No | Treemap hierarchy | Reports, reviews |
suppressed |
No | Yes | Flat multiset by mechanism | Suppression auditing |
Exit codes¶
All formats use the same exit codes:
| Exit code | Meaning |
|---|---|
| 0 | No violations (or only warnings, under the default --fail-on=error) |
| 1 | At least one warning, under --fail-on=warning |
| 2 | At least one error-severity violation |
| 3 | Configuration or input error |
| 4 | Analysis incomplete; policy result is not authoritative |
By default (--fail-on=error), warnings no longer cause exit code 1 — only errors trigger a non-zero exit. Use --fail-on=warning for the stricter behavior where warnings also fail. Exit 4 takes precedence over warning/error policy codes.
Note
All check diagnostics outside the selected report payload (configuration notices and errors, deprecation messages, logging, and output-file notices) are written to stderr, not stdout. This means you can safely pipe the analysis output to a file or another tool without interference: bin/qmx check src/ --format=json > results.json.