Перейти к содержанию

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

0.19.0 - 2026-05-17

Breaking

  • ThresholdAwareOptionsInterface gains a static getOverrideValidator() accessor that returns the per-rule OverrideValidatorInterface strategy used to validate @qmx-threshold annotations. Custom Options classes in extension code must implement the new method or use StandardOverrideValidatorTrait; for the default warning ≤ error + non-negative semantics. See ADR 0013.

Changed

  • Invalid @qmx-threshold annotations now surface a rule-specific code (e.g. warning_exceeds_error, error_exceeds_warning, error_not_supported) as violationCode: annotation.invalid-threshold.<code> in JSON / SARIF / Checkstyle output; the human message is unchanged. Validators that provide a remediation hint (e.g. WarningOnly's "omit error=...") now flow through to recommendation so users see actionable follow-up.

Fixed

  • @qmx-threshold maintainability.index warning=N error=M annotations with N > M were silently rejected by the parser, even though the rule's defaults are warning=40 error=20 (inverted thresholds are the natural orientation). The bug was latent across releases — Maintainability annotations work for the first time in v0.19.
  • @qmx-threshold design.type-coverage warning=N error=M with N > M was rejected on the same parser invariant; type coverage is an inverted-threshold rule and now accepts the natural form.
  • @qmx-threshold design.data-class warning=N error=M was rejected when N > M, but the rule maps warning to wocThreshold (high) and error to wmcThreshold (low) — independent metrics on independent axes. The annotation now validates accordingly.
  • @qmx-threshold design.god-class warning=W error=E previously accepted the error value silently and then discarded it inside withOverride(). Explicit error=N is now rejected at parse time with a clear diagnostic; the shorthand form @qmx-threshold design.god-class N still works.

0.18.0 - 2026-05-16

Breaking

  • architecture.layers YAML schema is now an ordered list (long form only), not a map. The first layer whose patterns match a class FQN owns the class — declaration order is meaningful. Migration: replace layers: { name: pattern } with layers: [{ name: x, patterns: [pattern] }]. See ADR 0006.
  • RuleInterface::getCliAliases() removed. CLI aliases are now declared via the repeatable class-level attribute #[CliAlias('alias', 'optionName')]. Custom rules in extension code must drop the method and add attributes on the class.
  • Architecture-feature classes moved to a vertical slice under Qualimetrix\Architecture\{Domain,Configuration,Processing,Rules} per ADR 0010. Extension authors importing from the old Qualimetrix\Core\Architecture, Qualimetrix\Configuration\Architecture, Qualimetrix\Analysis\Architecture, or Qualimetrix\Rules\Architecture namespaces must update imports.

Changed

  • New rule architecture.layer-violation: declare layers in YAML and enforce allowed inter-layer dependencies. Membership supports patterns, suffix, attributes, implements, extends (combined via match: any | all); parameterised template layers expand against the observed class set ({var} capture); exclude: blocks hard-filter assignment; allow-list relations: whitelists restrict permitted DependencyType kinds; capture-binding ('app-{m}': ['domain-{m}']) constrains allows to same-instance edges for DDD bounded contexts. Incremental adoption via architecture.coverage; expansion capped by architecture.max_expanded_layers (default 500). See ADRs 0006–0008.
  • New diagnostics: architecture.empty-template (warning — template expanded to zero layers), architecture.unreachable-layer (info — layer pattern matched zero classes), architecture.potential-shadow (info — evidence-based detection of layers silently stealing classes from later, narrower layers).
  • New CLI command debug:layer-assignment <fqn>: per-class introspection of layer assignment — reports the assigned layer and which other layers' patterns would also have matched. Runs full Discovery + Collection so output matches qmx check byte-for-byte.
  • qmx.yaml.example includes a commented-out architecture: stanza demonstrating multi-criterion membership, exclude:, templates, vendor layers, allow: (plain, captured same-instance, long-form with relations:), coverage, and max_expanded_layers.

Fixed

  • @qmx-threshold annotations on design.type-coverage, design.god-class, and design.data-class previously had no effect — the Options classes did not implement ThresholdAwareOptionsInterface. The three Options now implement it and apply overrides per class.
  • architecture.layer-violation now respects @qmx-ignore suppressions placed on the offending class — the dependency visitor used absolute paths while the suppression map was keyed by relative paths.
  • architecture.layer-violation no longer false-positives mutual-allow when the two directions use disjoint relations: filters or allow_cross_instance: true.
  • architecture.max_expanded_layers now actually takes effect when set in YAML (previously silently camelCased and ignored). See ADR 0009.
  • architecture.allow source and target selectors now reject [ brackets at config-load time with an actionable hint suggesting {var} capture-variable syntax.
  • debug:layer-assignment now honours memory_limit from qmx.yaml.
  • Architecture configuration warnings (e.g. mutual-allow detection) now actually reach the user logger.
  • SARIF formatter $schema URL updated to the OASIS canonical location after the upstream repo reorganized.

0.17.0 - 2026-05-12

Fixed

  • health.typing no longer reports 0% for namespaces with no typeable declarations (e.g. marker interfaces used for Symfony Messenger routing). Empty type surface now yields 100% (vacuous truth) at namespace and project levels, matching the existing class-level semantic.
  • Disabling a health dimension via computed_metrics.health.X.enabled: false no longer breaks health.overall. Both enabled: false and exclude_health: [X] now follow the same pipeline — the dimension is removed and health.overall weights are renormalized across the remaining dimensions.

Changed

  • Excluding a health dimension when health.overall has been overridden with a non-canonical formula (one that does not match (health__dim ?? fallback) * weight) now throws an explicit error instead of silently dropping the formula. Custom formulas should handle disabled dimensions via ?? fallbacks.

0.16.0 - 2026-05-01

Changed

  • health.coupling namespace formula rewritten to use efferent-only signals (ce.avg, ce_packages.avg, ce.max, ce, distance). Stable contracts namespaces (high incoming, low outgoing dependencies) are no longer unfairly penalized by bidirectional CBO. Class- and project-level formulas are unchanged.
  • New aggregations for the ce metric at namespace and project levels: ce.avg, ce.max, ce.p95.

0.15.0 - 2026-04-04

Changed

  • Strict configuration validation: unknown section sub-keys (cache.typo), invalid value types (cache.enabled: "yes"), and unknown rule names (rules.complexty) now produce clear errors with "Did you mean?" suggestions
  • Warnings (e.g., unknown rule option keys) are now visible at default verbosity via stderr, without requiring -v

Fixed

  • Configuration warnings were invisible without -v flag due to NullLogger at default verbosity

0.14.0 - 2026-04-03

Changed

  • --exclude-namespace CLI option for violation suppression by namespace (prefix or glob), merged with exclude_namespaces from qmx.yaml

Fixed

  • Computed metric names with underscores (e.g., computed.my_score) were incorrectly normalized to camelCase in YAML config

0.13.0 - 2026-04-03

Changed

  • --show-suppressed now lists each suppressed violation with file, line, message, and rule name (was count-only)
  • exclude_paths and exclude_namespaces now support both prefix matching (src/Entity) and glob patterns (src/Metrics/*Visitor.php); simple directory/namespace names work without trailing /*
  • --exclude-health with invalid dimension name now produces an error instead of silently ignoring

Fixed

  • "No PHP files found" message shown when all files had parse errors — now shows "All N file(s) were skipped due to parse errors"

0.12.0 - 2026-04-03

Changed

  • LCOM4 rule: exclude_methods option to exclude specific methods from the cohesion graph (reduces false positives from interface-mandated methods like getName, getDescription)
  • Partial scope warning when analysis paths don't cover all composer.json autoload entries
  • coupling.instability: min_afferent option replaces skip_leaf — configurable minimum afferent coupling (Ca) threshold for skipping symbols (default: 1, skip Ca=0)
  • code-smell.boolean-argument: parameters with common boolean prefixes (is*, has*, can*, should*, will*, did*, was*) are now allowed by default (configurable via allowed_prefixes: [])
  • code-smell.error-suppression: allowed_functions option to whitelist functions where @ usage is acceptable (e.g., fopen, unlink)
  • Per-rule exclude_paths option for targeted violation suppression by file path patterns
  • @qmx-ignore tags now work in regular comments (//, /* */), not just PHPDoc docblocks
  • JSON format (--format=json) now outputs all violations by default (was limited to 50); use --format-opt=violations=50 to restore the old behavior
  • Global exclude_namespaces config option for suppressing violations by namespace prefix (like exclude_paths but for namespaces)
  • Computed metric formulas referencing non-existent metrics now produce a clear error instead of silently failing
  • Warnings (partial scope, unknown rules, missing composer.json) now go to stderr to avoid corrupting machine-readable output
  • Exit codes: config/input errors now return exit code 3 (was 1, overlapping with "warnings found"). Scheme: 0=clean, 1=warnings, 2=errors, 3=config error

Fixed

  • graph:export command crash due to -d shortcut conflict with global --working-dir

Removed

  • --analyze option — was misleading (analyzed all files regardless, only filtered violations like --report). Use --report instead
  • analyze command alias — use check instead
  • baseline.json — replaced with proper qmx.yaml configuration using new features

0.11.2 - 2026-04-02

Changed

  • Project qmx.yaml for self-analysis with tuned coupling thresholds and exclude_namespaces for Core value objects
  • qmx.yaml.example — comprehensive annotated example with documentation links, default values, and all available options (replaces qmx.yaml.dist)
  • parallel section in config file for setting worker count (was CLI-only via --workers)

Fixed

  • coupling section in config file was rejected as unknown key

0.11.1 - 2026-04-01

Changed

  • --memory-limit option and memory_limit config key to control PHP memory limit (e.g., --memory-limit=1G)
  • Removed hidden 512M memory limit override — PHP's memory_limit from php.ini is now respected by default

0.11.0 - 2026-04-01

Changed

  • Cognitive Complexity violations include breakdown of top contributors: Top: nested if +5 L12, foreach +4 L15, &&/|| +1 L22
  • NPath Complexity violations include multiplicative chain: Chain: ×6 if/else L25, ×4 match L31, ×3 switch L20

0.10.0 - 2026-03-29

Breaking

  • Rule IDs code-smell.god-class and code-smell.data-class renamed to design.god-class and design.data-class
  • --format=health now produces a text table (was HTML). Use --format=html for the interactive HTML report

Changed

  • @qmx-threshold annotations for per-class/method threshold overrides in source code
  • Framework CBO distinction: cbo_app and ce_framework metrics separate application from framework coupling
  • Full dependency graph in --analyze=git:* modes — coupling metrics now correct in partial analysis
  • --group-by=class|namespace for JSON output
  • Worst contributors per health dimension in --format=health, configurable via --format-opt=contributors=N
  • Violation density metric (violationDensity: violations per 100 LOC) in worst offenders
  • NPath violations include severity categories (low/moderate/high/very high/extreme)
  • VO constructor exemption for long-parameter-list — relaxed thresholds (vo-warning, vo-error)
  • LCOM4: stateless methods grouped together, reducing false positives on utility classes
  • Duplication violations include content preview hint
  • Martin Diagram view in HTML report with parent-namespace instability/abstractness/distance
  • NamespaceTree: canonical namespace hierarchy replaces flat aggregator
  • Warn when @qmx-threshold targets rules that don't support overrides
  • Decomposed 13 large classes into focused components (SRP)

Fixed

  • Health: complexity contributors always empty; recalibrated formulas for per-method aggregation
  • Metrics: namespace .max/.avg/.p95 now aggregated from raw method values, not pre-aggregated class values
  • Reporting: aggregation suffixes stripped from metric keys in health text; uppercase metric keys fixed
  • Git: absolute path mismatch in GitScopeFilter for --analyze=git:*
  • Security: hardcoded credentials no longer flag dot-notation identifiers (e.g., config.database.host)
  • Duplication: self-duplication for overlapping/adjacent ranges in same file eliminated
  • Removed dead weighted average from aggregation, dead GitFileDiscovery class

0.9.2 - 2026-03-26

Fixed

  • CI: refactored ConfigDataNormalizer to eliminate complexity violations (NPath 442K → 4), regenerated baseline

0.9.1 - 2026-03-26

Changed

  • "Top issues by impact" redesigned: file path on the first line (clickable in terminal), rule name + message + symbol context on the second line. Shows recommendation when available. Handles architectural violations ([project])
  • HTML report: violations table now shows File column, uses violationCode (more specific than ruleName), and prefers recommendation over technical message

0.9.0 - 2026-03-26

Changed

  • Analysis presets: --preset=strict|legacy|ci for one-flag configuration. Multiple presets can be combined (--preset=strict,ci). Custom preset files supported via path (--preset=./team.yaml)
  • rules key now uses deep merge across pipeline stages — partial rule overrides in qmx.yaml no longer replace entire preset rule configurations

0.8.0 - 2026-03-26

Changed

  • Effort-aware prioritization: "Top issues by impact" section in summary and JSON output. Violations ranked by classRank × severity × remediation time — answering "what should I fix first?" New --top=N option (default 10, --top=0 to disable)

0.7.1 - 2026-03-25

Changed

  • CBO metric no longer counts PHP built-in classes (Exception, DateTime, Iterator, etc.) — only project and third-party dependencies contribute to coupling scores. Dependency graph exports (graph:export) are also affected

0.7.0 - 2026-03-25

Changed

  • --fail-on now defaults to error — warnings are shown in output but don't cause non-zero exit code. Use --fail-on=warning or fail_on: warning in config for the old behavior
  • threshold shorthand for rule configuration — sets both warning and error to the same value, making all violations errors at that threshold
  • Health score labels renamed to industry-standard terminology: Excellent / Good / Fair / Poor / Critical (was Strong / Good / Acceptable / Weak / Critical)
  • Line numbers shown only for violations with precise locations (method/class level), not for file-level violations

Fixed

  • Technical debt breakdown now calculated from all violations, not just the truncated display list

Breaking

  • Default --fail-on changed from warning to error. CI pipelines relying on exit code 1 for warnings must add --fail-on=warning explicitly

0.6.0 - 2026-03-18

Fixed

  • Baseline now correctly matches file-level violations (duplication, code smell, security rules) — previously ~150 violations passed through a freshly generated baseline
  • Duplicate code block locations are now sorted deterministically, making baseline entries stable across runs
  • File paths are normalized to relative (vs CWD) to prevent mismatches with absolute or ./-prefixed paths

Breaking

  • Baseline version bumped to 5 — existing v4 baselines must be regenerated with --generate-baseline

0.5.0 - 2026-03-18

Changed

  • exclude_namespaces is now a universal per-rule option available for any rule, not just coupling rules

Breaking

  • exclude_namespaces for coupling.cbo and coupling.instability moves from nested namespace: to top-level rule config
  • exclude_namespaces now filters violations at all levels (class + namespace), not just namespace level

0.4.0 - 2026-03-18

Changed

  • Health scores redesigned: 5-tier labels (Excellent / Good / Fair / Poor / Critical), recalibrated formulas for complexity (avg + P95 + sqrt(max) penalties), coupling (efferent-based, P95 + sqrt-scaled max), cohesion (TCC neutral value for small classes), maintainability (MI anchor shifted to 30). --exclude-health=DIMENSION to exclude dimensions from scoring
  • Computed metrics: 6 built-in health.* scores plus user-definable computed.* metrics via Symfony Expression Language formulas, per-level formulas, threshold-based violations
  • Summary-first CLI: --format=summary is now the default output — health bars, worst offenders, violation summary, and contextual hints in one screen
  • Drill-down navigation: --namespace=App\Service and --class=App\Service\UserService for progressive filtering with auto-enabled --detail. Namespace/class health scores shown in drill-down headers
  • Interactive HTML report: --format=health — self-contained D3.js treemap, health coloring, search, metric selector, dark mode. Use --output / -o to write any format to a file
  • JSON output redesigned: summary-oriented with meta, summary, health decomposition, worstNamespaces, worstClasses, violations (top 50 by default). --format-opt=violations=all|0|N, --format-opt=top=N
  • New rules: code-smell.long-parameter-list, code-smell.unreachable-code, code-smell.identical-subexpression, design.god-class (Lanza & Marinescu), design.data-class, code-smell.constructor-overinjection, code-smell.unused-private, design.type-coverage, duplication.code-duplication (Rabin-Karp token hashing), coupling.class-rank (PageRank), security.sql-injection, security.xss, security.command-injection, security.sensitive-parameter, security.hardcoded-credentials
  • New output formats: --format=metrics (raw metric values), --format=github (PR annotations)
  • Technical debt: remediation time estimates per violation, aggregated debt in reports, --detail shows per-rule breakdown
  • --fail-on=error option to allow warnings without failing the build
  • --include-generated to override automatic @generated file skipping
  • --disable-rule=duplication now skips the memory-intensive detection phase entirely (not just violations). Same for circular dependency detection
  • Violation messages improved: actionable recommendations, parameter names in boolean-argument, coupling direction in CBO, CCN divergence hints, top-5 dependencies in coupling violations
  • bin/qmx graph:export --format=json — dependency graph as aggregated JSON adjacency list
  • composer benchmark:check regression suite — validates health scores against 15 open-source projects
  • llms.txt and llms-full.txt — machine-readable documentation for AI coding agents

Fixed

  • Metric algorithm corrections: cognitive complexity nesting in closures, cyclomatic complexity for match arms, NPath formulas aligned with Nejmeh/PMD standards, Maintainability Index class-level aggregation, WOC formula, RFC for traits/enums, abstractness formula for interfaces
  • Anonymous class isolation: methods inside anonymous classes no longer attributed to enclosing class (CCN, NPath, Halstead, ParameterCount, UnreachableCode visitors)
  • Suppression system (@qmx-ignore): fully wired into pipeline, @qmx-ignore-next-line scoped to single line, file-level regex fixed, symbol-level no longer leaks to file-level
  • Output formatters: SARIF schema compliance (paths, locations, helpUri), Checkstyle/Text relative paths, GitLab project-level path, JSON NaN/Infinity handling
  • Configuration: --config now functional, exclude_paths accepted, YAML key normalization preserves rule IDs, deep merge for CLI overrides, fromArray([]) applies defaults
  • Security rules: XSS and command injection detect superglobals in interpolated strings
  • Infrastructure: cache hit skips AST traversal, runtime state reset between runs, baseline v3 migration errors, parallel worker validation

Breaking

  • --format=html renamed to --format=health; --format=metrics-json renamed to --format=metrics
  • --format=summary is now the default (was text). Use --format=text for the previous behavior
  • --format=json redesigned — no longer PHPMD-compatible. See documentation for new schema
  • JSON field humanMessage renamed to recommendation in violation objects
  • Health scores: 5-tier labels (was 4-tier), recalibrated formulas — baselines may need regeneration
  • NPath values changed due to formula corrections — baselines may need regeneration
  • Baseline version 3 no longer supported — regenerate with --generate-baseline

0.3.0 - 2026-03-08

Changed

  • CLI command renamed from analyze to check, with aliases for backward compatibility
  • Canonical config file name is now qmx.yaml
  • exclude_paths option for violation suppression by file path patterns
  • MkDocs Material documentation website (EN/RU)
  • Version derived from Composer/git tag instead of hardcoded constant

Fixed

  • LCOM4 calculation aligned with original Hitz & Montazeri specification
  • Maintainability Index accuracy: use ELOC instead of physical LOC
  • --workers=0 semantics corrected

0.2.2 - 2026-03-05

Changed

  • Rule NAME constants follow group.rule-name format (kebab-case)
  • SizeRule split into MethodCountRule and ClassCountRule
  • CouplingRule split into InstabilityRule and CboRule
  • RuleMatcher utility for prefix-based rule matching
  • ANSI colors, grouping, and FormatterContext for formatters
  • Baseline v3 format with duplicate NAME validation
  • Suppression system updated for dotted rule names and prefix matching

0.2.1 - 2026-03-05

Fixed

  • TTY output written line by line to prevent macOS terminal truncation

0.2.0 - 2026-03-05

Changed

  • Category filtering for rules
  • Default thresholds calibrated

0.1.1 - 2026-03-04

Changed

  • violationCode field in Violation for stable baseline hashing
  • Improved violation messages with thresholds and actionable advice

Fixed

  • Namespace-level violation display and minClassCount filter

0.1.0 - 2026-03-04

Initial release.

  • PHP static analysis CLI tool
  • Metrics: Cyclomatic Complexity, Cognitive Complexity, NPATH, Halstead, Maintainability Index
  • Metrics: RFC, Instability, Abstractness, Distance from Main Sequence
  • Metrics: TCC/LCC, LCOM4, WMC, LOC, DIT, NOC
  • Rules with configurable thresholds
  • Circular dependency detection with DOT graph export
  • Output formats: Text, JSON, Checkstyle, SARIF, GitLab Code Quality
  • Parallel file processing via amphp/parallel
  • Git integration: --staged, --diff
  • Baseline support with @qmx-ignore suppression tags
  • AST caching, progress bar, PSR-3 logging
  • Git hook installation (hook:install, hook:status)
  • Symfony DI with autowiring and autoconfiguration
  • GitHub Actions workflow