Форматы вывода¶
Qualimetrix поддерживает 11 форматов вывода. Выбирай тот, который подходит для твоего рабочего процесса.
summary (по умолчанию)¶
Обзор здоровья проекта с оценками, худшими нарушителями и сводкой нарушений. Это вывод CLI по умолчанию, предназначенный для быстрой оценки состояния проекта.
Когда использовать: Локальная разработка, быстрый обзор здоровья проекта.
Основные возможности:
- Общий бар здоровья плюс по одному бару на измерение (сложность, связность, связанность, типизация, сопровождаемость), за каждым — декомпозиция с
↳, детализирующая метрики, из которых сложилась оценка - Оценки в процентах, а не в сырых баллах
- Однострочные списки
Worst namespaces/Worst classes, каждая запись с оценкой впереди, и хвост+N more (use ...), когда список был обрезан. Повторяющееся имя класса получает суффикс(<файл>)или(<файл>#<n>)для следующего объявления в том же файле; у первого объявления в каждом файле номер опускается. - Секция
Top issues by impact: ранжированный список самых влиятельных отдельных нарушений — severity, impact score, файл, оценка времени на исправление, канал правила, сообщение и символ - Количество нарушений с оценкой технического долга (включая плотность долга на 1K LOC)
- Несколько контекстных
Hints:для следующих шагов
Пример вывода:
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 in subtree, 11 violations, 3.8/100 LOC)
55.9 App\Service\Order (4 classes in subtree, 7 violations, 2.1/100 LOC)
61.3 App\Repository (9 classes in subtree, 5 violations, 0.9/100 LOC)
+5 more (use --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-opt=top=12)
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 list violations (up to 200; --detail=all for every one) | --namespace='subtree:App\Billing\Invoice' to drill down | --format=html -o report.html for full report
Docs: https://qualimetrix.dev · AI agents: https://qualimetrix.dev/llms.txt
Флаг, управляющий числом записей в Top issues by impact, описан на странице CLI Options.
Детализация с --namespace и --class:
# Показать нарушения для конкретного поддерева пространства имён
bin/qmx check src/ --namespace='subtree:App\Service'
# Показать нарушения для конкретного класса
bin/qmx check src/ --class=App\\Service\\UserService
Детализация сужает то, что показывает отчёт, но не то, что решает код
возврата. Поэтому строка счётчиков говорит о выборке («… in this scope»),
называет нарушения вне её, которые решают код возврата, и берёт цвет от всего
прогона: чистое поддерево проекта с ошибками печатает
No violations in this scope. 9 outside it (5 errors, 4 warnings) decide the exit code,
а не зелёное No violations found.. --format=text делает то же в своей
итоговой строке.
Структурированные форматы тоже перечисляют только выборку, и каждый сообщает, что осталось вне её, в том канале, которым уже пользуется для диагностики о самом документе. Формат без такого канала вместо этого отклоняет выборку:
| Формат | Что осталось вне выборки |
|---|---|
json |
Объект верхнего уровня outOfScope: violationCount, errorCount, warningCount, infoCount, identities |
metrics |
Те же счётчики и идентичности outOfScope, что и в JSON |
sarif |
Уведомление уровня note в runs[0].invocations[0].toolExecutionNotifications[] с дескриптором QMX-DRILL-DOWN-OUT-OF-SCOPE |
gitlab |
Отказ с кодом 3 до анализа: виджет merge request считает каждую запись проблемой |
checkstyle |
Отказ с кодом 3 до анализа: читатель Checkstyle считает каждую запись ошибкой |
github |
Строка ::notice title=drill-down.out-of-scope:: |
html |
Баннер над отчётом |
suppressed |
Отказывает при селекторе: документ описывает весь прогон |
json и metrics несут outOfScope в каждом документе: null без выборки и
нулевые счётчики, когда вне выборки ничего не осталось. sarif, github и
html добавляют свою запись, только когда вне выборки что-то есть. Код возврата
вычисляется по выборке и outOfScope вместе, поэтому чистая выборка может
завершиться с кодом 2.
Селектор неймспейса использует объявленный неймспейс находки. Для файлового
агрегата (annotation.*, duplication.clone) подходит любой неймспейс,
открытый в файле или принадлежащий его декларациям. Если нет ни того ни другого,
используется глобальный неймспейс.
--class не выбирает файловый агрегат. suppress_namespaces намеренно не
делает этот файловый шаг: используй suppress_paths. Перекрывающиеся
namespace exclusions сохраняют кредит первому совпадению.
suppressed --namespace/--class отказывает с кодом 3: документ composition
описывает весь прогон. --show-suppressed допустим; заголовок списка в stderr
говорит, что он относится ко всему прогону и селектор к нему не применён.
Анализ и итоговый код выхода всегда относятся ко всему прогону.
Режим детализации с --detail:
# Добавить группированный список нарушений (лимит по умолчанию: 200)
bin/qmx check src/ --detail
# Показать все нарушения (без лимита)
bin/qmx check src/ --detail=all
# Пользовательский лимит
bin/qmx check src/ --detail=50
--detail включает список находок с необязательным потолком. --detail=N
выбирает N худших по severity, затем impact, до группировки вывода. Без ранга
используется место находки. --detail=0 равен --detail=all; неверные значения
отказывают с кодом 3 до анализа. --top управляет отдельным списком impact.
Подробный текстовый вывод
--detail включается автоматически при использовании --namespace или --class. Флаг также работает с --format=text: добавляет группированный список нарушений после компактного построчного вывода.
text¶
Компактный вывод, одна строка на нарушение. Совместим с форматом ошибок GCC/Clang, поэтому нарушения кликабельны в большинстве терминалов и IDE.
Когда использовать: Локальная разработка, быстрые проверки, передача в grep или wc.
Пример вывода:
src/Repository/OrderRepository.php: error[coupling.class-rank]: ClassRank share is 12.98× uniform, exceeds threshold of 10.00×. This class is a critical hub — changes have wide impact (OrderRepository)
src/Repository/OrderRepository.php: warning[complexity.ccn]: Cyclomatic complexity is 10, reaches threshold of 10. Consider extracting methods or simplifying conditions (OrderRepository::findByCriteria)
src/Service/UserService.php:9: warning[code-smell.error-suppression]: Error suppression (@) on file_get_contents() - handle errors explicitly
src/Service/UserService.php: warning[complexity.ccn]: Cyclomatic complexity is 14, exceeds threshold of 10. Consider extracting methods or simplifying conditions (UserService::calculate)
Qualimetrix 0.26.0: 1 error(s), 3 warning(s) in 2 file(s)
Analysis complete: 2 analyzed, 0 generated file(s) excluded.
Technical debt: 1h 40min
Docs: https://qualimetrix.dev · AI agents: https://qualimetrix.dev/llms.txt
Формат строки: есть три формы строк — в зависимости от того, к чему относится находка.
- Нарушение, привязанное к конкретному оператору, несёт номер строки:
файл:строка: уровень[кодНарушения]: сообщение (символ). - Находка уровня класса или метода, где правило судит о декларации целиком, а не об одном операторе — например,
complexity.ccn,complexity.wmc,coupling.class-rank— вместо этого опускает сегмент строки:файл: уровень[кодНарушения]: сообщение (символ), хотя та же находка несётlineв--format=json. - Находка уровня проекта (у неё вообще нет владеющего файла — например,
architecture.unreachable-layer) опускает и сегмент файла:[project]: уровень[кодНарушения]: сообщение, без завершающего(символ). На самоанализе этого проекта третья форма — не редкий случай:bin/qmx check src/Analysis/Evidence/Complexity --format=textпечатает строки уровня проекта для большинства вывода.
json¶
Машиночитаемый JSON-вывод. Формат, ориентированный на сводную информацию: оценки здоровья, худшие нарушители и все нарушения.
Когда использовать: Пользовательские скрипты, дашборды, программная обработка.
Ключи верхнего уровня: meta, summary, outOfScope, coverage, projectScope (см. Охват проекта во всех форматах), configurationDiagnostics, computedMetricOutcomes, abstentions, inheritanceDepthOutcomes, health, worstNamespaces, worstClasses, topIssues, violations, violationsMeta, плюс violationGroups, когда передан --group-by — без него ключа нет вовсе, это не пустой объект.
configurationDiagnostics перечисляет предупреждения о конфигурации, которую прогон принял, — те же, что check печатает в stderr, — и равен [], когда их нет. Каждая запись — {"message": "…", "source": [{"kind": "preset", "name": "…", "imported_by": null}, …]}: source называет каждый слой, о котором предупреждение, от младшего к старшему, в той же форме, что source ошибки конфигурации. Например, only_rules: [] в qmx.yaml поверх пресета, фильтрующего правила, законно и даёт одну запись, называющую оба слоя.
Каждый успешный JSON-документ check содержит computedMetricOutcomes, в том
числе пустой массив. Непустые записи группируют недостающие входы и null-результаты
авторских формул по метрике и уровню: metric, level, missingKeysCount,
noValueCount, missingKeys и subjects. Ключи — точное объединение недостающих
ключей; предметы — не более трёх детерминированных канонических точных идентичностей.
Это отдельная сводка, не находки и не configurationDiagnostics.
Успешный check с разрешённым форматом ровно json и выводом в stdout пишет полный
исходный документ при обычной и тихой подробности, в том числе под
--silent; отвечающие команды сохраняют этот документ.
JSON из конфигурации следует тому же пути. Другие отвечающие форматы тоже
публикуются под quiet и silent; файловые адресаты сохраняют формат.
meta называет инструмент, записавший документ: version, package, timestamp и два адреса документации — docs, сайт документации, и llmsTxt, индекс для ИИ-агентов. Оба адреса есть в каждом JSON-отчёте, у которого есть объект-конверт; см. исключения в Адреса документации в JSON-отчётах.
Пример вывода:
{
"meta": {
"version": "1.0.0",
"package": "qmx",
"timestamp": "2025-01-15T10:30:00+00:00",
"docs": "https://qualimetrix.dev",
"llmsTxt": "https://qualimetrix.dev/llms.txt"
},
"summary": {
"filesAnalyzed": 45,
"filesSkipped": 0,
"duration": 1.234,
"violationCount": 3,
"errorCount": 2,
"warningCount": 1,
"infoCount": 0,
"techDebtMinutes": 270,
"debtPer1kLoc": 2.1
},
"outOfScope": null,
"projectScope": {
"state": "covered",
"uncoveredAutoloadTargets": [],
"unjudgedChannels": [],
"unjudgedValues": [],
"reasons": []
},
"configurationDiagnostics": [],
"computedMetricOutcomes": [],
"abstentions": [],
"inheritanceDepthOutcomes": [],
"health": {
"complexity": {
"score": 78.0,
"label": "Good",
"threshold": {
"warning": 50,
"error": 30
},
"coverage": {
"state": "measured",
"measured": 2263,
"eligible": 2263,
"ratio": 1.0,
"unit": "callables",
"basis": "complexity.ccn.count",
"reason": null
},
"decomposition": [
{
"metric": "complexity.ccn.avg",
"humanName": "Cyclomatic complexity",
"value": 4.12,
"coverage": {
"state": "measured",
"measured": 2263,
"eligible": 2263,
"ratio": 1.0,
"unit": "callables",
"basis": "complexity.ccn.count",
"reason": null
},
"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": "Good",
"threshold": {
"warning": 50,
"error": 30
},
"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": "Fair",
"reason": "high coupling",
"violationCount": 15,
"violationDensity": 7.5,
"size.class-count.sum": 8,
"healthScores": {}
}
],
"worstClasses": [
{
"symbolPath": "App\\Service\\UserService",
"healthOverall": 45.0,
"label": "Poor",
"reason": "low cohesion",
"violationCount": 8,
"violationDensity": 4.0,
"file": "src/Service/UserService.php",
"metrics": {},
"healthScores": {}
}
],
"topIssues": [
{
"rank": 1,
"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",
"namespaces": ["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,
"baselineVerdict": null,
"baselineReason": null,
"impactScore": 3.71,
"coupling.class-rank-share": 3.711,
"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",
"namespaces": ["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,
"baselineVerdict": null,
"baselineReason": null
}
],
"violationsMeta": {
"total": 3,
"shown": 3,
"limit": null,
"truncated": false,
"byRule": {
"complexity.ccn": 2,
"coupling.cbo": 1
}
},
"violationGroups": {}
}
Записи worstNamespaces и worstClasses включают поле violationDensity: число находок на 100 строк кода. При наличии находок и отсутствующем или нулевом LOC значение равно null.
worstNamespaces включает неймспейсы, объявляющие хотя бы один собственный класс, интерфейс, трейт или enum; контейнеры только с дочерними объявлениями исключаются. Каждое числовое поле записи неймспейса относится к его поддереву: healthOverall, healthScores, violationCount, violationDensity и size.class-count.sum. Запись класса относится к его точному объявлению. Глобальный неймспейс — отдельный лист; его счётчики не включают именованные неймспейсы.
healthScores содержит доступные измерения complexity, cohesion, coupling, typing и maintainability; overall в нём нет. Композит — healthOverall. Причины, метки и цвета summary следуют действующим порогам вычисляемых метрик.
JSON и summary отбирают кандидатов из полной популяции до ранжирования и ограничения. Для отдельного заголовка class health выборка --class в JSON/summary требует единственного точного объявления. Если логическое имя имеет несколько объявлений, команда отказывает с кодом 3 и перечисляет точные объявления; --namespace сохраняет все их точные записи offender. --format-opt=rank-by=score по умолчанию сортирует health-баллы по возрастанию; density — плотность по убыванию. count отвергается с кодом 3. Summary показывает настоящий остаток: например, +12 more (use --format-opt=top=15) после трёх из пятнадцати кандидатов. Счётчик классов неймспейса выводится как "N classes in subtree". HTML показывает не более десяти локальных дочерних записей и полный доступный локальный размер; его список не является глобальным ранжированием JSON.
topIssues — тот же ранжированный список, что формат summary печатает как
«Top issues by impact»; другие форматы его не выводят. Каждая запись называет
impactScore, специфичный для правила и используемый для ранжирования, и
оценку debtMinutes. Ключ coupling.class-rank-share присутствует всегда.
Находки классов и методов используют измеренную долю своего класса; находки
файлов и неймспейсов — максимальную долю среди их классов. Значение равно null,
если конечная доля недоступна, а также для находок функций и проекта.
Ранжирование может использовать медиану измеренных долей для impactScore,
не заменяя этот null. file и line тоже могут быть null — у находки уровня
проекта нет позиции в исходнике. Доля равна сырому PageRank, умноженному на число
логических вершин графа, и выражена в кратных равномерной вероятности. Выгрузка метрик
сохраняет coupling.class-rank и добавляет coupling.class-rank-share;
ранжирование не подменяет отсутствующую долю сырой вероятностью.
Каждая находка содержит nullable acceptedLevel, baselineVerdict и
baselineReason. Verdict breached означает сравнимое превышение и Error;
not-compared сохраняет принятую границу, обычную серьёзность и скалярную причину.
Null не доказывает ни одно состояние. Null acceptedLevel не отличает отсутствие
baseline от новой identity. Непустой acceptedLevel несёт {"shape":"magnitude","describe":"20, 30","count":2};
текущая metricValue остаётся отдельным полем. HTML передаёт те же поля и показывает
оба состояния. Наличие acceptedLevel само по себе не доказывает превышение.
violationsMeta также сообщает shown — число нарушений, фактически
включённых в этот payload; оно может быть меньше total, когда
--format-opt=violations=N обрезает список. Обрезанный список выбирает N худших находок по severity, затем impact,
до группировки для вывода. Без ранга используется место находки.
message и recommendation значат одно и то же в violations и в
topIssues: сообщение находки и её рекомендацию или null. При
--namespace/--class объект summary сохраняет все свои ключи и считает
только выборку; debtPer1kLoc в нём равен null, потому что долг выборки на
строки всего проекта смешал бы две области. Находки, оставшиеся вне выборки,
посчитаны в outOfScope; без выборки он равен null — как и у остальных
форматов, это показано в таблице детализации в разделе summary выше.
Невалидные байты исходника публикуются как %XX; валидный UTF-8 не меняется.
Компоненты идентичности всегда кодируют буквальный % как %25, а разделители
каноники, например # в файле декларации, как %23. Отображаемые строки имеют
другой смысл: буквальный валидный Pa%FFth.php и путь с невалидным байтом могут
выглядеть одинаково. Для точной идентичности используй subject, а не file.
SARIF uri кодирует исходные байты пути: невалидный байт становится %FF,
буквальный знак процента — %25.
Каждый формат сообщает ненулевое число исправленных строк в stderr. JSON,
metrics, suppressed и HTML сохраняют маркер документа invalidUtf8Replaced;
SARIF дополнительно использует уведомление инструмента. GitLab и Checkstyle
содержат только настоящие находки, без синтетической записи
publication.invalid-utf8. Проза считает всё тело одной опубликованной строкой
и сохраняет буквальные проценты; структурированные форматы считают исправленные
поля. Эти числа не обязаны совпадать между форматами.
json.violations, json.topIssues и HTML используют одинаковые ключи находки:
file, line, symbol, channel, subject, occurrence, edge, namespace, namespaces,
rule, code, severity, message, recommendation, metricValue, threshold,
techDebtMinutes, acceptedLevel, baselineVerdict, baselineReason.
В HTML используй rule/code/symbol вместо ruleName/violationCode/symbolPath.
Диагностика, рекомендация и baseline status показаны отдельно; наличие потолка
само по себе не доказывает измеренного превышения.
namespaces содержит те же пространства имён, что используют выбор и
группировка. Объявление сохраняет свой неймспейс; файловая находка несёт
неймспейсы измеренных точных объявлений и логических классов в файле.
Блоки namespace без таких объявлений отсутствуют в индексе. Пустой список
заменяется на [""] для отображения как global, даже если в исходнике есть
блок namespace. Проектная
находка несёт []. namespace — единственное имя (в том числе "" для global)
или null для нескольких имён и проектных находок. Файловая находка с несколькими
неймспейсами входит в одну отсортированную группу и не дублируется.
outOfScope добавляет identities: мультимножество channel, subject,
occurrence, edge и severity, сохраняющее отдельные occurrences.
JSON и metrics используют violationCount/errorCount/warningCount/infoCount.
SARIF передаёт идентичности в уведомлении; GitHub сохраняет ::notice.
Health печатает счётчики выбранных находок и находок вне области даже без scores.
HTML viewer использует опубликованные мешки репозитория, включая глобальный
неймспейс. Площадь class-like берётся из size.class-loc, namespace/project —
из агрегированного LOC. Отсутствующий health остаётся неизвестным. Viewer не
пересчитывает здоровье поддерева и не заменяет его MI. Worst Sub-Namespaces
показывает опубликованный дробный score; координаты Мартина берутся из coupling.*.
Ключи словаря violationGroups используют полное процентное кодирование,
включая буквальный % как %25, чтобы группы байтово различных файлов
не сливались. Декодируй ключ через rawurldecode; поле file остаётся
отображаемым текстом.
Для машинной идентичности используй channel + subject + optional occurrence +
optional edge. symbol — логическая проекция для отображения; строка исходника,
сообщение и порядок вывода не являются стабильной идентичностью. subject
различает точные декларации, логические и агрегатные subjects, occurrence
различает семантические свидетельства внутри канала, а edge содержит
обязательную цель зависимости и необязательный type ссылки. Нетипизированное
ребро имеет вид {"target": "class:App\\Dependency"}, типизированное —
{"type": "new", "target": "class:App\\Dependency"}. Fingerprints форматтеров
используют ту же комбинацию, поэтому target-only рёбра различаются по цели и
отличаются от типизированного ребра к той же цели. Существующие fingerprints
без ребра и с полностью типизированным ребром не меняются.
При использовании --group-by=class или --group-by=namespace нарушения организуются в объект violationGroups. Каждая группа — это {count, violations}: счётчик нарушений и их массив; собственных errorCount, warningCount или violationDensity у группы нет.
Ключи группы — не всегда FQCN класса или пространство имён. Для --group-by=class: ключ — это FQCN класса для находки уровня класса, путь к файлу для находки уровня файла без контекста класса, и пустая строка "" для находки уровня проекта (у неё нет ни класса, ни файла). Для --group-by=namespace: ключ соединяет отсортированные имена через ,, показывает пустое имя как (global) и использует [project] для находки уровня проекта.
Опции:
# Ограничить количество нарушений в выводе (по умолчанию: все)
bin/qmx check src/ --format=json --format-opt=violations=50
# Управление количеством худших нарушителей (по умолчанию: 10)
bin/qmx check src/ --format=json --format-opt=top=20
Каждое значение --format-opt разбирается до начала анализа, одной грамматикой
на ключ, какой бы формат его ни читал: violations и limit принимают целое
число или all, top — целое число от 1, contributors — целое число,
rank-by — count или density, project-name — непустое имя. Значение,
которое не разбирается, отклоняется с кодом 3, а не заменяется значением по
умолчанию. violations и limit ограничивают один и тот же список —
violations=0 не показывает ничего, limit=0 показывает всё, — поэтому оба
вместе или limit рядом с --all отклоняются с кодом 3, а не игнорируется
один из них.
# Группировка нарушений по классу или пространству имён
bin/qmx check src/ --format=json --group-by=class
bin/qmx check src/ --format=json --group-by=namespace
Использование в CI:
metrics¶
Необработанные значения метрик для каждого символа (файл, класс, пространство имён, метод, функция, проект). В отличие от json, который выводит нарушения, metrics экспортирует исходные данные метрик, которые оценивают правила.
Когда использовать: Пользовательские дашборды, анализ трендов, пайплайны data science или создание собственных критериев качества на основе сырых метрик.
Ключи верхнего уровня: version, toolVersion, package, timestamp, docs, llmsTxt, symbols[] (каждый с type: file/class/namespace/method/function/project, name, subject, file, line, metrics: {...}), outOfScope, projectScope, coverage, summary. При --namespace/--class summary считает только выборку, а outOfScope — то, что осталось вне её; symbols[] выборка не сужает никогда. Типа callable не существует; одна запись project агрегирует статистические метрики по всему проекту (min/max/avg/p95 по всем символам) и имеет file и line равными null. Здесь version — версия формата этой выгрузки, а toolVersion — версия Qualimetrix; docs и llmsTxt — те же адреса документации, что json публикует в meta.
Каждое объявление класса или callable публикуется отдельно. Поле name может повторяться; идентичность записи задаёт subject. Графовые значения имени повторяются в каждом его объявлении, а class-агрегаты считают объявления. У namespace и project поля file и line равны null. Суффикс .count рядом с .avg — размер выборки; отдельный файловый .count у size.*-count неймспейса не публикуется. Для size.loc|lloc|cloc неймспейса .sum — целый итог поддерева, .count — число вкладывающих файлов, .avg — их отношение.
Пример вывода (сокращённо):
{
"version": "1.0.0",
"toolVersion": "0.26.0",
"package": "qmx",
"timestamp": "2025-01-15T10:30:00+00:00",
"docs": "https://qualimetrix.dev",
"llmsTxt": "https://qualimetrix.dev/llms.txt",
"symbols": [
{
"type": "file",
"name": "src/Service/UserService.php",
"subject": "file: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",
"subject": "declaration:class:App\\Service\\UserService@src/Service/UserService.php",
"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",
"subject": "declaration:callable:App\\Service\\UserService::calculate@src/Service/UserService.php",
"file": "src/Service/UserService.php",
"line": 42,
"metrics": {
"complexity.ccn": 15,
"complexity.cognitive": 22,
"maintainability.halstead.volume": 384.5,
"size.loc": 35
}
}
],
"projectScope": {"state": "covered", "uncoveredAutoloadTargets": [], "unjudgedChannels": [], "unjudgedValues": [], "reasons": []},
"summary": {
"filesAnalyzed": 45,
"filesSkipped": 0,
"duration": 1.234,
"violations": 3,
"errors": 2,
"warnings": 1,
"info": 0
},
"outOfScope": null
}
Использование:
Полный набор метрик
Формат metrics экспортирует все собранные метрики, а не только те, которые вызвали нарушения. Это делает его полезным для отслеживания трендов метрик со временем, даже для кода, который проходит все правила.
Строки на уровнях namespace и project считаются по разным популяциям
size.loc, size.lloc и size.cloc на двух агрегатных уровнях считают разное. Неймспейс считает строки внутри своей инструкции namespace; проект считает каждый проанализированный файл целиком, включая открывающий тег, комментарии-заголовки и declare над неймспейсом. Поэтому size.loc.sum проекта больше суммы size.loc по неймспейсам, и это не ошибка округления или агрегации. Внутри дерева неймспейсов суммы сходятся: size.loc.sum неймспейса равен его собственному size.loc плюс size.loc.sum его детей.
checkstyle¶
Формат Checkstyle XML. Широко поддерживается CI-инструментами.
Когда использовать: Jenkins, SonarQube или любой инструмент, принимающий Checkstyle XML.
Checkstyle 3.0 XML: <file name="..."> с вложенными <error line="" severity="error|warning|info" message="" source="qmx.<rule>"/>.
Все находки публикуются. Находка с исходным файлом сохраняет его путь;
безфайловая находка неймспейса указывает общий каталог его измеренных объявлений,
а находка проекта — .. Это каталог, а не строка исходника; строка 1 служит
якорем формата. Аудит baseline указывает файл baseline внутри проекта.
Ошибки охвата сохраняют отдельную проекцию.
Выборка --namespace/--class с этим форматом отклоняется (код 3): каждый
<error> для его читателя — ошибка, поэтому сказать, что отчёт перечисляет
лишь часть прогона, было бы нечем.
Пример вывода:
<?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 (Jenkins):
sarif¶
SARIF (Static Analysis Results Interchange Format) 2.1.0. Стандартный формат для инструментов статического анализа, принятый GitHub, Microsoft и многими производителями IDE.
Когда использовать: Вкладка Security на GitHub, VS Code (с расширением SARIF Viewer), JetBrains IDE, Azure DevOps.
SARIF 2.1.0: runs[].results[] с ruleId, ruleIndex (позиция правила в tool.driver.rules), level (error/warning/note), message.text, partialFingerprints.primaryLocationLineHash, и locations[].physicalLocation.{artifactLocation.{uri,uriBaseId}, region.{startLine,startColumn}}. locations есть не у каждой записи: у находки уровня проекта без позиции в исходнике (например, architecture.unreachable-layer) массива locations вообще нет.
runs[].invocations[0] сообщает executionSuccessful (см. таблицу coverage ниже), а runs[].originalUriBaseIds объявляет базу %SRCROOT%, на которую ссылается каждый artifactLocation.uriBaseId, разрешая её в корень анализируемого проекта как file://-URI. Каждый artifactLocation.uri — относительная ссылка в процентной кодировке (пробел — %20, # — %23), закодированная так же, как эта база. Связанное место без файла несёт message и не несёт physicalLocation.
runs[].tool.driver описывает сам инструмент: name, version,
informationUri (сайт документации, https://qualimetrix.dev), мешок
properties, в котором properties.llmsTxt — индекс для ИИ-агентов, и каталог
rules[], в который указывает ruleIndex каждой записи. Адрес лежит в
properties, потому что SARIF закрывает driver для ключей, которых не
определяет сам, а properties — его точка расширения. Каждая запись каталога —
{"id": "...", "name": "...", "shortDescription": {"text": "..."}, "fullDescription": {"text": "..."}, "helpUri": "...", "defaultConfiguration": {"level": "..."}}.
Каждая запись runs[].invocations[] —
{"executionSuccessful": true, "toolExecutionNotifications": []}, а каждое
уведомление в ней —
{"descriptor": {"id": "..."}, "level": "...", "message": {"text": "..."}};
именно здесь сообщается о файле, который не удалось разобрать.
runs[].originalUriBaseIds отображает %SRCROOT% в
{"uri": "file:///path/to/project/"}.
partialFingerprints.primaryLocationLineHash важен не только для полноты
спецификации: именно по нему де-дупликация алертов code-scanning на GitHub
между прогонами определяет, что это один и тот же отслеживаемый алерт, а не
новый — пока фингерпринт остаётся стабильным.
Пример вывода (сокращённо):
{
"$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",
"informationUri": "https://qualimetrix.dev",
"properties": {
"llmsTxt": "https://qualimetrix.dev/llms.txt"
},
"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 (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
Результаты появятся во вкладке Security вашего репозитория и как инлайн-аннотации в пулл-реквестах.
gitlab¶
Формат GitLab Code Quality JSON. Показывает нарушения прямо в диффах Merge Request.
Когда использовать: GitLab CI/CD с отчётами Code Quality.
Массив объектов с description, check_name, fingerprint, severity (critical/major/info), location.{path,lines.begin}. Маппинг: error → critical, warning → major, info → info.
Все находки публикуются. location.path сохраняет исходный файл, а для
безфайлового агрегата указывает каталог, как в Checkstyle выше. Строка 1 —
якорь каталога; GitLab UI не сможет перейти к строке исходника. Аудит baseline
указывает файл baseline внутри проекта. Ошибки охвата сохраняют отдельную проекцию.
Выборка --namespace/--class с этим форматом отклоняется (код 3): каждая
запись — проблема в виджете merge request, поэтому сказать, что отчёт
перечисляет лишь часть прогона, было бы нечем.
Пример вывода (сокращённо):
[
{
"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 (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
Нарушения появятся инлайн во вкладке Changes вашего Merge Request.
github¶
Формат workflow-команд GitHub Actions. Создаёт инлайн-аннотации, которые отображаются прямо в диффах пулл-реквестов при запуске в GitHub Actions.
Когда использовать: GitHub Actions CI. Проще в настройке, чем SARIF — не нужен шаг загрузки.
Формат workflow-команд: ::<level> file=<path>,line=<n>,title=<rule>::<message> (по строке на нарушение). Маппинг: info → ::notice, warning → ::warning, error → ::error.
На каждой строке присутствует только title=. У находки уровня проекта нет
позиции в исходнике, поэтому она печатается как
::<level> title=<rule>::<message> — без file= и line=; GitHub тогда
показывает её у прогона workflow, а не у строки в диффе.
Диагностика отчёта тоже использует ::notice только с title=: выбранные
популяции без оценки (rule-population.incomplete), охват проекта
(run.project-scope) и находки вне выборки (drill-down.out-of-scope). Эти
уведомления описывают отчёт, не добавляя нарушений и не меняя код завершения
проверки правил.
Пример вывода:
::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 (GitHub Actions):
Аннотации появляются прямо на изменённых строках вашего пулл-реквеста — загрузка SARIF не требуется. По умолчанию --fail-on=error — предупреждения не блокируют сборку.
Совет
Используйте --format=github для быстрых инлайн-аннотаций. Используйте --format=sarif, если также хотите видеть результаты во вкладке Security на GitHub.
health¶
Текстовая таблица оценок здоровья для терминального вывода. Показывает каждое измерение с оценкой, статусом, порогами и деталями декомпозиции.
Включённые встроенные измерения проекта сохраняются с недоступными оценками и явным покрытием. Отсутствующие значения декомпозиции равны null, отличаются от числового 0 и показывают измеренную/подходящую долю. Text, summary и health также публикуют ограниченные строки отсутствующих авторских значений, даже без оценок здоровья и вывода логгера. При выборе класса или пространства имён отсутствующая локальная оценка не заменяется проектной.
Когда использовать: Быстрая проверка здоровья из CLI, рабочие процессы AI-агентов, диагностика пайплайнов.
Основные возможности:
- Табличное отображение всех измерений здоровья (сложность, связность, связанность, типизация, сопровождаемость)
- Цветовая индикация статуса (зелёный/жёлтый/красный)
- Видимость порогов (предупреждение и ошибка)
- Декомпозиция по каждому измерению
- Колонка
Coverageи по одной строкеComputed over N of M ...на измерение внутри декомпозиции — доля предмета, о которой говорит эта оценка (см. Что покрывает оценка) - Поддержка drill-down через
--namespaceи--class
Участники cohesion следуют TCC, когда он участвует, иначе измеренному LCOM с его направлением «меньше — лучше».
Худшие участники по измерениям:
Вывод health включает худших участников для каждого измерения -- классы или пространства имён, которые больше всего снижают каждую оценку здоровья. Управляйте количеством отображаемых участников через --format-opt=contributors=N (по умолчанию: 3):
Использование:
bin/qmx check src/ --format=health
bin/qmx check src/ --format=health --namespace='subtree:App\Service'
html¶
Интерактивный отчёт в виде treemap с визуализацией D3.js. Генерирует самодостаточный HTML-файл с иерархией пространств имён и классов.
Когда использовать: Визуализация всего проекта, отчёты для заинтересованных сторон, командные ревью.
Основные возможности:
- Иерархия пространств имён и классов с размерами, пропорциональными LOC
- Цветовая кодировка оценок здоровья для каждого узла
- Переход вглубь пространств имён по клику
- Панель деталей с метриками, нарушениями и декомпозицией
- Декомпозиция проекта подготовлена PHP в
summary.healthDecomposition, покрытие — вsummary.healthCoverage; viewer сохраняет null-входы и измеренный 0 без вычисления формул в JavaScript - Покрытие рядом с каждым проектным баром здоровья (
n/a, когда покрытие не определено) — из объектаsummary.healthCoverage, который нагрузка несёт рядом сsummary.healthScores - Каждое нарушение отчёта висит на узле дерева, поэтому счётчики дерева сходятся с
summary.totalViolations: файловое нарушение висит на единственном классе своего файла, если файл не открывает других пространств имён, иначе на пространстве имён из своей записи (для файла с несколькими — на их глубочайшем общем пространстве имён, для пространства имён без узла — на ближайшем предке с узлом); нарушение без такого узла — проектное, файловое в файле, чьи пространства имён не имеют общего или включают глобальное, — показывается на корне проекта; глобальная функция и файл только с глобальным пространством имён висят на узле(no namespace) - Отчёт назван по анализируемому проекту:
--format-opt=project-name=..., иначеnameиз егоcomposer.json, иначе имя его каталога - Самодостаточный HTML-файл (без внешних зависимостей)
Использование:
Пример рабочего процесса:
# Сгенерировать и открыть отчёт
bin/qmx check src/ --format=html -o report.html
open report.html # macOS
xdg-open report.html # Linux
Сохраняйте HTML в файл
Флаг -o (output) рекомендуется при использовании формата html. Без него HTML-содержимое выводится в stdout.
suppressed¶
Машиночитаемый JSON-состав того, что прогон исключил из отчёта и почему.
Отдельный формат, а не секция json: обычный payload check не меняет форму
из-за возможности, которую вы не запрашивали, каким бы форматом вы его ни
выбрали.
Когда использовать: разобраться, почему ожидаемое нарушение отсутствует в
отчёте; проверить, что именно молчаливо исключает настройка qmx.yaml; найти
неработающую запись suppress_paths/suppress_namespaces (опечатку в пути,
удалённый файл).
Захват включается двумя независимыми способами — флагом
--show-suppressed или выбором --format=suppressed, в том числе через
format: suppressed в qmx.yaml. Оба пути включают один и тот же захват
пер-рулевого исключения, поэтому счётчики по этому механизму на обеих
поверхностях никогда не расходятся.
В остальном эти две поверхности не эквивалентны. --show-suppressed на
--format=text печатает прозой инлайновые подавления @qmx-ignore и
пер-рулевые исключения. Глобальные path-suppression и namespace-suppression
там видны только как счётчик под -v, а не по находкам; снятия baseline и
git-scope не выводятся вовсе; текстового аналога neverMatched нет.
suppressed — единственная поверхность, публикующая все восемь механизмов
по отдельным находкам.
Состав — это мультимножество, а не множество находок. Одна находка может
попасть под несколько механизмов сразу — например, находку, которую убрал бы
инлайновый @qmx-ignore, могло раньше убрать исключение по неймспейсу. Всего
восемь механизмов: suppression (инлайновые @qmx-ignore/@qmx-ignore-file/
@qmx-ignore-next-line), path-suppression и namespace-suppression (глобальные
suppress_paths/suppress_namespaces), baseline (потолок принятого уровня),
git-scope (сужение --report=git:*) и две половины пер-рулевого леджера
исключений, настраиваемого под ключом rules: {<имя-правила>: {...}} —
rule-namespace-suppression, rule-path-suppression и снятие реально
порождённых находок механизмом selection. byMechanism считает
записи по каждому механизму отдельно; поскольку одна и та же находка может
попасть под несколько механизмов, эти счётчики не складываются в число
различных подавленных находок — об этом прямо говорит поле note самого
формата.
Отдельный список neverMatched показывает настроенные подавители, не
исключившие в этом прогоне ничего: без него устаревшую запись suppress_paths,
указывающую на удалённый файл, невозможно отличить от записи, которую вообще
никогда не писали.
meta — тот же блок, что у json, включая docs и llmsTxt.
Ключи верхнего уровня: meta, note, coverage (тот же объект, что у
json, — аудит неполного прогона говорит, что он неполон), projectScope
(тот же объект, что у json, — аудит суженного прогона называет каналы
подавлений, которые не судились), mechanisms (все
восемь, всегда присутствуют), byMechanism (счётчик на каждый механизм, включая
нулевые), suppressed (само мультимножество), neverMatched и notRun
(metadata producers).
Каждая запись suppressed несёт идентичность, которую публикует json, —
channel (здесь это код находки), subject, occurrence, edge, — чтобы её
можно было машинно сопоставить с записью json, и message и recommendation
находки под теми же ключами, что в json. Полей metricValue, threshold,
techDebtMinutes и acceptedLevel в ней нет: формат проверяет, что удержало
находку, а идентичность ведёт к её собственной записи.
Пример вывода (сокращённый, из самоанализа этого проекта):
{
"meta": {
"version": "dev-main",
"package": "qmx",
"timestamp": "2026-08-29T09:14:02+00:00",
"docs": "https://qualimetrix.dev",
"llmsTxt": "https://qualimetrix.dev/llms.txt"
},
"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.",
"coverage": {
"complete": true,
"discovered": 1204,
"analyzed": 1204,
"generatedExcluded": 0,
"excluded": 0,
"failed": 0,
"failures": []
},
"projectScope": {"state": "covered", "uncoveredAutoloadTargets": [], "unjudgedChannels": [], "unjudgedValues": [], "reasons": []},
"mechanisms": [
"suppression",
"path-suppression",
"namespace-suppression",
"baseline",
"git-scope",
"rule-namespace-suppression",
"rule-path-suppression",
"selection"
],
"byMechanism": {
"suppression": 12,
"path-suppression": 0,
"namespace-suppression": 0,
"baseline": 0,
"git-scope": 0,
"rule-namespace-suppression": 58,
"rule-path-suppression": 131,
"selection": 0
},
"suppressed": [
{
"mechanism": "suppression",
"suppressor": "src/Infrastructure/Ast/CachedFileParser.php:15",
"rule": "code-smell.empty-catch",
"channel": "code-smell.empty-catch",
"subject": "aggregate:file:src/Infrastructure/Ast/CachedFileParser.php",
"occurrence": "6f1c0e9b2a4d7e35",
"edge": null,
"file": "src/Infrastructure/Ast/CachedFileParser.php",
"line": 73,
"symbol": "src/Infrastructure/Ast/CachedFileParser.php",
"severity": "error",
"message": "Empty catch block detected - exceptions should not be silently ignored",
"recommendation": "Log or rethrow the exception, or handle it explicitly. A comment alone does not clear this finding; suppress an intentional ignore with `@qmx-ignore code-smell.empty-catch` and a reason."
},
{
"mechanism": "rule-path-suppression",
"suppressor": "code-smell.constructor-overinjection",
"rule": "code-smell.constructor-overinjection",
"channel": "code-smell.constructor-overinjection",
"subject": "declaration:callable:Qualimetrix\\Analysis\\Run\\Contract\\Collection\\SuccessfulFileProcessing::__construct@src/Analysis/Run/Contract/Collection/SuccessfulFileProcessing.php",
"occurrence": null,
"edge": null,
"file": "src/Analysis/Run/Contract/Collection/SuccessfulFileProcessing.php",
"line": 28,
"symbol": "Qualimetrix\\Analysis\\Run\\Contract\\Collection\\SuccessfulFileProcessing::__construct",
"severity": "warning",
"message": "Constructor of SuccessfulFileProcessing has 8 parameters (threshold 8). Consider using a parameter object or splitting responsibilities",
"recommendation": "Constructor parameters: 8 (threshold: 8) — consider splitting responsibilities"
}
],
"notRun": [],
"neverMatched": [
{
"mechanism": "rule-path-suppression",
"suppressor": "coupling.cbo: src/Analysis/Evidence/Design/*Visitor.php"
}
]
}
Для двух механизмов леджера (rule-namespace-suppression,
rule-path-suppression) suppressor называет правило-производитель; для
path-suppression/namespace-suppression — сработавший настроенный паттерн; для
suppression — файл:строка директивы; для baseline — описание принятой
записи; для git-scope — настроенную git-ссылку.
Использование:
JSON аудита директив¶
bin/qmx directives --format=json публикует meta, scope,
selection, sweep, directives, summary и exit_code.
У директивы есть file, строка тега line, form, target,
effect, reason, masked_by, boundary_observable и обязательный
refusals. Исходы — effective, overrun, inert, unmeasured
и refused; reason описывает Unmeasured (выключенный producer или
перекрытый threshold). Место отказа несёт непустой список
[{channel, message}]; остальные исходы — []. Внутренние физическая
позиция и addressed producer не публикуются. Два одинаковых комментария на
строке могут поэтому дать две одинаковые публичные записи.
Threshold overrides одного правила на одной строке пока сливаются.
summary считает все пять исходов, включая refused. Отказ виден и тогда,
когда селекция не позволяет его каналу изменить exit_code.
Неполный анализ возвращает 4 раньше решения по отказам/inert; иначе публикуемый
отказ или наблюдаемый inert даёт 2, оставшиеся случаи — 0.
См. справочник команды.
Для --format=suppressed Inline передаёт первую реально применившуюся
директиву; Reporting не выводит размещение повторно. Публичный suppressor
по-прежнему равен файл:строка, поэтому разные физические места на строке
имеют одну метку.
Адреса документации в JSON-отчётах¶
Каждый JSON-отчёт из таблицы ниже называет, где лежит документация, чтобы
скрипт или ИИ-агент, у которого есть только вывод, нашёл остальное: docs —
сайт документации, llmsTxt — индекс для ИИ-агентов.
| Документ | Где адреса |
|---|---|
check --format=json |
meta.docs, meta.llmsTxt |
check --format=suppressed |
meta.docs, meta.llmsTxt |
check --format=metrics |
docs, llmsTxt верхнего уровня, рядом с version (формат выгрузки) и toolVersion |
check --format=sarif |
runs[].tool.driver.informationUri и runs[].tool.driver.properties.llmsTxt |
directives --format=json |
meta.docs, meta.llmsTxt |
baseline:rename-channels --format=json |
meta.docs, meta.llmsTxt |
debug:layer-assignment --format=json |
meta.docs, meta.llmsTxt |
graph:export --format=json |
meta.docs, meta.llmsTxt, рядом со своим meta.version (формат графа) |
Три команды вне check начинают документ с того же объекта meta, что и
json, — version, package, timestamp, docs, llmsTxt — перед своими
собственными ключами. graph:export --format=json дополняет тот же блок
meta, который уже был в его конверте, сохраняя свой version (формат графа,
а не версию инструмента) — так же, как metrics сохраняет свой.
Адреса есть не в каждом JSON-выводе. gitlab — голый массив, в нём нет объекта
для них; у DOT-вывода graph:export нет конверта вовсе; отказ — это всегда
ровно {"error": ..., "exit_code": ..., "position": ..., "source": ...}, где
position равен null, если отказ не привязан к месту в конфигурационном
документе: значение из командной строки, отказ о файле целиком или синтетическом
слитом входе даёт null, даже когда сообщение называет ключ. Неверные
memory_limit, fail_on и parallel.workers из файла сохраняют написавший слой
и место в документе.
Если position есть, он указывает отвергнутое место так, как его нашла
проверка: для обязательного ключа, которого нет, path заканчивается этим
ключом, а written называет его; для значения вне фиксированного списка
(format, fail_on, слова опции правила) written — само значение,
accepted — список, а closed равен true. source перечисляет слои конфигурации, о
которых отказ, от младшего к старшему — один для значения, написанного одним
слоем, все вкладчики для ограничения между ключами, — каждый в виде
{"kind": ..., "name": ..., "imported_by": ...}. kind — это defaults,
composer, preset, file, cli, baseline или resolved; name — имя
пресета, путь файла или опция. resolved называет отказ, владелец которого
не имеет происхождения написанного значения; его name — ключ, если он известен,
иначе null. source равен null при внутренней ошибке и при отказе без
источника в конфигурации. Текстовый отказ называет те же написавшие слои в
сообщении или отдельной строке Source:, в том числе под --quiet;
синтетический слитый источник не называется автором.
imported_by равен null у слоя, который не импортировался. Для файла или
встроенного пресета, названного в imports, это объект той же формы — файл,
который его импортировал, — и он вкладывается: файл, импортированный файлом,
импортированным qmx.yaml, несёт два уровня. Встроенный пресет, импортированный
по имени, выглядит как
{"kind": "preset", "name": "strict", "imported_by": {"kind": "file", "name": "qmx.yaml", "imported_by": null}};
тот же пресет из --preset импортирующего файла не имеет.
А baseline-файл — который пишут
baseline:generate, update, cleanup и переписывает на месте
baseline:rename-channels — это версионированный входной артефакт, который
инструмент читает обратно, со своей схемой, а не отчёт.
Популяции выбранных правил¶
Правило может не судить выбранный предмет: нужная метрика не опубликована, действует его штатное исключение или необходимые данные неизвестны. Это отличается от здорового предмета, который был оценён без находки. Отключённые и невыбранные каналы не добавляют записи о популяции.
Каждый успешный JSON-документ check содержит abstentions, включая [].
Непустая группа несёт producer, channel, level, gate, reason,
unit, count, examples и dataGap. Группа принадлежит первому не пройденному
объявленному условию; примеры — не более пяти отсортированных уникальных
канонических идентичностей. Счётчик использует штатную единицу условия:
декларацию, неймспейс, цикл или настроенный селектор. Разные единицы нельзя
складывать в общее число предметов. Независимые вызовы анализа сохраняют
свои популяции при объединении; повторное принятие одной замороженной
популяции не удваивает счётчики.
Поле dataGap истинно только тогда, когда оценке мешают отсутствующие данные.
Субъекты вне определённой правилом популяции остаются в этих метаданных с ложным
флагом. Краткие уведомления и баннеры включают только группы с истинным флагом.
| Формат | Выбранные предметы, оставшиеся без оценки |
|---|---|
text, summary, health |
Краткое уведомление при обычной подробности и ограниченные детали при -v, в том числе без находок |
json |
Полные группы abstentions при обычной и тихой подробности, а также в файлах |
sarif |
Уведомление note с дескриптором QMX-RULE-POPULATION-INCOMPLETE |
github |
Строка ::notice title=rule-population.incomplete:: |
html |
Видимый баннер и полная нагрузка abstentions |
Эти записи не создают нарушения, не меняют серьёзность или код выхода по
правилам, не расходуют границы baseline и не увеличивают число подавлений.
Они отделены от computedMetricOutcomes, покрытия файлов анализа и покрытия
оценок здоровья. Metrics, Checkstyle, GitLab, suppressed и baseline не получают
искусственных находок об отсутствии оценки. Успешный ответ в quiet и silent остаётся в stdout; файл отчёта сохраняет сведения о популяции своего формата.
Неполная глубина наследования¶
Измеренный DIT может быть числовой нижней границей, если предка нельзя прочитать,
или не иметь конечной глубины, если цепочка достигает цикла. Это полнота
измерения, отдельная от abstentions выбранных правил, авторских
computedMetricOutcomes, находок и покрытия файлов анализа. Отключение
design.dit или сужение списка находок не удаляет собранные исходы.
JSON и HTML всегда содержат inheritanceDepthOutcomes, включая []. Каждая
непустая группа содержит status (lower-bound для неполного числового DIT,
absent-depth для неполного объявления без числового DIT), count и examples.
Счётчики учитывают различные канонические идентификаторы точных объявлений;
примеры сохраняют идентичность файла и объявления и содержат до пяти уникальных
отсортированных идентификаторов на группу. Объявления без собранного признака
неполноты исключаются; пустой список не доказывает полноту неизмеренной цепочки.
| Формат | Публикация |
|---|---|
json |
Поле верхнего уровня inheritanceDepthOutcomes, включая обычный и тихий вывод |
html |
Те же данные и видимый баннер |
sarif |
Уведомление invocation уровня note с дескриптором QMX-INHERITANCE-DEPTH-INCOMPLETE и теми же группами в свойствах уведомления |
text, summary, health |
Краткое объяснение, включая отчёты без находок или оценок здоровья |
checkstyle, gitlab |
Только обычные находки; искусственная находка о неполном измерении не создаётся |
metrics |
Существующие признаки design.dit и design.dit-unresolved |
Эти исходы не меняют серьёзность, технический долг или итоговый код выхода. Нижняя граница остаётся числом; отсутствующую глубину нельзя считать нулём.
Покрытие анализа во всех форматах¶
Запись завершается анализом, намеренным авторским/generated исключением или
ошибкой. excluded считает названные авторские записи вне discovered.
В discovered входят PHP analyzed, PHP generatedExcluded и выбранные записи
с итоговым failed: это могут быть каталоги, ссылки и специальные записи,
а также PHP-файлы. Снятый каталог не выдаёт число его PHP-потомков. Исключение не делает анализ неполным, любой failure —
делает. Полный намеренно пустой набор (analyzed=0, failed=0,
excluded + generatedExcluded > 0) и действительно пустое дерево различаются.
Код 4 при неполноте имеет приоритет над успехом и политическими нарушениями;
check сохраняет выбранный диагностический отчёт, но результат неавторитетен.
| Формат | Представление coverage |
|---|---|
summary |
Текстовая строка coverage после заголовка |
text |
Текстовая строка coverage после сводки нарушений |
health |
Текстовая строка coverage после заголовка |
json |
Объект coverage верхнего уровня: complete, discovered, analyzed, generatedExcluded, excluded, failed, failures[] |
metrics |
Тот же объект coverage верхнего уровня, что и в json |
sarif |
runs[0].invocations[0].executionSuccessful; ошибки в toolExecutionNotifications[] |
gitlab |
По blocker-issue на каждый сбой с check_name: analysis.<kind>; пустой полный прогон даёт [] |
checkstyle |
Сбои как errors в синтетическом файле [analysis], source — qmx.analysis.<kind> |
github |
По одной ::error-аннотации на каждый сбой; полный прогон без нарушений не даёт аннотаций |
html |
Встроенные данные coverage; при неполном анализе также виден warning-banner |
suppressed |
Объект coverage верхнего уровня: complete, discovered, analyzed, generatedExcluded, excluded, failed, failures[] |
В json и metrics каждый элемент failures[] содержит path, kind и message.
Текстовые форматы выводят измеренные количества, не утверждая, что каждый названный
путь исключён, когда другой корень просто пуст.
kind |
Запись |
|---|---|
parse |
PHP с синтаксической ошибкой |
processing |
Сбой измерения PHP |
unreadable-file |
Source snapshot не удалось получить до вызова parser |
directory-symlink |
Ссылка на каталог, встретившаяся внутри обхода и не пройденная |
not-regular-file |
Необычная PHP-запись: FIFO, socket, device или недопустимая walked file link |
unreadable-directory |
Каталог, который нельзя перечислить |
Skips называют запись, не ставшую единицей анализа. Неизвестный kind считай
признаком неполного прогона, а не причиной отбросить весь документ.
Охват проекта во всех форматах¶
Отчёт публикует итоговое измерение, объединяющее захваченный Composer-состав и фактический выбор записей. Initial target-state до обхода не является итогом: отсутствующий в selection пустой каталог сам по себе не означает пропущенный PHP.
state остаётся covered, narrowed, unknown или unmeasured. Этот enum
описывает свидетельства, но не разрешает одинаково все проверки. Есть два вопроса:
- Отсутствие деклараций. Пропущенные PHP, авторское снятие PHP, generated-исключения и неизвестный состав кода удерживают утверждение об отсутствии неймспейса, класса или имени метода.
- Полнота селекторов/путей. Проверяется полнота PHP-путей и известность
состава кода отдельно. Авторские и generated-исключения не являются входами
этого вопроса. Уже привязанный selector остаётся
Removedи при удержанном ответе.
Первый вопрос задают семь каналов:
architecture.empty-template, architecture.unmatched-exclude,
architecture.unmatched-type, architecture.unreachable-layer,
cohesion.unmatched-exclude-method, coupling.unmatched-framework-namespace,
suppression.unmatched-namespace.
Второй используют discovery.unmatched-exclude, suppression.unmatched-path
и suppression.unmatched-rule-ledger; namespace-значения последнего также
спрашивают первый. Всего десять каналов.
Для suppression.unmatched-namespace и namespace-значений под
suppression.unmatched-rule-ledger литеральное (exact/subtree) значение
судится, несмотря на исключённый PHP или generated-снятие, если неймспейсы,
объявленные этими файлами (токен-скан их операторов namespace, а для файлов больше 64 КиБ текстовый скан, который может удержать больше), не могут его
совпасть. Остальное в первом вопросе не меняется, а прочие шесть каналов остаются
«всё или ничего». См. Исключённый и generated-код.
Полный поимённый roster PHP может покрыть известную область. Наблюдённый regular
.php вне выбора — пропущенный файл, даже если он совпал с exclude. Снятый каталог
вне selection скрывает невидимых потомков: именованный incomplete-universe
удерживает оба вопроса без listing или PHP-поиска там. Assets и special entries
не считаются предполагаемыми пропущенными PHP. Целый корень может установить
полноту без пригодной объявленной автозагрузки; произвольная часть — нет.
Причина omitted-composer-root сохраняет cause и visitedLevels. Поля
candidate, startDirectory и lastDirectory публикуются только для путей
внутри проекта и относительно его корня. При отсутствии основного манифеста
причина пропущенного Composer-корня не добавляется.
| Формат | Представление охвата проекта |
|---|---|
json, metrics, suppressed |
Объект верхнего уровня projectScope в каждом документе |
sarif |
Уведомление invocation с дескриптором QMX-RUN-PROJECT-SCOPE |
github |
Строка ::notice title=run.project-scope:: |
html |
Баннер над отчётом |
summary, text, health |
Строка Project scope … |
gitlab, checkstyle |
Записи об охвате нет: их потребители считают каждую запись находкой |
Уведомление об охвате в sarif, github, html, summary, text и health
называет не более трёх первых пропущенных значений и указывает число остальных.
Массив projectScope.unjudgedValues в json, metrics и suppressed
содержит все пропущенные значения.
Объект сохраняет пять полей: state, uncoveredAutoloadTargets[],
unjudgedChannels[], unjudgedValues[], reasons[]. Пропущенное значение имеет
{channel, option, pattern}. unjudgedChannels перечисляет только каналы,
у которых не судилось ни одного значения; частично проверенный канал может
отсутствовать в этом списке, хотя его skipped values сохранены с channel.
Поэтому пустой список каналов не доказывает, что каждое значение проверено: suppression.unmatched-namespace исчезает из него, как только судится хотя бы одно его значение.
Причины сохраняют kind и именованные данные, включая места снятия и
неизвестности. Вспомогательные Composer-проблемы объясняют ограничения ancestry,
не закрывая охват основного проекта. Карта PSR-4 по-прежнему размещает
namespace-значения независимо от enum. См. правила подавления.
Когда удалённый код (исключение или фильтрация generated-файлов) один оставляет
литеральные namespace-значения подавлений без суждения, check дополнительно
предупреждает в stderr (гасится -q): называет исключения, число значений и их
каналы. directives и baseline:* такого предупреждения не печатают. Из этих двух
объект projectScope публикует только directives; baseline:* его не публикуют.
Если селектор скрыт другим источником, skipped value сохраняет три ключа.
Причина Exclude связывает selector с coveredBy, называет только sources
hider, отсутствующие среди источников селектора, и даёт совет rerun.
Это объяснение удержания ответа, а не finding или доказательство remediation.
Недоступные метаданные поиска внутри реально снятой run entry также удерживают
только отсутствие деклараций, сохраняя фактическое свидетельство unlistable.
Запись exclude, suppress_paths, suppress_namespaces или suppress_namespace_channels,
которая ничему не соответствует и которую записали только файлы, установленные
пакетом Composer, сохраняет три ключа в
unjudgedValues, а по одной причине installed-configuration на каждый такой файл
называют его в source с поясняющей фразой detail (см.
правила подавления).
Сравнительная таблица¶
| Формат | Читаемость | Машинный | Группировка | Интеграция с CI |
|---|---|---|---|---|
summary |
Лучшая | Нет | Оценки здоровья, drill-down | Любой (код выхода) |
text |
Хорошая | Парсируемый | --group-by |
Любой (код выхода) |
json |
Нет | Да | Встроенная (по файлам) | Скрипты |
metrics |
Нет | Да | Встроенная (по символам) | Скрипты, дашборды |
checkstyle |
Нет | Да | Встроенная (по файлам) | Jenkins, SonarQube |
sarif |
Нет | Да | Встроенная | GitHub, VS Code, JetBrains |
gitlab |
Нет | Да | Плоский список | GitLab MR виджет |
github |
Нет | Нет | Плоский список | GitHub Actions аннотации |
health |
Хорошая | Нет | Измерения здоровья | Быстрые проверки, CI |
html |
Интерактивная | Нет | Иерархия treemap | Отчёты, ревью |
suppressed |
Нет | Да | Плоское мультимножество по механизму | Аудит подавления |
Коды выхода¶
Все форматы используют одинаковые коды выхода:
| Код выхода | Значение |
|---|---|
| 0 | Нет нарушений (или только предупреждения при --fail-on=error) |
| 1 | Есть предупреждения (при --fail-on=warning) |
| 2 | Есть хотя бы одно нарушение уровня error |
| 3 | Отказ из-за конфигурации, ввода или среды |
| 4 | Неполный анализ или OOM координатора; политический результат неавторитетен |
5 |
Внутренняя ошибка инструмента |
По умолчанию --fail-on=error: предупреждения отображаются, но не приводят к ненулевому коду выхода. Используйте --fail-on=warning, чтобы предупреждения тоже вызывали код выхода 1. Код 4 имеет приоритет над policy-кодами warning/error. Text, summary и health называют до десяти необработанных файлов, затем «and N more». Вывод воркера перехватывается, а причина смерти относится к его файлу. Связанный OOM координатора в JSON-форматах использует конверт ошибки. graph:export без PHP-файлов выдаёт пустой граф, код 0 и пояснение в stderr.
Прогон неполон, когда он не смог прочитать часть дерева, на которое его направили: файл, который не удалось разобрать, каталог, который нельзя перечислить, символическую ссылку на каталог внутри дерева или запись *.php, не являющуюся обычным файлом. Раньше такая запись молча отбрасывалась, и подобный прогон отвечал 0 или 2.
Символическая ссылка, названная как сканируемый путь, — единственное исключение: qmx check src/linked анализирует то, на что ссылка указывает, и прогон полон, тогда как та же ссылка, встреченная при обходе src/, сообщается как непрочитанная запись. Назвать путь — значит попросить проанализировать то, что за ним; ссылку внутри дерева никто не просил, а переход по ней изменил бы состав измеряемых файлов, мог бы увести за корень проекта и не завершился бы на цикле.
Примечание
Все диагностические сообщения check вне выбранного report-payload (уведомления и ошибки конфигурации, deprecation, logging и сообщения о записи файла) выводятся в stderr, а не в stdout. Это позволяет безопасно перенаправлять вывод анализа в файл или другой инструмент: bin/qmx check src/ --format=json > results.json.
Selection removals и skipped producers¶
selection — восьмой suppression mechanism. Он содержит реально порождённые
находки, снятые итоговым publication selection. byMechanism.selection считает
только эти multiset entries, никогда не число неисполненных producers.
notRun всегда массив. У записи есть producer, reason (disabled или
filtered), decisive statement и описание layer. Skipped producer не даёт
искусственной находки или suppression count. У выключенного channel может быть
живой sibling, поэтому producer notRun и channel removal — разные исходы.
Обычные suppressed identity/multiset rules сохраняются; drill-down и formatter
truncation остаются presentation, а не selection.
Directive JSON сохраняет selection.only и selection.disabled как списки
строк. Публикуются итоговый filter и все tied decisive disabling texts;
повторяющиеся cells удалены, нижние disables, снятые поздним enable, отсутствуют.
qmx rules печатает реальные origins/layer indices отдельно в human listing;
это не меняет молча форму directive JSON.
Baseline entry audit¶
baseline.unused-entry — проектный Warning о stale/inert после полного ceiling и до Git-проекции. Не попадает в measured set, capture или accept-new; подавления путей/пространств имён и Git его не скрывают. Невыбранный аудит и несравнимые записи дают только счётчики stderr без списков путей. Девять форматов с находками публикуют аудит; metrics, health и suppressed сохраняют свой предмет. Во всех двенадцати форматах изолированное предупреждение даёт 0 по умолчанию/--fail-on=error/none, 1 с --fail-on=warning. Неполный анализ приоритетен и даёт 4. Оценка исправления — 5 минут.