Skip to content

Default Thresholds Reference

This page lists the default thresholds for every rule in Qualimetrix. When a metric exceeds the warning threshold, a warning is reported. When it exceeds the error threshold, an error is reported.

Complexity Rules

Rules that measure how hard code is to understand and test.

Rule ID Level Warning Error Scope
Cyclomatic Complexity complexity.ccn Method 10 20 Method
Cyclomatic Complexity complexity.ccn Class (max) 30 50 Class
Cognitive Complexity complexity.cognitive Method 15 30 Method
Cognitive Complexity complexity.cognitive Class (max) 30 50 Class
NPath Complexity complexity.npath Method 200 1000 Method
NPath Complexity complexity.npath Class (max) 500 1000 Class (disabled)
WMC complexity.wmc - 50 80 Class

Cyclomatic Complexity counts the number of independent paths through a method. A method with CCN of 10 has 10 different paths to test.

Cognitive Complexity measures how hard code is to read. Unlike cyclomatic complexity, it penalizes nested structures more heavily.

NPath Complexity counts the number of possible execution paths. It grows much faster than cyclomatic complexity for code with many conditions.

WMC (Weighted Methods per Class) is the sum of cyclomatic complexities of all methods in a class. A high WMC means the class is doing too much.

Size Rules

Rules that check if classes and namespaces are too large.

Rule ID Warning Error Scope
Method Count size.method-count 20 30 Class
Class Count size.class-count 15 25 Namespace
Property Count size.property-count 15 20 Class

Design Rules

Rules that check class design and inheritance structure.

Rule ID Warning Error Scope
LCOM cohesion.lcom 3 5 Class
NOC design.noc 10 15 Class
DIT design.dit 4 6 Class
Parameter Type Coverage design.type-coverage.param 80 (below) 50 (below) Class
Return Type Coverage design.type-coverage.return 80 (below) 50 (below) Class
Property Type Coverage design.type-coverage.property 80 (below) 50 (below) Class

LCOM (Lack of Cohesion of Methods) measures how well the methods in a class belong together. A high LCOM suggests the class should be split.

NOC (Number of Children) counts direct subclasses. Too many children means the parent class may be too general.

DIT (Depth of Inheritance Tree) counts how many levels of inheritance a class has. Deep hierarchies are harder to understand and maintain.

Type Coverage measures the percentage of typed declarations. Unlike most rules, violations are reported when values fall below the threshold.

Coupling Rules

Rules that check how tightly classes and namespaces are connected to each other.

Rule ID Warning Error Scope
CBO coupling.cbo 14 20 Class
CBO coupling.cbo 14 20 Namespace
Instability coupling.instability 0.8 0.95 Class
Instability coupling.instability 0.8 0.95 Namespace
Distance coupling.distance 0.3 0.5 Namespace
ClassRank coupling.class-rank 0.02 0.05 Class
Unmatched framework namespace coupling.unmatched-framework-namespace — (warning, fixed) Project

CBO (Coupling Between Objects) counts the number of other classes a class depends on. High coupling makes code harder to change.

Instability is a ratio from 0 (fully stable) to 1 (fully unstable). A class that depends on many others but is not depended upon is unstable. By default, min_afferent: 1 -- classes and namespaces with no dependents (Ca=0) are skipped since they have I=1.0 by definition. Set to 2 to also skip symbols with only one dependent.

Distance from the Main Sequence measures how well a namespace balances abstractness and stability. A distance close to 0 is ideal.

ClassRank uses the PageRank algorithm on the dependency graph to identify the most critical classes. Ranks sum to 1.0 across the project; a high rank means many (or important) classes depend on it. Thresholds are automatically adjusted by project size using sqrt scaling (calibrated for 100 classes).

Unmatched framework namespace has no numeric threshold: it reports a framework-namespaces prefix that no name in the run falls under, once per prefix, at a fixed warning severity. It is an ordinary finding, so --fail-on, --disable-rule and the baseline all reach it. See Coupling rules.

Maintainability Rules

These rules are inverted: a violation is reported when the metric falls below the threshold, not above it.

Rule ID Warning (below) Error (below) Scope
Maintainability Index maintainability.mi 40 20 Method

Maintainability Index combines complexity, lines of code, and Halstead metrics into a single score from 0 to 100. Higher is better. A score below 20 means the code is very hard to maintain.

Architecture Rules

Rules that detect structural problems in the dependency graph. These rules do not use numeric thresholds — they either find a structural violation or they don't.

