quality-gate — оркестратор контроля качества 1С
Единственная точка входа плагина. Контуры проверки (code, arch, xml, hygiene) не
вызываются напрямую: сначала определяется профиль изменения, и уже он решает, какие
контуры и на какой глубине запускать.
Главное правило. Полный прогон на правке комментария — налог, из-за которого гейт начинают обходить. Пропуск проверки без следа — ложная зелень. Поэтому глубина адаптивная, но любой пропуск фиксируется явной записью с причиной.
<ЖЁСТКИЙ-ШЛЮЗ>
По умолчанию — только проверка и отчёт. НЕ переписывай бизнес-логику и НЕ меняй метаданные
по своей инициативе. Правки применяются лишь в режиме --fix и только из безопасных
категорий. Находки уровня Critical и любые изменения логики, проведения, запросов, прав —
никогда без явного подтверждения пользователя.
</ЖЁСТКИЙ-ШЛЮЗ>
Инварианты прогона
Восемь утверждений, без которых результат недействителен. Если из всего навыка усвоено только это — прогон ещё имеет смысл; если нарушено любое из них — уже нет.
- Профиль считается один раз, до контуров, и контуры его не пересчитывают.
- Глубина — по профилю, а не по привычке. Не гонять архитектуру на опечатке и не ограничиваться гигиеной на новом модуле проведения.
- Контур исполняется вызовом навыка, а не воспроизведением по памяти.
- Строку следа инструментальной проверки печатает инструмент — она не сочиняется и не переписывается по смыслу.
- Любой пропуск — запись
skippedс причиной. Молчание неотличимо от выполнения, и это единственная ошибка, которая обесценивает плагин целиком. - Вердикт «чисто» обязан признавать непроверяемое — записью
not_verified. - Каждая находка — с номером стандарта, кодом диагностики или названием эвристики и
измеренным значением против порога; 🔴 и 🟠 блокируют вердикт «Чисто». Вне
--fix— только отчёт. - Гейт снимается утилитой
gate.mjs release, а не удалением файла состояния.
Шаг 1. Профиль изменения — три оси
Считается один раз, до запуска контуров.
Пороги берутся из проекта, а не по памяти. До расчёта осей выполни:
node "$QG/tools/config.mjs" show
Команда печатает действующие значения и источник каждого. Значения в таблицах ниже —
умолчания; если вывод расходится с ними, считай по выводу. Последняя строка вывода —
готовое поле config=... для записи scope: перенеси её дословно, валидатор
пересчитывает эту отметку сам и написанное по памяти отвергает. Почему именно так —
references/profile-axes.md.
Ось 1: объём (скаляр)
Что тронуто и пересекает ли правка границы.
| Класс | Признаки |
|---|---|
| C0 Косметика | комментарии, форматирование, переименование без изменения смысла; тела методов не менялись |
| C1 Точечная | файлов ≤ volume.c1MaxFiles (умолчание 1), изменённых строк ≤ volume.c1MaxLines (умолчание 40), правка внутри существующих методов; нет новых экспортов, изменённых сигнатур, новых модулей и объектов метаданных |
| C2 Модульная | >1 метода, ИЛИ новый экспортный метод, ИЛИ изменена сигнатура, ИЛИ превышен порог строк либо файлов — в пределах существующих модулей |
| C3 Структурная | новый модуль / объект метаданных / форма, ИЛИ изменение проведения и бизнес-логики, ИЛИ новая интеграция, ИЛИ затронуто ≥4 модуля разных подсистем |
Источник данных: git diff --stat и git diff по рабочему дереву, либо явно названные
пользователем файлы. Состав правки — из node "$QG/tools/gate.mjs" status.
Ось 2: архетипы кода (множество меток)
Объёма недостаточно: три строки внутри транзакции опаснее трёхсот строк переименований. Архетипы не упорядочены и комбинируются — одна правка может быть одновременно «запросом», «транзакцией» и «интеграцией». Определяются механически по маркерам в изменённом коде и путям файлов.
| Архетип | Метка в следе | Маркер в изменениях | Мин. code |
Мин. arch |
|---|---|---|---|---|
| Запрос | query |
Новый Запрос, правка текста запроса |
L2 | — |
| Транзакция, блокировки | transaction |
НачатьТранзакцию, Заблокировать |
L2 | — |
| Запись наборов записей | record-set |
Записать(Истина), СоздатьНаборЗаписей |
L2 | — |
| Обработчик события объекта | object-event |
ПередЗаписью, ПриЗаписи, ОбработкаПроведения |
L2 | ур. 1 |
| Интеграция, HTTP | integration |
HTTPСоединение, WSПрокси, Новый COMОбъект |
L2 | ур. 1 |
| Права, RLS | rights |
XML ролей, УстановитьПривилегированныйРежим |
L2 | ур. 2 |
| CFE-перехват | cfe-patch |
&Перед, &После, &Вместо, &ИзменениеИКонтроль |
L2 | ур. 1 |
| Регламентное, фоновое | scheduled-job |
подписка регл. задания, ФоновыеЗадания |
L2 | — |
| Клиент-сервер | client-server |
директивы компиляции | L1 | ур. 1 |
| Диалог посреди логики | user-dialog |
ПоказатьВопрос, ВопросАсинх, ОповещениеОЗавершении |
L1 | ур. 1 |
| Модуль формы | form-module |
путь Forms/*/Module.bsl |
L1 | ур. 1 при loc > 400 |
| Асинхронный клиент | async-client |
Асинх, Ждать, Обещание |
L1 | — |
| Новый общий модуль | new-common-module |
новый файл CommonModule |
L1 | ур. 2 |
| Новый объект метаданных | new-metadata-object |
новый XML в src/ |
L1 | ур. 3 |
Обязательные чеклисты по каждому архетипу перечисляет контур в своём SKILL.md: продублируй их здесь — и они разъедутся с тем, что контур делает на самом деле.
Колонка «Метка в следе» — ровно то, что пишется в поле archetypes записи scope. Метка
не переводится и не сокращается: queries вместо query означает не «почти то же самое», а
«правило не сработало», и снятие гейта валидатор не пропустит. Свои архетипы проект заводит
в секции archetypes.custom (имя, маркеры, minCode, minArch), их имена — тоже законные
метки. Почему минимумы по двум контурам асимметричны — references/profile-axes.md.
Ось 3: сложность (скаляр)
Закрывает случай «мало строк, но код тяжёлый», когда архетипа может не быть вовсе.
Считается по изменённым методам: вложенность ≥ complexity.maxNesting (4), длина метода
complexity.maxMethodLines(120), параметров ≥complexity.maxParams(7), цепочка ветвлений ≥4, рекурсия. Срабатывание поднимаетcodeдо L2 иarchдо уровня 1.
Метрики — из отчёта tools/analyzer-run.mjs --json (functions, complexity,
cognitive_complexity), считать самому не нужно. Анализатор запускается с гейтовым
конфигом из состава плагина: проектный может отсечь изменённые файлы фильтром подсистем и
оставить правдоподобно пустой отчёт (references/profile-axes.md).
Итог: правило разрешения
глубина контура = max(по объёму, максимум минимумов по сработавшим архетипам, по сложности)
Отдельно фиксируется driver — что именно подняло глубину: объём, конкретный архетип
или сложность. Без него вердикт виден, а логика нет, и первое же «почему так долго на трёх
строках?» превращается в спор.
Понижающий модификатор: эталонная правка
Понижает глубину до C1 независимо от объёма, если выполнены три условия сразу: (а) все фрагменты — кальки типового эталона того же механизма с точечной адаптацией имён и полей; (б) уже прошли предметный верификатор механизма с нулём ошибок; (в) не трогают транзакции, блокировки и права. Хотя бы одно не выполнено — модификатор не применяется.
Шаг 2. Матрица глубин по объёму (базовый уровень)
| Контур | C0 | C1 | C2 | C3 |
|---|---|---|---|---|
hygiene |
полный | полный | полный | полный |
code |
пропуск | L1 | L1 + L2 | L1 + L2, предложить аудит |
arch |
пропуск | пропуск | ур. 1–2 | ур. 3 |
xml |
пропуск | не применим, если XML не менялся | изменённые объекты + регистрация | полный: валидация + сироты в обе стороны + права ролей |
| компилируемость | — | если платформа доступна | да | да |
hygiene гоняется всегда: стоимость околонулевая, а ловит он то, что проявляется позже
всего и объясняется хуже всего. Разовое отклонение от расчётной глубины — --deep /
--quick.
Шаг 3. Прогон контуров
Запускай только те контуры и глубины, которые дал шаг 2. Каждый контур обязан вернуть
запись applied либо skipped — молчание не допускается.
| Контур | Навык | Состояние |
|---|---|---|
code |
bsl-code-review — стандарты, антипаттерны, верификация API |
готов |
arch |
bsl-architecture-review — принципы, паттерны, границы модулей |
готов |
xml |
xml-structure-review — структура метаданных, регистрация, права |
готов |
hygiene |
file-hygiene — кодировки, BOM, символы, переводы строк |
готов |
Передавай контуру профиль целиком: класс, сработавшие архетипы, список изменённых файлов. Пересчитывать профиль контур не должен — иначе оси разъедутся и решение о глубине перестанет быть проверяемым.
Контур исполняется вызовом навыка, а не по памяти
Таблица называет навыки, а не проверки: чем именно проверяется признак, написано в SKILL.md
контура. Прогон без открытия навыка воспроизводит контур как чеклист для чтения глазами —
вердикт выглядит результатом проверки, но им не является, и происходит это не по
небрежности (references/run-environment.md).
У части проверок есть исполняемый инструмент. Для них строку следа печатает сам
инструмент — не сочиняй её. Прогон отмечается в журнале (qg-runs.jsonl в каталоге
состояния гейта: .claude/.state/ в Claude Code, .opencode/.state/ в OpenCode)
вместе с путями файлов, и валидатор сверяет по нему каждую запись applied: и то, что
инструмент запускался, и то, что он видел весь состав правки. Прогон по одному файлу из
десяти заявление обо всех десяти не закрывает; skipped ... reason=not_applicable — тоже
утверждение о работе инструмента.
Гонять инструмент по частям можно, прогоны складываются: передавай все изменённые файлы его
вида, список даёт gate.mjs status. query-lint принимает не только .bsl — он читает
<query> схем компоновки и <QueryText> динамических списков, поэтому изменённые XML идут
и в него.
Проверка (scope) |
Инструмент |
|---|---|
static-analysis |
tools/analyzer-run.mjs |
platform-api |
tools/platform-context-run.mjs (сервер справки заводится сам) |
query-alias-shadowing, query-top-order |
tools/query-lint.mjs (.bsl и .xml) |
transaction-nesting, enum-string-assign, unbounded-string-column, attribute-access, form-attribute-shadowing, dispatch-fallback, db-read-in-loop |
tools/bsl-lint.mjs |
stale-local-calls |
tools/rename-check.mjs |
file-encoding |
tools/hygiene-check.mjs |
registration-check |
tools/xml/orphan-check.mjs |
uuid-uniqueness |
tools/xml/uuid-unique.mjs |
structure-validation |
tools/xml/meta-validate.py |
form-binding |
tools/xml/form-validate.py |
Есть модули — сверка со справочником платформы обязательна. Валидатор требует не успеха, а
отчёта: движок сам печатает и applied, и skipped с причиной. Этот класс дефектов не
закрывает больше ничто — анализатор знает имена конфигурации, но не платформы.
Остальные проверки (архитектурные признаки, разбор стандартов, ручная сверка API там, где
движок промолчал) инструмента не имеют: журнала для них не требуется. Полный словарь имён — tools/evidence-scopes.mjs;
имя вне словаря валидатор отвергает, потому что закрывает требование, которого не выполняло.
Субагенты в составе прогона
Три штатных, все дешёвые и только читающие. Записи следа они не пишут: агент возвращает факты, запись формирует контур.
| Субагент | Кем вызывается | Зачем | Спавнов |
|---|---|---|---|
bsl-verifier |
контур code, верификация API |
сигнатуры платформы, экспортность общих модулей, состав метаданных | один на весь список файлов |
bsl-scout |
контур arch, признаки по графу вызовов |
вызывающие, экспорты модуля, триггеры в XML | один на вопрос, независимые — параллельно |
xml-runner |
контур xml |
сверка «диск ↔ состав», валидаторы структуры, разбор их вывода | один на прогон контура |
Недоступность субагента, контура или инструмента проверку не отменяет: она выполняется сама
либо получает skipped с точной причиной. Границы применения и список причин деградации —
references/run-environment.md.
Перед повторным прогоном слоя проверь gate.mjs status: слой, уже отработавший по этому
содержимому, пропускается с причиной verified_earlier, а любая правка файла снимает его
отметку. Механика — references/evidence-format.md.
Слой 3 контуров: состязательный аудит
Самая дорогая проверка и единственная, которая никогда не запускается сама. Контуры
предлагают её в отчёте при классе C3 с находками 🔴 или 🟠; запуск — только после явного
согласия пользователя. Методология, пороги и поведение при недоступной оркестрации —
в references/adversarial-audit.md.
Шаг 4. Sentinel — проверка живости источника стандартов
Один раз за прогон запроси через MCP v8std заведомо существующий стандарт — тот, чей номер
задан в sentinel.id проектной настройки (умолчание std454, фактическое значение — в
выводе config.mjs show из шага 1): v8std_get_page("<sentinel.id>"). Ожидание — страница
найдена.
Без этой проверки «нарушений стандартов не найдено» неотличимо от «сервис стандартов
недоступен»: неподтверждённый часовой делает прогон недостоверным, и валидатор следа
отклонит снятие гейта. Почему номер вынесен в настройку — references/run-environment.md.
Шаг 5. Отчёт и след
Отчёт для человека — находки по важности (🔴 Critical / 🟠 Major / 🟡 Minor) в формате
контура. Ниже, в секции ## quality evidence, — машиночитаемый след: одна строка на
проверку. Формат, полный список причин и правила валидатора — references/evidence-format.md.
Каждая запись not_verified повторяется в человекочитаемой части одной фразой — «Ошибок: 0»
рядом с невидимым not_verified читается как «проверено». Фраза короткая и по делу:
«текст запроса статически чист; выполнимость не проверялась — платформы нет».
Обязательный минимум следа: одна запись scope (с полем config из шага 1), одна
sentinel, по записи от каждого запущенного или пропущенного контура, и — если
компилируемость тел модулей не проверялась — запись not_verified: dimension=compilation.
Компилируемость не проверяется ничем, кроме платформы, поэтому полностью зелёный отчёт без
такой записи валидатор отклоняет.
Сработал архетип «Запрос» — выполни запрос до вердикта: в консоли запросов, на тестовых параметрах, прогоном обработки. Для всех инструментов текст запроса остаётся строковым литералом, и «Неоднозначное поле» доживает до продуктива. Выполнить негде — законный исход, но записанный:
[qg applied: layer=code, scope=query-execution, ids=[qg:QRY-EXECUTED], verdict=clean]
[qg not_verified: dimension=query-execution, reason=no_platform]
Изменённый файл, до которого не добрался анализатор, не проверен. analyzer-run.mjs
считает такие файлы и печатает запись сам — переноси её дословно; число непроверенных
уходит в журнал, и промолчать о них валидатор не даст.
Проверить след перед снятием:
node "$QG/tools/evidence-validator.mjs" <файл отчёта> --gate
Шаг 6. Снятие гейта
Гейт снимается только утилитой — не удалением файла состояния вручную:
# по результатам прогона (след проверяется, дефектный след снятие не пропустит)
node "$QG/tools/gate.mjs" release --evidence <файл отчёта>
# правка не требует проверки — допустимо только для C0/C1, причина обязательна
node "$QG/tools/gate.mjs" release --class C0 --reason "<почему>"
НЕ снимай гейт, если прогон прерван на полпути и отчёт не сформирован: гейт должен остаться, чтобы проверка прогналась заново.
Чужие сессии не трогай. Состояние гейта разделено по сессиям. Если gate.mjs status
показывает несколько, verify и release без --session <id> отказывают — выбор «самой
свежей» брал чужую. Идентификатор напечатан в подсказке при взводе гейта и в сообщении о
блокировке; свою сессию видно по составу правок (references/run-environment.md).
Путь к инструментам плагина ($QG)
Все команды выше используют $QG — каталог установленного плагина. Под OpenCode он приходит
готовым в QG_ROOT; в Claude Code CLAUDE_PLUGIN_ROOT доступна хукам, но не оболочке —
в шелле она пуста, и путь через неё схлопнулся бы в /tools/....
Разреши путь первой командой прогона и дальше подставляй полученное значение буквально
(состояние оболочки между вызовами не сохраняется). Каждый кандидат принимается только
после проверки test -d "$QG/tools": существование переменной или каталога ещё не значит,
что там лежит этот плагин — установка могла быть частичной, устаревшей или чужой.
QG="${QG_ROOT:-}"
[ ! -d "$QG/tools" ] && QG="${CLAUDE_PLUGIN_ROOT:-}"
[ ! -d "$QG/tools" ] && QG="$(node -e "const p=require(require('node:os').homedir()+'/.claude/plugins/installed_plugins.json').plugins;const k=Object.keys(p).find(n=>n.startsWith('1c-quality-gate@'));if(k&&p[k][0])process.stdout.write(p[k][0].installPath)" 2>/dev/null)"
[ ! -d "$QG/tools" ] && QG="$(ls -d ~/.claude/plugins/cache/*/1c-quality-gate/*/ 2>/dev/null | sort -V | tail -1)" && QG="${QG%/}"
test -d "$QG/tools" && echo "$QG" || { echo "Плагин не найден ни в одном харнессе" >&2; exit 1; }
sort -V не декоративен: без него сессия работает инструментами устаревшей версии и честно
отчитывается, что проверок «не существует». Разбор — references/run-environment.md.