quality-gate · diff

git:20260906.1f0fab4 to git:20260906.74b3426

1 added, 1 removed. Audit A to A.

---
name: quality-gate
description: >-
Оркестратор контроля качества 1С-разработки. Определяет профиль изменения по трём осям
(объём правки, архетипы кода, сложность), выбирает глубину каждого контура, запускает
проверки, формирует отчёт с машиночитаемым следом и снимает блокирующий гейт.
Вызывать после правок BSL или XML метаданных 1С, перед завершением работы и коммитом.
Триггеры: «прогони гейт качества», «проверь код перед коммитом», «сними гейт»,
«отревьюй что я написал», «проверь по стандартам», «готов ли код к коммиту».
license: MIT
---
# quality-gate — оркестратор контроля качества 1С
Единственная точка входа плагина. Контуры проверки (`code`, `arch`, `xml`, `hygiene`) не
вызываются напрямую: сначала определяется **профиль изменения**, и уже он решает, какие
контуры и на какой глубине запускать.
> **Главное правило.** Полный прогон на правке комментария — налог, из-за которого гейт
> начинают обходить. Пропуск проверки без следа — ложная зелень. Поэтому глубина
> адаптивная, но **любой пропуск фиксируется явной записью с причиной**.
<ЖЁСТКИЙ-ШЛЮЗ>
По умолчанию — только проверка и отчёт. НЕ переписывай бизнес-логику и НЕ меняй метаданные
по своей инициативе. Правки применяются лишь в режиме `--fix` и только из безопасных
категорий. Находки уровня Critical и любые изменения логики, проведения, запросов, прав —
никогда без явного подтверждения пользователя.
</ЖЁСТКИЙ-ШЛЮЗ>
---
## Инварианты прогона
Восемь утверждений, без которых результат недействителен. Если из всего навыка усвоено
только это — прогон ещё имеет смысл; если нарушено любое из них — уже нет.
1. **Профиль считается один раз**, до контуров, и контуры его не пересчитывают.
2. **Глубина — по профилю, а не по привычке.** Не гонять архитектуру на опечатке и не
ограничиваться гигиеной на новом модуле проведения.
3. **Контур исполняется вызовом навыка**, а не воспроизведением по памяти.
4. **Строку следа инструментальной проверки печатает инструмент** — она не сочиняется и
не переписывается по смыслу.
5. **Любой пропуск — запись `skipped` с причиной.** Молчание неотличимо от выполнения, и
это единственная ошибка, которая обесценивает плагин целиком.
6. **Вердикт «чисто» обязан признавать непроверяемое** — записью `not_verified`.
7. **Каждая находка — с номером стандарта, кодом диагностики или названием эвристики** и
измеренным значением против порога; 🔴 и 🟠 блокируют вердикт «Чисто». Вне `--fix` —
только отчёт.
8. **Гейт снимается утилитой** `gate.mjs release`, а не удалением файла состояния.
---
## Шаг 1. Профиль изменения — три оси
Считается **один раз**, до запуска контуров.
**Пороги берутся из проекта, а не по памяти.** До расчёта осей выполни:
```bash
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` | `tools/bsl-lint.mjs` |
+ | `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`
считает такие файлы и печатает запись сам — переноси её дословно; число непроверенных
уходит в журнал, и промолчать о них валидатор не даст.
Проверить след перед снятием:
```bash
node "$QG/tools/evidence-validator.mjs" <файл отчёта> --gate
```
---
## Шаг 6. Снятие гейта
Гейт снимается **только** утилитой — не удалением файла состояния вручную:
```bash
# по результатам прогона (след проверяется, дефектный след снятие не пропустит)
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"`**: существование переменной или каталога ещё не значит,
что там лежит этот плагин — установка могла быть частичной, устаревшей или чужой.
```bash
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`.