Rule ID Severity Default Notes
Circular Dependencies architecture.circular-dependency Error (direct) / Warning (transitive) enabled Direct cycles (size 2) reported as Error; longer cycles as Warning. See Architecture rules.
Layer Violations architecture.layer-violation Warning (configurable) enabled (no-op without architecture.layers) No numeric thresholds; only an enabled flag and a severity selector. Active only when the top-level architecture: YAML section declares layers. See Architecture rules.
Unreachable Layer architecture.unreachable-layer Error (fixed, not configurable) enabled (fires only with architecture.layers) One diagnostic per declared layer whose patterns matched zero classes and zero dependency-edge ends. Catches a broader pattern earlier in the order silently swallowing a later layer; counting edge ends too keeps vendor-only layers (e.g. ClickHouseDB\**, matched only as a dependency target) from being reported unreachable. This is a configuration error: it fails the run unconditionally, independent of --fail-on, and cannot be baselined or suppressed. The removed unreachable_layer_severity option no longer exists — severity is fixed. A layer declared pending: true is skipped by this diagnostic.
Pending Layer Matched architecture.pending-layer-matched Error (fixed, not configurable) enabled (fires only for layers declared pending: true) One diagnostic per layer declared pending: true — "code not written yet", the declaration that suppresses architecture.unreachable-layer for it — whose criteria matched at least one class or dependency-edge end after all. A match counts even when a broader layer declared earlier won every assignment, which is the case a count of assignments would miss. This is a configuration error: it fails the run unconditionally, independent of --fail-on, and cannot be baselined or suppressed. See Architecture rules.
Potential Shadow architecture.potential-shadow Error (fixed, not configurable) enabled (fires only with architecture.layers) Evidence-based detection of a more specific layer declared after a broader one, which can therefore never win in its own area. Overlap on its own is not reported: the narrow-before-broad idiom, up to a final ** catch-all, is legal and silent. One diagnostic per (assigned, shadowed) pair. This is a configuration error: it fails the run unconditionally, independent of --fail-on, and cannot be baselined or suppressed. The removed potential_shadow_severity option no longer exists — severity is fixed.
Empty Template architecture.empty-template Error (fixed, not configurable) enabled (fires only with template layers) One diagnostic per template layer that expanded to zero concrete instances — silently disables the policy attached to it. Typical causes: typo in the template pattern, every candidate excluded, or single-segment {var} where {var:**} is needed. This is a configuration error: it fails the run unconditionally, independent of --fail-on, and cannot be baselined or suppressed. The removed empty_template_severity option no longer exists — severity is fixed.
Architecture Coverage Gap architecture.coverage-gap Warning or Error (per coverage-gap mode) disabled (coverage-gap: ignore) One aggregated diagnostic when architecture.coverage-gap is warn or error and analysed logical classes (including isolated classes with no edges) or dependency-edge endpoints are outside every declared layer. The printed word matches the configured coverage-gap: mode, but this is still a configuration error: whenever it fires it fails the run unconditionally, independent of --fail-on, and it cannot be baselined or suppressed. coverage-gap: ignore remains the way to decline the diagnostic entirely.
Unassigned Class architecture.unassigned-class Warning or Error (per mode) disabled (mode: ignore) One aggregated diagnostic counting the analysed class-like declarations (classes, interfaces, traits, enums) that match no declared layer. Unlike architecture.coverage-gap it never counts a dependency-edge end, so vendor code the project cannot classify does not enter the number. The reported metric value is the absolute count, so a project can accept the current count in a baseline and ratchet it down. Set with its own mode option (CLI: --unassigned-class-mode).

Discovery Rules

The built-in discovery.unmatched-exclude rule reports on the run's own file selection: an --exclude value or an exclude: entry that matched no directory. It has no numeric thresholds.

Channel Severity Default Notes
discovery.unmatched-exclude Warning (fixed, not configurable) enabled Ordinary finding, not a configuration error: a shared configuration may legitimately name a path one repository does not have. Reported at project level, and only on a run whose paths cover the project's production autoload roots.

Suppression Rules

The built-in suppression.configuration rule reports on the run's own suppression configuration: a suppress_paths or suppress_namespaces value, global or per-rule, that names nothing this run holds. It has no numeric thresholds.

Channel Severity Default Notes
suppression.unmatched-path Warning (fixed, not configurable) enabled A global suppress_paths value matching no analysed file. Reported at project level, only on a run covering the production autoload roots, and never written into a baseline.
suppression.unmatched-namespace Warning (fixed, not configurable) enabled The same for suppress_namespaces against the namespaces the run declared.
suppression.unmatched-rule-ledger Warning (fixed, not configurable) enabled The same for either key configured under rules.<name>.

Annotation Rules

The built-in annotation.directive rule reports inline @qmx-* directives that address nothing, cannot apply, or no longer do anything. It has no numeric thresholds and reports through four channels, each its own diagnostic. See Annotation rules for the full reference and Baseline for how directives interact with suppression and baselines.

Channel Severity Default Notes
annotation.unresolved-directive Error (fixed, not configurable) enabled Configuration error: the directive names a channel that does not exist. Fails the run unconditionally, independent of --fail-on; cannot be baselined or suppressed.
annotation.unsupported-threshold Error (fixed, not configurable) enabled Configuration error: @qmx-threshold targets a rule that declares no threshold override support. Fails the run unconditionally; cannot be baselined or suppressed.
annotation.invalid-threshold Error (fixed, not configurable) enabled Configuration error: the @qmx-threshold payload itself is malformed. Fails the run unconditionally; cannot be baselined or suppressed.
annotation.unused-directive Info (configurable via unused_directive_severity) enabled Ordinary finding, not a configuration error: the directive is valid but nothing it addressed fired this run. Can be baselined, dropped by the top-level suppress_paths or narrowed by a git scope; suppress_namespaces and the rule's own exclusions do not reach it. It is the one channel no @qmx-ignore may address.

Code Smell Rules

These rules detect specific patterns that are usually bad practice. Most do not have numeric thresholds -- they either find the pattern or they don't. Two rules (Long Parameter List and Unreachable Code) use numeric thresholds.

Rule ID Warning Error Status
Constructor Over-injection code-smell.constructor-overinjection 8 params 12 params enabled
Data Class design.data-class WOC ≤ 33%, WMC ≤ 10 enabled
God Class design.god-class WMC ≥ 47, TCC < 0.33, LCOM ≥ 3, LOC ≥ 300 (3 of 4) enabled
Boolean Argument code-smell.boolean-argument enabled (allowed_prefixes: is, has, can, should, will, did, was; flag_promoted_properties: false)
count() in Loop code-smell.count-in-loop enabled
Debug Code code-smell.debug-code always enabled
Empty Catch code-smell.empty-catch always enabled
Error Suppression code-smell.error-suppression always enabled (allowed_functions: [])
eval() code-smell.eval always enabled
exit()/die() code-smell.exit always enabled
goto code-smell.goto always enabled
Superglobals code-smell.superglobals always enabled
Long Parameter List code-smell.long-parameter-list 4 params (VO: 8) 6 params (VO: 12) enabled
Unreachable Code code-smell.unreachable-code 1 2 enabled
Unused Private code-smell.unused-private always enabled
Identical Sub-expression code-smell.identical-subexpression always enabled

Duplication Rules

Rules that detect duplicated code.

Rule ID Warning Error Scope
Code Duplication duplication.clone <50 lines >=50 lines Method

Code Duplication detects duplicate code blocks. Configured with min_lines: 5 and min_tokens: 70 -- blocks shorter than these thresholds are ignored. Duplicates under 50 lines produce a warning; 50 lines or more produce an error.

Security Rules

Rules that detect potential security vulnerabilities.

Rule ID Severity Default
Hardcoded Credentials security.hardcoded-credentials Error enabled
SQL Injection security.sql-injection Error enabled
XSS security.xss Error enabled
Command Injection security.command-injection Error enabled
Sensitive Parameter security.sensitive-parameter Warning enabled

Hardcoded Credentials detects passwords, API keys, and tokens hardcoded directly in source code.

SQL Injection detects superglobals used in SQL contexts without parameterized queries.

XSS detects unsanitized superglobals in echo/print statements.

Command Injection detects superglobals passed to shell execution functions without escaping.

Sensitive Parameter detects parameters with sensitive names missing the #[\SensitiveParameter] attribute.

How to Customize Thresholds

Using a YAML Config File

Create an qmx.yaml file in your project root:

rules:
  complexity.ccn:
    callable:
      warning: 15
      error: 30
    class:
      max_warning: 40
      max_error: 60

  size.method-count:
    warning: 25
    error: 40

  coupling.cbo:
    warning: 18
    error: 25

  maintainability.mi:
    warning: 30
    error: 15

Threshold Shorthand

If you want a single pass/fail cutoff where all violations are errors, use the threshold key instead of separate warning/error:

rules:
  complexity.ccn:
    callable:
      threshold: 15    # equivalent to warning: 15, error: 15

  size.method-count:
    threshold: 25

  coupling.cbo:
    class:
      threshold: 18

This sets both warning and error to the same value, so every violation at this level is an error. Useful in CI where you want a simple pass/fail threshold. You cannot mix threshold with explicit warning/error keys in the same rule level.

Each type-coverage dimension is its own rule, so each takes its own bare threshold:

rules:
  design.type-coverage.param:
    threshold: 90
  design.type-coverage.return:
    threshold: 90
  design.type-coverage.property:
    threshold: 80

Computed metrics (health scores) also support threshold:

computed_metrics:
  health.complexity:
    threshold: 50      # score below 50 → error

Then run the analysis with the config file:

vendor/bin/qmx check src/ --config=qmx.yaml

Disabling Rules

To disable a rule entirely, set enabled: false:

rules:
  code-smell.boolean-argument:
    enabled: false

Disabling a Group of Rules

You can disable all rules in a group via the CLI:

vendor/bin/qmx check src/ --disable-rule=code-smell.*

code-smell.* disables every descendant of the code-smell group; the bare code-smell prefix without the star is now an error, not a shorthand for the group.

Using the CLI

Override thresholds from the command line:

vendor/bin/qmx check src/ --disable-rule=complexity.npath

Suppressing Individual Violations

Add @qmx-ignore in a docblock to suppress a specific violation. @qmx-ignore addresses a channel, and complexity.ccn is one channel reporting at two levels (callable and class). The bare channel name suppresses both levels; narrow it to one with :callable or :class:

/**
 * @qmx-ignore complexity.ccn:callable
 */
function complexButNecessary(): void
{
    // ...
}

You can also suppress every rule in a group with the same wildcard form:

/**
 * @qmx-ignore complexity.*
 */

See Baseline for the full selector syntax.