quality-gate
Оркестратор контроля качества 1С-разработки. Определяет профиль изменения по трём осям (объём правки, архетипы кода, сложность), выбирает глубину каждого контура, запускает проверки, формирует отчёт с машиночитаемым следом и снимает блокирующий гейт. Вызывать после правок BSL или
Install
npx skills add https://github.com/Romandredan/1c-quality-gate/tree/main/skills/quality-gate
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install romandredan-1c-quality-gate@llmmart
git clone https://github.com/Romandredan/1c-quality-gate.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole romandredan/1c-quality-gate collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
quality-gate — оркестратор контроля качества 1С
Единственная точка входа плагина. Контуры (code, arch, xml, hygiene) не вызываются
напрямую: сначала считается профиль изменения, он решает глубину.
Главное правило. Полный прогон на правке комментария — налог, из-за которого гейт начинают обходить; пропуск без следа — ложная зелень. Глубина адаптивная, но любой пропуск фиксируется явной записью с причиной.
<ЖЁСТКИЙ-ШЛЮЗ>
По умолчанию — только проверка и отчёт. НЕ переписывай бизнес-логику и НЕ меняй метаданные по
своей инициативе. Правки — только в режиме --fix, из безопасных категорий. Critical и любые
изменения логики, проведения, запросов, прав — никогда без явного подтверждения пользователя.
</ЖЁСТКИЙ-ШЛЮЗ>
Инварианты прогона
Девять утверждений: усвоен только этот блок — прогон ещё имеет смысл; нарушено любое — уже нет.
- Профиль считается один раз, до контуров; контуры его не пересчитывают.
- Глубина — по профилю, не по привычке: не гонять архитектуру на опечатке, не ограничиваться гигиеной на новом модуле проведения.
- Контур исполняется вызовом навыка, а не по памяти.
- Строку следа печатает инструмент, где он есть — не модель по смыслу.
- Любой пропуск — запись
skippedс причиной. Молчание неотличимо от выполнения. - Вердикт «чисто» признаёт непроверяемое — записью
not_verified. - Находка — с номером стандарта, кодом диагностики или эвристикой и значением против
порога; 🔴/🟠 блокируют «Чисто», вне
--fix— только отчёт. - Гейт снимается утилитой
gate.mjs release, а не удалением файла состояния. - План печатает
gate.mjs plan; модель вправе поднять глубину с причиной, понизить — нет.
Шаг 1. План
Путь к инструментам плагина ($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).
Профиль и план прогона
node "$QG/tools/gate.mjs" plan --files <f> [<f> ...] [--no-analyzer]
Без --files — состав из взведённой сессии (gate.mjs status). Печатает профиль по трём
осям, строку scope (с config=...), инструменты по порядку, что закрыть в следе.
--no-analyzer — когда анализатор недоступен или падает; ось сложности тогда not_computed,
driver не бывает complexity:*.
Строку [qg scope: ...] перенеси в отчёт дословно — валидатор пересчитывает профиль сам.
Расхождение — сверь порог: node "$QG/tools/config.mjs" show. Профиль можно поднять,
нельзя понизить: увидел то, чего план не видит (правка тише архетипа, но трогает деньги) —
подними, добавь «глубина поднята: <причина>»; обратное валидатор не пропустит.
Почему оси устроены так — references/profile-axes.md; данные — tools/profile.mjs,
tools/gate.mjs.
Шаг 2. Инструменты
Выполни команды раздела «## Инструменты» плана по порядку — файлы уже подставлены. У части
есть исполняемый инструмент: он печатает строку следа ([qg applied: ...] / [qg skipped: ...]) сам — переноси дословно, не сочиняй.
Прогон отмечается в журнале qg-runs.jsonl: валидатор сверяет по нему каждую запись
applied — инструмент видел весь состав правки, а не файл из десяти; skipped ... reason=not_applicable — тоже утверждение о работе. Прогоны по частям складываются.
platform-context-run.mjs, когда включён планом, обязателен: анализатор знает имена
конфигурации, но не платформы. query-lint берёт и изменённые XML, не только .bsl.
Перед повторным прогоном слоя проверь gate.mjs status: отработавший по этому
содержимому слой пропускается с причиной verified_earlier (references/evidence-format.md).
Шаг 3. Контуры
Запускай контуры и глубины из resolved: code=... arch=... xml=... hygiene=... плана.
Каждый контур обязан вернуть applied либо skipped — молчание не допускается.
| Контур | Навык |
|---|---|
code |
bsl-code-review |
arch |
bsl-architecture-review |
xml |
xml-structure-review |
hygiene |
file-hygiene |
Передавай контуру профиль целиком: класс, архетипы, файлы.
Контур исполняется вызовом навыка, а не по памяти
Таблица называет навыки, а не проверки: чем проверяется признак — в SKILL.md контура. Прогон
без открытия навыка — чеклист для чтения глазами, не проверка
(references/run-environment.md).
Субагенты в составе прогона
Четыре штатных, только читающие: возвращают факты, запись формирует контур. Три дешёвые;
antipattern-reader — на модели уровня контура: семантика LLM-антипаттернов не
механизируется до haiku.
| Субагент | Вызывает | Факты | Спавнов |
|---|---|---|---|
bsl-verifier |
code |
сигнатуры платформы, экспортность модулей, метаданные | на список файлов |
antipattern-reader |
code |
кандидаты с цитатами; аттестует tools/catalog.mjs |
на контур |
bsl-scout |
arch |
вызывающие, экспорты, триггеры XML | по вопросу |
xml-runner |
xml |
«диск ↔ состав», валидаторы структуры | на контур |
Недоступность субагента, контура или инструмента проверку не отменяет — она проходит сама
либо получает skipped с причиной (references/run-environment.md).
Слой 3: состязательный аудит
Самая дорогая проверка, никогда не запускается сама: контуры предлагают её при классе C3 с
находками 🔴/🟠, запуск — только по явному согласию пользователя
(references/adversarial-audit.md).
Шаг 4. Sentinel — проверка живости источника стандартов
Один раз за прогон запроси через MCP v8std стандарт, чей номер — sentinel.id настройки
(умолчание std454): v8std_get_page("<id>"), ожидание — страница найдена.
Без неё «нарушений не найдено» неотличимо от «сервис недоступен»: неподтверждённый часовой
делает прогон недостоверным (references/run-environment.md).
Шаг 5. Отчёт и след
Отчёт для человека — находки по важности (🔴 Critical / 🟠 Major / 🟡 Minor). Ниже, в
## quality evidence, — машиночитаемый след: строка на проверку
(references/evidence-format.md).
Каждая запись not_verified повторяется в человекочитаемой части фразой: «Ошибок: 0»
рядом с невидимым not_verified читается как «проверено».
Минимум следа: scope (Шаг 1), sentinel (Шаг 4), запись от каждого контура и всё из плана
«Закрыть в следе» — включая 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 <файл отчёта> # по результатам прогона
node "$QG/tools/gate.mjs" release --class C0 --reason "<почему>" # C0/C1 без прогона
Отчёт — во временный каталог, не в проект: release копирует его в архив вне git;
ссылайся на путь Копия отчёта: (references/run-environment.md).
НЕ снимай гейт, если прогон прерван на полпути и отчёт не сформирован: он должен остаться для повторной проверки.
Чужие сессии не трогай. Гейт разделён по сессиям: несколько в gate.mjs status —
verify/release без --session <id> отказывают, «самая свежая» может быть чужой.
Идентификатор — в подсказке при взводе и в сообщении о блокировке
(references/run-environment.md).
Files (1c-quality-gate)
-
references
-
adversarial-audit.md 9.5 KB
# Состязательный аудит (Слой 3) Самая дорогая проверка плагина и единственная, которая **никогда не запускается сама**. Контуры лишь предлагают её в отчёте; запуск — только после явного согласия пользователя. Общий ресурс для контуров `bsl-code-review` и `bsl-architecture-review`: методология одна, различаются лишь измерения веера. ## Зачем нужен отдельный слой Обычное ревью ищет подтверждения: модель формулирует находку и на ней останавливается. Состязательный аудит устроен наоборот — каждую находку **пытаются опровергнуть**, и она проходит, только если опровергнуть не удалось. Это прямое продолжение принципа контр-сигнала: ложная находка провоцирует переделку рабочего кода. На крупном изменении, где находок десятки, доля ложных растёт, а цена каждой — чужое время и подорванное доверие к инструменту. Второе назначение — покрытие. Один проход смотрит на код одним взглядом; веер по измерениям находит то, что при последовательном чтении сливается в фон. ## Когда предлагать Предлагать в отчёте, если выполнено **хотя бы одно**: - класс изменения C3 и найдена хотя бы одна находка уровня 🔴 либо 🟠; - затронуты проведение документов, транзакции, права или интеграция — и объём выше C1; - пользователь явно просил «глубокий аудит», «будь исчерпывающим», «проверь тщательно»; - изменение уходит в продуктивную среду и откат дорог. **Не предлагать** при C0/C1, при чистом вердикте без находок, при эталонной правке (калька типового механизма, прошедшая предметный верификатор). Формулировка предложения в отчёте — одной строкой, с честной ценой: ``` Рекомендую состязательный аудит: класс C3, найдено 🔴 2 / 🟠 5. Это N параллельных ревьюеров и по 3 проверяющих на находку — заметно дороже обычного прогона. Запускать? ``` ## Схема прогона Три стадии. Вторая и третья — то, чего нет в обычном ревью. **1. Веер по измерениям.** Независимые ревьюеры, каждый смотрит на изменение под своим углом и не видит выводов остальных. Изоляция принципиальна: общий контекст порождает согласие, а нужны независимые мнения. Измерения для контура кода: корректность механики платформы · запросы и производительность · транзакции и блокировки · обработчики событий объектов · клиент-серверное взаимодействие · безопасность и права · антипаттерны кода, порождаемого моделью. Измерения для контура архитектуры: распределение ответственности · границы и контракты · связанность модулей · дублирование и ветвление вместо диспетчера · переусложнение (обратная сторона). **2. Состязательная проверка каждой находки.** По каждой находке — несколько независимых проверяющих, которым поставлена задача **опровергнуть** её, а не подтвердить. Формулировка задания принципиальна: «попробуй опровергнуть, при сомнении считай опровергнутой». Лучше давать проверяющим **разные линзы**, а не копии одного вопроса: воспроизводится ли дефект на конкретных входных данных · нарушает ли это правило платформы, а не вкус · существует ли законная форма, в которой такой код корректен (контр-сигнал). **3. Синтез.** Собрать выжившие находки, снять дубликаты по ключу локации, отсортировать по важность. Опровергнутые не выбрасываются молча — они попадают в отчёт отдельным списком с причиной опровержения: это защита от повторного «открытия» той же находки на следующем прогоне. ## Пороги | Параметр | Значение по умолчанию | Когда менять | |---|---|---| | Ревьюеров в веере | по одному на измерение | «быстро» — только измерения с находками из Слоя 1 | | Проверяющих на находку | 3 | «исчерпывающе» — 5 | | Порог прохождения | большинство не опровергло | 🔴 требуют единогласия для отклонения | | Максимум находок на проверку | 20 самых тяжёлых | больше — сначала синтез, потом второй круг | | Одновременных задач | 8 | среда с жёстким лимитом на конкурентные задачи — 4 | **Потолок параллелизма объявлен явно** по той же причине, что и бюджет обращений к индексу кода в контуре архитектуры. Без него вторая стадия на двадцати находках попытается развернуть шестьдесят проверяющих разом: часть упрётся в лимит среды, часть оборвётся на середине, а аудит отчитается по неполным данным и будет выглядеть выполненным. Веер запускается волнами по потолку, пока не пройдены все находки. Недобранная волна — это не «проверено», а причина остановиться и сказать об этом записью `skipped`. **Асимметрия для 🔴 намеренная.** Находку уровня Critical отклоняют, только если **все** проверяющие сочли её ложной. Пропустить настоящий Critical дороже, чем потратить время на разбор спорного. ## Если механизм параллельного запуска недоступен Оркестрация субагентов есть не в каждой среде и требует явного разрешения пользователя. Без неё аудит выполняется **последовательно и в сокращённом виде**: измерения с находками из предыдущих слоёв прогоняются по очереди, состязательная проверка делается для находок 🔴 и 🟠. Это дольше по времени, но методологически то же самое. Чего делать нельзя — молча пропустить слой, о котором заявлено в отчёте: тогда пользователь считает аудит выполненным. Пропуск фиксируется записью: ``` [qg skipped: layer=code, scope=adversarial-audit, reason=orchestration_unavailable] ``` ## Записи следа ``` [qg applied: layer=code, scope=adversarial-audit, ids=[std436,std783], verdict=violation:std436] [qg applied: layer=arch, scope=adversarial-audit, ids=[qg:ARCH-A1], verdict=clean] ``` В отчёте дополнительно указывается, сколько находок не пережило проверку — это показатель качества первых двух слоёв. Если опровергается больше половины, проблема не в аудите, а в порогах контуров: они выдают шум, и чинить нужно их. -
evidence-format.md 42.7 KB
# Формат следа проверок (evidence) Слова, которые плагин употребляет как свои — контур, архетип, признак, эвристика, часовой, след прогона, — объяснены в `glossary.md`. Машиночитаемый след прогона. Живёт в отчёте, в секции `## quality evidence`, по одной строке на проверку. Проверяется `tools/evidence-validator.mjs`. ## Зачем он нужен Невыполненная проверка неотличима от выполненной, если после неё ничего не остаётся. Отчёт «нарушений не найдено» одинаково выглядит и когда всё проверено и чисто, и когда инструмент не запустился, и когда слой просто забыли. След делает разницу видимой: каждая проверка оставляет запись, **включая обоснованный пропуск**. Валидатор отвергает записи, которые лишь выглядят заполненными: пустое обязательное поле или пустой список идентификаторов — это не «проверил ничего», а отсутствие проверки. ## Грамматика ``` [qg <тип>: <ключ>=<значение>, <ключ>=[<элемент>,<элемент>], ...] ``` Значение — либо скаляр без запятых и скобок, либо список в квадратных скобках. ## Типы записей ### `scope` — профиль изменения (ровно одна за прогон) ``` [qg scope: volume=C2, files=3, loc=+87/-12, archetypes=[query,transaction], complexity=[nesting:4], driver=archetype:transaction, resolved=code:L2|arch:skip|xml:n/a|hygiene:full, config=custom:volume+sentinel] ``` Обязательные поля: `volume`, `files`, `archetypes`, `driver`, `resolved`, `config`. `volume` — один из `C0`, `C1`, `C2`, `C3`. `archetypes` — список сработавших меток, либо `[none]`, если ни одна не сработала (пустой список запрещён — он неотличим от «не считали»). `driver` — что подняло глубину: `volume`, `archetype:<имя>` или `complexity:<метрика>`. **Метки берутся из колонки «Метка в следе»** таблицы архетипов в `../SKILL.md`; проектные — из `archetypes.custom` настройки. Список закрытый, и это не педантизм: на метках завязаны требования валидатора, а поле пишет модель — инструмент его не печатает. `queries` вместо `query` не «почти то же самое», а молчаливое неприменение правила, поэтому незнакомую метку валидатор называет и снятие гейта не пропускает. `config` — настройка проекта, применённая к этому прогону: `default`, если все пороги умолчаний, либо `custom:<секция>[+<секция>]` с перечнем переопределённых секций (`analyzer`, `volume`, `complexity`, `archetypes`, `sentinel`). **Строку печатает `node "$QG/tools/config.mjs" show`** — её переносят в запись, а не сочиняют: сочинённая отметка ничего не доказывает. Без неё «C1» из одного отчёта не означает того же, что «C1» из другого, а прогон, не заглянувший в настройку проекта, неотличим от прогона, который её учёл. Снятие гейта такую запись не пропустит; при обычном линте отсутствие поля — предупреждение, чтобы отчёты, собранные до его появления, оставались читаемыми. **Поле сверяется с фактической настройкой проекта, а не принимается на слово.** Приписать `config=default` там, где пороги задраны, не сложнее, чем забыть посмотреть настройку, — и последствия те же. Расхождение блокирует снятие гейта и называет оба значения. Отсюда же следует: если настройка изменилась после прогона, след устарел вместе с профилем и прогон надо повторить. **`volume` и `archetypes` тоже сверяются, но с расчётным профилем** — тем, что по файлам сессии вернул бы `computeProfile` (`tools/profile.mjs`). Правило асимметрично: модель вправе поднять глубину, но не понизить — заявленный `volume` ниже расчётного или отсутствие в `archetypes` сработавшей метки блокируют снятие гейта. Строка `scope` из `gate.mjs plan` проходит всегда: план считает профиль той же функцией по тем же файлам. Расхождение значит, что после плана файлы сессии изменились, — план печатается заново. ### `applied` — проверка выполнена ``` [qg applied: layer=code, scope=query-in-loop, ids=[std436,bslls:QueryInLoop], verdict=clean] [qg applied: layer=arch, scope=module-responsibility, ids=[qg:ARCH-A1], verdict=violation:qg:ARCH-A1] ``` Обязательные поля: `layer`, `scope`, `ids`, `verdict`. `layer` — `code`, `arch`, `xml` или `hygiene`. `scope` — что именно проверялось, **из закрытого словаря** `tools/evidence-scopes.mjs`. `ids` — непустой список идентификаторов: `stdNNN`, `bslls:<Код>`, `acc:NNN`, `v8cs:<код>`, `qg:<ЭВРИСТИКА>`, `patterns:<путь>`. `verdict` — `clean` либо `violation:<id>`. **Пространство `qg:` — тоже закрытый список**, реестр `QG_IDS` в том же `tools/evidence-scopes.mjs`. Пока идентификатор проверялся только по форме, проходило любое правдоподобное имя: в живой сессии больше половины `qg:*` в отчётах не существовало в плагине (`qg:XML-VALID` вместо `qg:XML-STRUCT`, выдуманный `qg:SKD-VALID`), и отчёт с вымышленными признаками выглядел строже настоящего. Неизвестный `qg:*` — ошибка, а не предупреждение. Чужие пространства (`std`, `bslls`, `acc`, `v8cs`) по-прежнему проверяются формой: их реестры не наши. **Почему словарь закрытый.** Пока проверялся только формат имени, проходило любое похожее слово — и этим пользовалась не злая воля, а документация: одну проверку называли `static-diagnostics`, `lsp-diagnostics` и `static-analysis` в трёх разных местах. Запись со свободным именем выглядит проверкой, но не закрывает ни одного требования: имена измерений и имена проверок сведены в одно пространство, и требование ищется по точному совпадению. Валидатор называет замену для каждого прежнего имени. **Проверка с инструментом заявляется только по прогону.** Строку `[qg applied: ...]` пишет в отчёт модель, и написанная по прочтении кода она неотличима от полученной прогоном — наблюдалось четыре раза подряд в одной живой сессии, причём вердикты случайно оказались верны. Поэтому инструменты печатают свою строку сами и отмечаются в журнале `.claude/.state/qg-runs.jsonl`, а валидатор в строгом режиме сверяет с ним каждую запись `applied`, чей `scope` есть в списке проверок с инструментом — полный словарь имён — `tools/evidence-scopes.mjs` (поле `tool` у записи `SCOPES`); тот же список печатает `gate.mjs plan` для конкретной правки. Отметка должна быть **не старше последней правки файлов** своей сессии: прогон, сделанный до правки, описывает состояние, которого уже нет. Это то же правило, по которому гейт снимает отметки проверенного при изменении файла. **Сверяется и покрытие.** В журнале лежат пути файлов, которые инструмент видел, и они сопоставляются с составом правки из состояния гейта: прогон по одному файлу из десяти больше не закрывает заявление обо всех десяти. Складываются все прогоны — гонять инструмент по частям законно, требование к результату, а не к способу. Файл засчитывается и тогда, когда в журнале записан содержащий его каталог: валидаторам XML путь дают то файлом, то каталогом объекта. Непокрытый файл — ошибка там, где инструмент применим к каждому файлу своего расширения (гигиена, проверки `.bsl`), и предупреждение для проверки структуры: часть служебных XML выгрузки не проверяет ни один валидатор, и отказ на них был бы находкой за отсутствующий инструмент. **`skipped` с причиной `not_applicable` тоже требует отметки.** Это утверждение о работе инструмента — «посмотрел файлы, правило к ним не относится», — и без требования оно было бы дырой шире исходной: такой записью закрывается любая проверка. То же относится к `no_queries_found` (query-lint смотрел файлы и запросов не нашёл), `no_metadata_resolved` (bsl-lint не нашёл XML объекта рядом с модулем) и `unreadable` (`catalog.mjs attest` самостоятельно проверил файлы и часть не читается — не принял заявление читателя на слово). Инструменты ставят отметку и на этих исходах. Прочие причины (`analyzer_unavailable`, `contour_not_installed`, `reader_unavailable`) отметки не требуют: ставить её некому — читатель или инструмент не запускался вовсе. **Проходы по каталогу антипаттернов заявляются всегда, когда запущен контур кода.** Записи `scope=ai-antipatterns` и `scope=platform-antipatterns` печатает режим `attest` инструмента `catalog.mjs` по результату субагента-читателя: он сверяет `examined`, `files`, каждую находку и каждый заявленный `unreadable` файл (не существует или не читается по факту, а не потому что так написал читатель) с каталогом и с содержимым файлов, и только тогда пишет журнал и печатает строку следа. Без записи об этих проходах строгий режим отказывает, обычный линт предупреждает. Причина: проходы делает модель, и без записи их пропуск неотличим от выполнения. Чего сверка не делает: она не защищает от записи, дописанной в журнал вручную. В отличие от поля `config`, где истина заново выводится с диска, независимого источника здесь нет. Это обнаружение молчания — и сказано об этом прямо, чтобы никто не принял его за большее. **Признак `needs: [diff]` (сегодня — `qg:AI-11`) не заявляется без сравнения версий.** Он виден только в дифе, а не в теле метода как он есть сейчас — читателю без оболочки его взять негде. Основание, которое `attest` признаёт, — только то, что сам вернул `git diff HEAD --` по файлам прогона: подстрочная сверка переданного файла с текстом, «упоминающим имя файла», пропускала обычный текст, дифф без единого hunk и дифф ЧУЖОГО файла с именем целевого, дописанным в комментарий (ревью round 1, task-22) — три фикстуры, каждая с `ok: true`. `--diff <файл>` остаётся входом для читателя (путь к `git diff HEAD --`, сохранённому оркестратором до делегирования), но attest ему не верит на слово: если файл передан, его заголовки `diff --git` и `@@` сверяются с тем, что вернул git по тем же файлам, — расхождение называет, чего не хватает или что лишнее. Без непустого git diff карточка с `needs: [diff]` в `examined` не бывает независимо от того, передан ли `--diff`. Сравнивать легитимно нечем — новый файл без истории, репозиторий без git — тогда единственный путь: `attest --no-diff-available` печатает честную запись вместо молчания: ``` [qg skipped: layer=code, scope=ai-antipatterns-diff, planned=[qg:AI-11], reason=no_diff] ``` ### `logic-review` — слой 2 контура кода не оставляет иного следа `advisor()` и, при высокой цене ошибки, холодный читатель (`../../bsl-code-review/SKILL.md`, слой 2) не печатают ничего, кроме того, что впишет в отчёт модель — в отличие от инструментальных проверок, здесь нет журнала, который отличил бы прогон от его пропуска. A/B-прогон на живой правке класса C3 показал: пропуск слоя 2 не заметил никто, потому что заметить было нечему. ``` [qg applied: layer=code, scope=logic-review, ids=[qg:...], verdict=clean] [qg skipped: layer=code, scope=logic-review, reason=advisor_unavailable] ``` Требование адресное: контур `code` дошёл до `L2` (поле `resolved` записи `scope`) — запись обязана быть одной из двух форм. Отсутствие записи на `L2` — ошибка строгого режима. **Находка слоя закрывается записью `violation`, как и любая другая.** Каждая находка 🔴 и 🟠 в тексте отчёта обязана иметь в следе запись `verdict=violation:<id>` хотя бы по одному идентификатору, который в ней назван. Дефект логики, у которого нет своего признака, пишется одним из двух: - `qg:LOGIC-CONTRACT` — код расходится с заявленным поведением: справкой, комментарием, описанием метода, подписью в интерфейсе; - `qg:LOGIC-CASE-LOSS` — часть допустимых входов молча теряется или обрабатывается не так: неполный разбор значений, отсечение соединением, пустой результат вместо ошибки. ``` [qg applied: layer=code, scope=logic-review, ids=[qg:LOGIC-CONTRACT], verdict=violation:qg:LOGIC-CONTRACT] [qg applied: layer=code, scope=logic-review, ids=[qg:LOGIC-CASE-LOSS], verdict=violation:qg:LOGIC-CASE-LOSS] ``` Одна запись — одно нарушение. Инструментальный признак (`qg:BSL-DISPATCH-NO-FALLBACK` и подобные) руками не заявляется и как нарушение: если дефект того же рода найден там, куда инструмент не достаёт (текст запроса внутри XML схемы компоновки), это `qg:LOGIC-CASE-LOSS`. Почему это правило. В третьем A/B блокирующая находка слоя 2 и ещё одна существенная стояли только в тексте отчёта: признака под них не было, модель записала `qg:LOGIC` прозой, а след остался без единого нарушения по ним — и валидатор принял отчёт с нулём предупреждений. Теперь валидатор разбирает текст отчёта и называет каждую находку 🔴/🟠 без покрывающей записи. Разбор прозы приближённый (важность берётся из заголовка, отклонённые и непроверенные разделы пропускаются), поэтому это предупреждение, а не ошибка, и ошибкой оно не станет. ### `skipped` — проверка не выполнялась, и это заявлено ``` [qg skipped: layer=arch, reason=volume_below_threshold] [qg skipped: layer=code, scope=static-analysis, planned=[bslls:*], reason=analyzer_unavailable] ``` Обязательные поля: `layer`, `reason`. Типовые причины: `volume_below_threshold`, `not_applicable`, `no_queries_found`, `no_metadata_resolved`, `unreadable`, `contour_not_installed`, `analyzer_unavailable`, `reader_unavailable`, `rlm_unavailable`, `platform_unavailable`, `stale_or_unavailable_index`, `verified_earlier`. Отметки в журнале требуют причины, утверждающие, что инструмент СМОТРЕЛ файлы: `not_applicable`, `no_queries_found`, `no_metadata_resolved`, `unreadable`. `reason=unreadable` печатает `catalog.mjs attest`, когда среди переданных файлов есть настоящие нечитаемые — `reader_unavailable`, напротив, отметки не требует: это значит, что читатель вообще не запускался, ставить отметку некому. `no_queries_found` и `not_applicable` — разные утверждения: первое значит «инструмент файлы ЧИТАЛ и запросов не нашёл», второе — «правило к файлам этого вида не относится». Файл, чьи запросы инструмент не умеет читать, прятался бы за вторым — поэтому query-lint его больше не пишет. ### `not_verified` — измерение непроверяемо доступными средствами ``` [qg not_verified: dimension=compilation, reason=no_platform] [qg not_verified: dimension=query-execution, reason=no_platform] ``` Обязательные поля: `dimension`, `reason`. Отличие от `skipped`: `skipped` — «слой можно было прогнать, но не требовалось или инструмент лежал»; `not_verified` — «этого в принципе нельзя проверить тем, что есть». Измерения: `compilation`, `query-execution`, `static-analysis`, `cross-config-resolution`, `artifact-freshness`. Последнее печатает `gate.mjs release`, когда собранный артефакт старше своих исходников (пары «исходники → артефакт» — секция `artifacts` настройки проекта); `static-analysis` и `cross-config-resolution` печатает `analyzer-run.mjs` сам: ``` [qg not_verified: dimension=static-analysis, reason=parse_failed, files=3] [qg not_verified: dimension=static-analysis, reason=not_in_analyzer_report, files=2] ``` `parse_failed` — файл не разобрался, и остальные находки по нему получены на обрывке дерева. `not_in_analyzer_report` — изменённый файл движок не видел вовсе: отсечён фильтром подсистем, не передан. Пока этой записи не было, такой файл давал результат, неотличимый от чистого. Число непроверенных файлов уходит в журнал прогонов, и молчание о них блокирует снятие гейта. Выгрузки внешних обработок и отчётов под эту запись больше не попадают: у них нет `Configuration.xml`, поэтому корень ищется по дескриптору `X.xml` рядом с каталогом `X`, и такая выгрузка разбирается вторым проходом от своего корня. Состава конфигурации в ней нет по устройству формата — об этом печатается отдельная оговорка: ``` [qg not_verified: dimension=cross-config-resolution, reason=standalone_artifact_without_configuration, roots=1] ``` Имя причины своё, а не общее с расширением: у расширения основную конфигурацию можно доложить в проект и разрешение восстановится, у внешней обработки — нет. Список измерений закрытый: `compilation`, `query-execution`, а также `static-analysis` и `cross-config-resolution` — последние два печатает `analyzer-run.mjs`, когда файл не разобран или основной конфигурации в проекте нет. Опечатка вроде `query_execution` оставила бы запись, которая выглядит заполненной, но требуемое измерение не закрывает; валидатор такое имя называет. **`compilation` — компилируемость тел модулей.** Ни загрузка конфигурации из файлов, ни выгрузка, ни валидаторы XML не компилируют тела: они разбирают структуру. Синтаксическая ошибка внутри процедуры проходит их все и всплывает лишь при инициализации модуля в базе. Ловит только проверка конфигурации платформой или реальный запуск. **`query-execution` — выполнимость запроса.** Близнец предыдущего. Текст запроса — строковый литерал: для анализатора это строка, для сборки бинарника — строка, для валидатора XML — строка. Проверки по тексту кода (`qg:QRY-ALIAS-SHADOWS-FIELD`) снимают один класс дефектов, но «Поле не найдено», несовместимость типов в `ОБЪЕДИНИТЬ` и опечатка в имени параметра виртуальной таблицы обнаруживаются только выполнением. Измерение считается закрытым двумя способами: оно проверено — тогда есть запись `applied` с тем же именем в `scope`; либо признано непроверяемым — тогда есть `not_verified`. Запись `not_verified` обязана прозвучать и в человекочитаемой части отчёта — одной фразой на запись. Валидатор этого не проверяет и проверить не может, но читатель отчёта видит «Ошибок: 0» и не видит след: измерено на живом разборе, что такая пара дважды создала ложную уверенность в проверенности запроса. Формулировка называет и вывод, и его границу: «текст запроса статически чист; выполнимость не проверялась — платформы нет». ``` [qg applied: layer=code, scope=query-execution, ids=[qg:QRY-EXECUTED], verdict=clean] ``` Что требует строгий режим: | Условие прогона | Требование | |---|---| | Все проверки `clean` | закрыт `compilation` | | В `archetypes` есть `query` | закрыт `query-execution` | Первое правило именно по измерению, а не «хотя бы одна запись `not_verified`»: иначе каждое новое измерение ослабляло бы проверку — прогон заявляет непроверенным что-нибудь одно, о компилируемости молчит, и полностью зелёный отчёт снова проходит. **Обе записи — заявление, а не доказательство.** Валидатор не умеет проверить, что запрос действительно выполнялся или что конфигурация действительно компилировалась: он требует сознательного выбора между «проверил» и «проверить было нечем». Это слабее сверки поля `config`, которую валидатор пересчитывает сам, и сильнее молчания, которое ничего не значит. ### `sentinel` — источник жив ``` [qg sentinel: target=v8std, id=std454, status=found] [qg sentinel: target=bslls, id=CommonModuleInvalidType, status=found, engine=bsl-analyzer@0.2.73] ``` Обязательные поля: `target`, `status` (`found` либо `not_found`). Без этой записи «нарушений не найдено» неотличимо от «источник недоступен». Прогон с `status=not_found` считается недостоверным. **Часовой проверяется по целям, а не «хотя бы один живой».** Иначе подтверждённый `v8std` маскирует мёртвый анализатор: в следе стоит `bslls:…` с вердиктом `clean`, рядом живой часовой по `v8std` — формально правило выполнено, а про анализатор не известно ничего. Правило: каждый вердикт `clean` опирается на источник, доказавший в этом прогоне, что он жив. | Идентификаторы в `ids` | Цель часового | |---|---| | `stdNNN`, `acc:NNN`, `v8cs:<код>` | `v8std` | | `bslls:<Код>`, `bslls:*` | `bslls` | | `qg:<ЭВРИСТИКА>`, `patterns:<путь>` | не требуется: это наши эвристики, внешнего источника у них нет | Часовой по `bslls` устроен как **фикстура**: мини-конфигурация из состава плагина с заведомо неверным сочетанием флагов общего модуля прогоняется тем же вызовом и тем же конфигом, что и рабочие файлы. Ожидается `CommonModuleInvalidType` — диагностика, читающая **метаданные**. Выбор не случаен: при потере контекста конфигурации гаснет именно этот класс проверок, а обычные замечания продолжают приходить, и отчёт выглядит рабочим. Часовой, доказывающий лишь «хоть что-то сработало», такое ухудшение пропустит. Записи `sentinel` и `applied` по слою `code` формирует `tools/analyzer-run.mjs` — вручную их писать не нужно. `bslls:*` — законная форма для чистого прогона: перечислять полторы сотни проверенных кодов бессмысленно. Нарушения, наоборот, выводятся по записи на код. ## Отклонённая находка: обоснование через последствие Формат требует обоснования от находки — сигнал против измеренного порога — и до сих пор ничего не требовал от обратного решения, «здесь это не важно». А пишет его тот же, кто писал код, и звучит оно короче любой находки: «пустая ветка смысла не несёт», «другого признака всё равно нет». Так самое слабое звено отчёта оказывается единственным местом без правил. Порядок обратный привычному: сначала назови **последствие** — что произойдёт, если инструмент прав, — и лишь потом объясняй, почему оно невозможно либо приемлемо. ``` 🟡 bslls:<Код> — <где>: <почему принято> Последствие, если находка верна: <что сломается и как это проявится> ``` Разница видна сразу. «Ветвление по типу документа без `Иначе`» отклонить легко, пока не написано последствие: документ неизвестного вида уходит во внешнюю систему, не пройдя ни одной проверки. «Поиск справочного значения по коду» — то же самое: после переименования элемента механизм перестаёт находить документы молча, без ошибки, и обнаружится это расхождением с внешней системой. Правило дешёвое и работает на самом частом сценарии: отчёт пишет тот, кто заинтересован в зелёном вердикте. Отклонение без названного последствия — не разбор, а согласие с самим собой. ### Довод, снимающий находку: источник или прогон Последствие описывает, что сломается, если находка верна, но ничего не говорит о том, откуда взят сам довод «здесь это не так». «Обработчик реально может сработать лишь на повреждённых данных — он защитный, а не маскирующий штатный путь» звучит как разбор, а по факту то же согласие с собой на один шаг дальше: утверждение о поведении платформы, ничем не подкреплённое. Правило: **довод, который снимает находку или понижает её важность, обязан опираться на прочитанный источник — файл и строку, страницу справочника платформы, пункт стандарта — либо на выполненный прогон — вывод инструмента, результат исполненной проверки.** Довода без того и другого не бывает — есть только предположение того, кто писал код, а находка существует ровно на случай, если это предположение ошибочно. Пример разрыва. Правка убрала проверку `Ответ.ТелоСтрокой = Неопределено` — кандидат в `qg:AI-11` (пропавшая проверка редкого случая при упрощении кода, разбор — `../../bsl-code-review/references/catalog/AI-11.md`). Один прогон отклонил находку доводом «обработчик исключения реально может сработать лишь на повреждённом gzip или неверном имени кодировки — он защитный, а не маскирующий штатный путь», не сославшись ни на строку кода, ни на прогон. `Тело` заполняется из `Ответ.ПолучитьТелоКакДвоичныеДанные()`; справочник платформы называет типом результата «ДвоичныеДанные, Неопределено», но не называет условие, при котором возвращается `Неопределено`, — прочитанный источник вопрос не закрывает. Более ранний прогон того же гейта на этом же коде эту неопределённость признал прямо: «Уверенность: средняя. Как проверить до правки: … Если … отдаёт `ДвоичныеДанные` нулевой длины, а не `Неопределено`, — находка снимается». Довод, отбросивший её позже, не прочитал страницу справочника до конца и не выполнил тот прогон — то есть не имел ни источника, ни прогона. Пока источника или прогона нет, находка **не снимается** — остаётся с понижением уверенности и называет проверку, которая её закроет: ``` 🟡 qg:AI-11 — <где>: обработчик <условие> не задаёт причину возврата `Неопределено` Уверенность: средняя. Как проверить: <один прогон или чтение, который решает вопрос> ``` **Когда так делать не нужно** — снимать находку без прогона можно, если прочитанный источник решает вопрос сам, а не просто упоминает возможность: - источник закрывает вопрос целиком: страница справочника платформы прямо называет условие («возвращает `Неопределено`, если тело ответа пусто») — находка снимается со ссылкой на страницу, прогон не требуется; - источник допускает исход, но не называет условие (как в примере выше) — вопрос остаётся открытым, и снять находку может только прогон; до него она держится с понижением уверенности. Проверяется это чтением текста находки, а не `evidence-validator.mjs`: инструмент видит формат записи следа, а не то, на чём держится довод внутри неё. ## Переиспользование доказательств Гейт — требование к **текущему состоянию артефакта**, а не просьба ещё раз позвать тот же инструмент. Если слой уже отработал по этому содержимому файла, повторный прогон ничего не добавляет, кроме расхода времени. Отметить проверенное: ```bash node "$QG/tools/gate.mjs" verify --layer code <файл> [<файл> ...] ``` Отметка хранится в состоянии гейта по паре файл × слой. Перед повторным прогоном слоя загляни в `gate.mjs status`: файлы с уже проставленной отметкой можно пропустить, записав `skipped` с причиной `verified_earlier`. **Инвалидация автоматическая.** Любая правка файла снимает все его отметки — это делает хук взвода. Устаревшее доказательство переиспользовано быть не может по построению: оно относится к другому содержимому. Это единственный случай, когда пропуск слоя не требует отдельного обоснования: причина объективна и проверяема по состоянию. ## Режимы валидатора ```bash node tools/evidence-validator.mjs <файл> # lint: только оформление node tools/evidence-validator.mjs <файл> --gate # строгий, для снятия гейта ``` Строгий режим дополнительно требует: ровно одну запись `scope`, хотя бы одну `applied`/`skipped`, подтверждённый `sentinel` **по каждой цели, на которую опирается вердикт `clean`**, закрытое измерение `compilation` при полностью чистом вердикте и закрытое `query-execution`, если сработал архетип `query`. Коды выхода: `0` — чисто, `1` — предупреждения, `2` — блокирующие нарушения. ## Пример полного следа ```markdown ## quality evidence [qg scope: volume=C1, files=1, loc=+18/-3, archetypes=[query], complexity=[none], driver=archetype:query, resolved=code:L2|arch:skip|xml:n/a|hygiene:full, config=default] [qg sentinel: target=v8std, id=std454, status=found] [qg sentinel: target=bslls, id=CommonModuleInvalidType, status=found, engine=bsl-analyzer@0.2.73] [qg applied: layer=hygiene, scope=file-encoding, ids=[qg:HYG-BOM,qg:HYG-DASH], verdict=clean] [qg applied: layer=code, scope=static-analysis, ids=[bslls:MissingCodeTryCatchEx], verdict=violation:bslls:MissingCodeTryCatchEx] [qg applied: layer=code, scope=query-in-loop, ids=[std436,bslls:QueryInLoop], verdict=clean] [qg applied: layer=code, scope=query-alias-shadowing, ids=[qg:QRY-ALIAS-SHADOWS-FIELD], verdict=clean] [qg applied: layer=code, scope=attribute-access, ids=[qg:BSL-REF-DOT-ACCESS,std437], verdict=violation:qg:BSL-REF-DOT-ACCESS] [qg skipped: layer=arch, reason=volume_below_threshold] [qg skipped: layer=xml, reason=not_applicable] [qg not_verified: dimension=compilation, reason=no_platform] [qg not_verified: dimension=query-execution, reason=no_platform] ``` Здесь видно не только что нашли, но и почему архитектурный контур не гонялся, почему XML неприменим, что компилируемость осталась непроверенной и что запрос ни разу не выполнялся. Последнее — не придирка: правка текста запроса прошла проверку по тексту, но «Поле не найдено» без выполнения не обнаружить, и отчёт говорит об этом прямо. Обратите внимание на двух часовых: вердикт `clean` в строке про `query-in-loop` опирается сразу на два источника — сервис стандартов и анализатор, — и оба обязаны подтвердить, что живы. Именно это отличает след от подписи «проверено». -
glossary.md 6.6 KB
# Словарь: слова, которые плагин употребляет как свои Здесь объяснены слова, которых нет в языке разработчика 1С и которые придуманы или заимствованы внутри этого плагина. Всё остальное — термины платформы и стандартов — объясняется по месту употребления либо ссылкой на номер стандарта. Файл читается один раз: дальше по документам эти слова используются без повторного объяснения. ## Устройство проверки **Гейт** — блокировка, которая не даёт объявить работу законченной, пока проверки не прогнаны. Взводится при изменении файлов 1С, снимается отчётом о прогоне. **Контур** — набор проверок одного рода со своим навыком и своими инструментами. Контуров четыре: код, архитектура, структура XML метаданных, гигиена файлов. Контур запускается целиком, а не выборочными пунктами. **Оркестратор** — навык `quality-gate`, который смотрит на состав правки, выбирает нужные контуры и глубину проверки, запускает их и собирает общий отчёт. Сам проверок не выполняет. **Профиль изменения** — три ответа о правке, по которым оркестратор выбирает глубину: сколько изменено, какого рода этот код, насколько он сложен. **Архетип кода** — род изменённого кода, опознаваемый по коду и путям файлов: запрос, транзакция, интеграция, форма, роль. Одна правка может относиться сразу к нескольким. От архетипа зависит, какие проверки обязательны. ## Проверки и находки **Признак** — то, по чему находка опознаётся: `qg:BSL-REF-DOT-ACCESS`, `qg:ARCH-A4`, `qg:AI-17`. Идентификатор со `qg:` есть в реестре плагина; чужие пространства — `std`, `bslls`, `acc`, `v8cs` — принадлежат стандартам разработки и внешним анализаторам. **Эвристика** — правило распознавания, которое срабатывает по совпадению формы кода, без доказательства. Оно даёт находки-кандидаты: часть из них при проверке окажется законной формой. Поэтому у эвристики всегда есть контр-сигнал — описание того, когда совпадение не является дефектом. **Контр-сигнал** — форма, в которой найденное законно. Проверка без него рано или поздно сработает на нормальном коде, и тогда перестают применять всю проверку целиком. **Проверка по тексту кода** — разбор исходного текста без выполнения и без обращения к метаданным. Так работают быстрые проверки плагина: они видят написанное, но не знают ни типов переменных, ни того, что вернёт вызванный метод. **Часовой** — заведомо дефектный образец кода из состава плагина, который прогоняется вместе с проверяемыми файлами. Если анализатор молчит на нём, значит он не работает вовсе, и «чисто» по проекту ничего не значит. **Холодный читатель** — второй просмотр кода моделью, которой не показывают ни задачу, ни переписку: только сам код. Он видит программу такой, какой её увидит сопровождение через год. **Контракт** — договорённость о том, какие данные метод принимает и что возвращает, записанная в его описании по #std453: состав полей структуры, колонки таблицы, тип и смысл каждого параметра. «Нарушить контракт» значит вернуть не то, что обещано описанием; «доверять контракту» — не перепроверять у себя то, что вызванный метод уже обязался обеспечить. ## Отчёт **След прогона** — машиночитаемая часть отчёта: по одной строке на каждую выполненную проверку, с признаком, областью и вердиктом. Проверяется отдельным инструментом, чтобы вердикт нельзя было написать, не запуская проверку. **Инварианты прогона** — несколько утверждений, без которых результат прогона недействителен. Если из всего навыка усвоено только это, прогон ещё имеет смысл. **Уровень «требует нового шва»** — граница между контуром кода и контуром архитектуры. Исправление, которое умещается в замену строк внутри метода, относится к коду. Исправление, требующее новой границы между частями программы — выделить метод, перенести его в другой модуль, изменить, кто кого вызывает, — относится к архитектуре. -
profile-axes.md 20.2 KB
# Профиль изменения: оси, матрица глубин и почему они устроены так Оркестратор считает профиль по трём осям и по нему выбирает глубину контуров. С Task 14 это делает инструмент: таблицы и пороги из этого файла — данные `tools/profile.mjs` (`ARCHETYPES`, функция `computeProfile`) и `tools/gate.mjs` (`TOOL_ORDER`, построение плана). `node "$QG/tools/gate.mjs" plan` печатает результат для конкретной правки — считать оси в голове по таблицам не нужно. Здесь — то же самое для чтения: расшифровка таблиц из кода и причины, почему ось устроена именно так, что она закрывает и какой дефект появился бы без неё. Читать, когда правишь правила профиля, добавляешь архетип, разбираешь спорный результат плана или объясняешь пользователю, почему на трёх строках прогон получился глубоким. --- ## Ось 1: объём (скаляр) Что тронуто и пересекает ли правка границы. Считает `computeProfile` по `git diff --numstat` и `git diff -U0` (или по всем строкам файла, если он «без истории» в HEAD). | Класс | Признаки | |---|---| | **C0** Косметика | комментарии, форматирование, переименование без изменения смысла; тела методов не менялись | | **C1** Точечная | файлов ≤ `volume.c1MaxFiles` (умолчание 1), изменённых строк ≤ `volume.c1MaxLines` (умолчание 40), правка внутри существующих методов; нет новых экспортов, изменённых сигнатур, новых модулей и объектов метаданных | | **C2** Модульная | >1 метода, ИЛИ новый экспортный метод, ИЛИ изменена сигнатура, ИЛИ превышен порог строк либо файлов — в пределах существующих модулей | | **C3** Структурная | новый модуль / объект метаданных / форма, ИЛИ изменение проведения и бизнес-логики, ИЛИ новая интеграция, ИЛИ затронуто ≥4 модуля разных подсистем | Новый общий модуль или объект метаданных выводит правку из C1 **безусловно**, раньше проверки числа строк: однострочный новый общий модуль — всё равно C3, а не C1 по объёму. Иначе определение C1 («нет... новых модулей и объектов метаданных») просто не выполнялось бы для такой правки, и класс разошёлся бы с собственным же условием. «>1 метода, ИЛИ новый экспортный метод, ИЛИ изменена сигнатура» — не оценка на глаз, а разбор границ `Процедура|Функция ... КонецПроцедуры|КонецФункции` (`methodRanges` в `tools/profile.mjs`, кириллица и латиница ключевых слов — обе формы). Для файла, у которого есть версия в HEAD, инструмент строит границы методов дважды — для рабочего дерева и для HEAD — и по изменённым строкам диффа (не суммарному числу, а конкретным номерам) решает: (a) сколько РАЗНЫХ методов правка задела — больше одного значит C2 независимо от суммарного числа строк; (b) есть ли в рабочем дереве метод, которого не было в HEAD под тем же именем — новый, экспортный или нет (переименование сюда тоже попадает: старое имя пропало); (c) совпадает ли строка сигнатуры (список параметров, `Знач`, `Экспорт`) существующего метода с версией в HEAD. Причина уходит в поле `volumeReason` (`methods:2`, `new-method:Имя`, `signature:Имя`, `lines`, `files`) и печатается `gate.mjs plan` отдельной строкой «объём: C2 (…)». Для файла БЕЗ версии в HEAD (только что созданный) сравнивать методы не с чем — правила (a)-(c) на него не распространяются, объём такого файла решают архетипы `new-common-module` / `new-metadata-object` (весь новый модуль/объект метаданных — C3) и обычный порог по размеру. ## Ось 2: архетипы кода (множество меток) **Объёма недостаточно: три строки внутри транзакции опаснее трёхсот строк переименований.** Архетипы не упорядочены и комбинируются — одна правка может быть одновременно «запросом», «транзакцией» и «интеграцией». Определяются механически по маркерам и путям файлов. Маркер ищется не только в добавленных строках диффа, но и в ПОЛНЫХ ТЕЛАХ методов, которых правка коснулась (working tree, те же границы `Процедура|Функция`, что и на оси 1). Иначе правка внутри уже архетипичного метода могла остаться незамеченной: если строка `Новый HTTPСоединение` в методе уже была, а diff тронул другую строку того же метода, маркер не попал бы в добавленные строки — но метод как был работой с HTTP-соединением, так ей и остался. Источник истины — `ARCHETYPES` в `tools/profile.mjs`; таблица здесь — для чтения, при правке архетипа менять нужно код, а не эту копию. | Архетип | Метка в следе | Маркер в изменениях | Мин. `code` | Мин. `arch` | |---|---|---|---|---| | Запрос | `query` | `Новый Запрос`, `ВЫБРАТЬ` | L2 | — | | Транзакция, блокировки | `transaction` | `НачатьТранзакцию`, `Заблокировать`, `БлокировкаДанных` | L2 | — | | Запись наборов записей | `record-set` | `Записать(Истина)`, `СоздатьНаборЗаписей` | L2 | — | | Обработчик события объекта | `object-event` | `ПередЗаписью`, `ПриЗаписи`, `ОбработкаПроведения`, `ОбработкаУдаленияПроведения`, `ПередУдалением` | L2 | ур. 1 | | Интеграция, HTTP | `integration` | `HTTPСоединение`, `WSПрокси`, `Новый COMОбъект` | L2 | ур. 1 | | Права, RLS | `rights` | XML ролей (`Roles/*/Ext/Rights.xml`), `УстановитьПривилегированныйРежим` | L2 | ур. 2 | | CFE-перехват | `cfe-patch` | `&Перед`, `&После`, `&Вместо`, `&ИзменениеИКонтроль` | L2 | ур. 1 | | Регламентное, фоновое | `scheduled-job` | путь `ScheduledJobs/`, `ФоновыеЗадания.`, `РегламентныеЗадания.` | L2 | — | | Клиент-сервер | `client-server` | директивы `&НаСервере...`, `&НаКлиенте...` | L1 | **ур. 1** | | Диалог посреди логики | `user-dialog` | `ПоказатьВопрос`, `ВопросАсинх`, `ОповещениеОЗавершении` | L1 | **ур. 1** | | Модуль формы | `form-module` | путь `Forms/*/Module.bsl` | L1 | ур. 1 при `loc > 400` | | Асинхронный клиент | `async-client` | `Асинх`, `Ждать`, `Обещание` | L1 | — | | Новый общий модуль | `new-common-module` | новый `CommonModules/<Имя>/Ext/Module.bsl` + декларация объекта | L1 | **ур. 2** | | Новый объект метаданных | `new-metadata-object` | новый XML/MDO в `src/`, кроме декларации общего модуля | L1 | **ур. 3** | **Колонка «Метка в следе» — ровно то, что пишется в поле `archetypes` записи `scope`.** Метка не переводится и не сокращается: `queries` вместо `query` означает не «почти то же самое», а «правило не сработало», и снятие гейта валидатор не пропустит. Свои архетипы проект заводит в секции `archetypes.custom` (имя, маркеры, `minCode`, `minArch`), их имена — тоже законные метки. ### Почему минимумы по контурам асимметричны У архетипа два минимума — по контуру `code` и по контуру `arch`, — и они намеренно не совпадают. В этом весь смысл оси. Правка внутри транзакции требует глубокого разбора **кода**: порядок `Попытка` и `НачатьТранзакцию`, вложенность, отмена. Проверять там SOLID нечего — метода два, границы модулей не двигались. Новый общий модуль на тридцать строк — зеркально: транзакций нет, антипаттернов производительности нет, а вопросы размещения, границ ответственности и флагов модуля критичны, и цена ошибки — годы жизни с неправильным швом. Одна шкала «глубины вообще» усреднила бы эти два случая и в обоих дала бы не тот результат. ### Архетипы проекта участвуют наравне со встроенными Секция `archetypes.custom` проектной настройки описывает свои архетипы: имя, маркеры в изменённых файлах, минимальные `minCode` и `minArch`. Они входят в правило разрешения так же, как встроенные, и их имя — законная метка следа: валидатор читает настройку. Без этого механизм, которого нет во встроенной таблице, не поднял бы глубину никогда, и знал бы об этом только автор проекта — остальные видели бы прогон, который «по правилам» проверил обмен как обычную правку. Запись `archetypes.custom` может вместо `name` задать `extends: "<встроенная метка>"` — расширить встроенный архетип своими маркерами, не заводя новую метку. Так учат плагин видеть проектную обёртку платформенного вызова (например, общий модуль-обёртка HTTP расширяет `integration`): находка идёт под встроенной меткой, справочники и чеклист архетипа не меняются, а `minCode`/`minArch` могут только поднять встроенный минимум. Разбор — `docs/CONFIG.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`); `gate.mjs plan` запускает его сам, если не передан `--no-analyzer`. Важно, чем именно запущен движок. Проектные конфигурации (`.bsl-language-server.json`, `bsl-analyzer.toml`) могут отключать диагностики или ограничивать анализ подсистемами через `subsystemsFilter`. С таким конфигом изменённые прикладные файлы в анализ не попадают вовсе, а отчёт остаётся правдоподобным: находок нет, метрик нет, вердикт «чисто». Гейт запускает движок со своим конфигом из состава плагина — проектный правит IDE, гейтовый правит гейтом. ## Матрица глубин по объёму (базовый уровень) | Контур | C0 | C1 | C2 | C3 | |---|---|---|---|---| | `hygiene` | полный | полный | полный | полный | | `code` | пропуск | L1 | L1 + L2 | L1 + L2, предложить аудит | | `arch` | пропуск | пропуск | ур. 1–2 | ур. 3 | | `xml` | пропуск | не применим, если XML не менялся | изменённые объекты + регистрация | полный: валидация + сироты в обе стороны + права ролей | | компилируемость | — | если платформа доступна | да | да | `hygiene` гоняется всегда: стоимость околонулевая, а ловит она то, что проявляется позже всего и объясняется хуже всего. У `arch`, как и у `code`, объём даёт пол сам по себе: C2 — не ниже уровня 1, C3 — уровень 3, безусловно. Живой след, показывавший `arch:skip` при `volume=C2` без сработавших архетипов (до Task 17, раунд 2), был отступлением модели от этой же таблицы при заполнении evidence, а не альтернативным прочтением правила. Конфликта с `minArch: 2` архетипа `new-common-module` нет: итог берёт максимум пола объёма и минимумов архетипов (`max(3, 2) = 3`), архетип пол не понижает. ## Правило разрешения и `driver` > **глубина контура = max(по объёму, максимум минимумов по сработавшим архетипам, по сложности)** Отдельно фиксируется **`driver`** — что именно подняло глубину: объём, конкретный архетип или сложность. Без него вердикт виден, а логика нет, и первое же «почему так долго на трёх строках?» превращается в спор, в котором ни одна сторона не может сослаться на запись, — а следующим шагом глубину начинают занижать вручную, потому что объяснить её нечем. `driver` считается отдельно по каждой оси (`code`, `arch`) через контрфактическое сравнение — во что превратился бы итог без вклада сложности и без вклада архетипов, — а не только по `code`: иначе прогон, где `arch` подняла ИСКЛЮЧИТЕЛЬНО правка архетипа (а по объёму `arch` остался бы `skip`), печатал бы `driver=volume` и врал о причине. ## Понижающий модификатор: эталонная правка Понижает глубину до C1 независимо от объёма, если выполнены **три условия сразу**: 1. все фрагменты — кальки типового эталона того же механизма с точечной адаптацией имён и полей; 2. они уже прошли предметный верификатор механизма с нулём ошибок; 3. они не трогают транзакции, блокировки и права. Невыполнение любого условия отменяет модификатор целиком. Два из трёх — не «почти эталонная правка», а обычная: именно в оставшейся трети и живёт отличие, ради которого правку писали руками. --- ## Пороги читаются из проекта, а не по памяти Значения в таблицах выше — умолчания. Действующие печатает `tools/config.mjs show` вместе с источником каждого: умолчание, проектный файл `.1c-quality-gate.json` или переменная окружения. Файл плагин заводит сам при первом взводе гейта — пользователю создавать его не нужно, и пустые секции в нём означают «берём умолчание», а не «настройка забыта». Расхождение вывода команды с таблицей разрешается в пользу команды. Иначе настройка, которую никто не читает, неотличима от правила, которое не сработало: проект переопределил `volume.c1MaxFiles`, прогон посчитал по умолчанию, и оба выглядят одинаково правдоподобно. ## Отметка `config=` переносится дословно Строка `[qg scope: ...]`, которую печатает `gate.mjs plan`, уже несёт готовое поле `config=...`. Валидатор пересчитывает его сам и сверяет с заявленным, поэтому написанное по памяти не пройдёт. Причина не формальная. Прогон, не заглянувший в настройку, оставлял бы запись, неотличимую от прогона, который её учёл. При этом «C1» в проекте с поднятыми порогами и «C1» в соседнем проекте — разные утверждения: без отметки читатель отчёта не может узнать, какое из них перед ним. -
run-environment.md 10.7 KB
# Среда прогона: путь к инструментам, часовой, субагенты Механика, от которой зависит достоверность прогона, но которая не участвует в решении о глубине. Команды и правила — в `SKILL.md`; здесь то, почему они выглядят именно так, и что ломается, если сделать иначе. --- ## Почему `$QG` разрешается командой `CLAUDE_PLUGIN_ROOT` доступна хукам, но **не оболочке**: в шелле она пуста, и путь вида `"${CLAUDE_PLUGIN_ROOT}/tools/..."` схлопывается в `/tools/...`. Проверено эмпирически. Отсюда правило: путь разрешается первой командой прогона, дальше подставляется буквально — состояние оболочки между вызовами не сохраняется. В OpenCode угадывать не приходится: у него есть хук `shell.env`, которым плагин кладёт корень пакета в переменную `QG_ROOT` при каждом запуске оболочки. Поэтому под OpenCode перебор не выполняется вовсе — первый же кандидат верен по построению. Почему это важнее, чем кажется. Пакет OpenCode ставит из репозитория и держит в своём кэше, обновляя при смене закреплённого тега. Прежняя редакция вместо этого раскладывала копию пакета в проект установочным скриптом и искала её по `QG_PROJECT_DIR`. Копия не обновлялась никогда, а переменная — публичная и не помечена как «только OpenCode»: сессия Claude Code, где её выставили руками, молча уходила работать в устаревшую копию. Ни ошибки, ни кода возврата — тот же класс отказа, против которого написан `sort -V` ниже. ## Порядок источников и `sort -V` Сначала `QG_ROOT` — прямой ответ от плагина OpenCode. Затем `installed_plugins.json`, указатель на **активную** версию, который ведёт сам Claude Code. Перебор каталогов кэша — только запасной путь, и обязательно через `sort -V`. В кэше лежат все когда-либо установленные версии, а лексикографический порядок ставит `0.10.0` раньше `0.9.0`. Сессия, взявшая последний по алфавиту каталог, работает инструментами устаревшей версии и честно отчитывается, что проверок «не существует» — диагноз, который выглядит как отсутствие функциональности, а не как неверный путь. Инструменты печатают свою версию в выводе: если поведение кажется устаревшим, сверь её. ## Где лежит отчёт Место файла отчёта раньше выбирала модель, и отчёты оседали в двух местах. Одно — временный каталог сессии: его чистят, и ссылка в журнале снятий потом ведёт в пустоту. Другое — документация проекта, рядом с кодом и под версионным контролем. Теперь принятый отчёт раскладывает утилита: `gate.mjs release` копирует его в `qg-reports/` каталога состояния под датой снятия и пишет путь копии в журнал снятий (`evidenceArchive`). Место одно при любом выборе модели, а каталог состояния вне git — архив не засоряет репозиторий. Черновик пишется во временный каталог, чтобы в проекте не остался второй экземпляр; отчёт, написанный прямо в архив, не копируется. Черновик в каталогах проекта снятию не мешает, но `release` называет его предупреждением: иначе оригинал рядом с кодом остался бы незамеченным. Отклонённый след в архив не попадает, снятие без отчёта (C0/C1 с причиной) архива не заводит. Не удалась запись копии — гейт всё равно снят: прогон принят, копия лишь удобство; вывод говорит об этом прямо, а поле в журнале остаётся пустым. ## Часовой: почему номер задан настройкой Номер стандарта для проверки живости берётся из `sentinel.id` (умолчание `std454`). Будь он зашит в навык, исчезновение именно этой страницы уронило бы часового во всех проектах разом, и отличить «страницы больше нет» от «сервис стандартов недоступен» было бы нечем. Настройка даёт проекту сменить цель, не трогая плагин. Смысл самой проверки: без неё «нарушений стандартов не найдено» неотличимо от «источник стандартов не отвечал». Неподтверждённый часовой делает прогон недостоверным, и валидатор следа отклоняет снятие гейта. ## Субагенты: границы применения Штатные субагенты только читают и никогда не пишут записи следа сами: агент возвращает факты, запись формирует контур. Иначе нельзя проверить, кто что утверждал, — а отчёт, в котором вердикт написан тем же, кто собирал факты, теряет единственную точку сверки. Недоступность субагента проверку не отменяет: контур выполняет её сам либо пишет `skipped` с причиной. Веер из многих агентов существует только в состязательном аудите (слой 3) и запускается исключительно после явного согласия пользователя — методология в `adversarial-audit.md`. ## Деградация записывается, а не замалчивается Контур не установлен (плагин ставится по частям) или его инструменты недоступны — пишется `skipped` с точной причиной: `contour_not_installed`, `lsp_unavailable`, `rlm_unavailable`, `platform_unavailable`. Полный список причин и что каждая означает — в `evidence-format.md`. Деградация допустима всегда. Недопустимо одно: молчание, при котором непрогнанная проверка выглядит как прогнанная и чистая. ## Чужие сессии Состояние гейта разделено по сессиям: блокирует только то, что правила текущая сессия. Если `gate.mjs status` показывает несколько сессий, `verify` и `release` без `--session <id>` отказывают с перечнем — утилита не угадывает свою по свежести, потому что в параллельной работе самая свежая сессия почти всегда чужая. Идентификатор печатается в подсказке при взводе гейта и в сообщении о блокировке; при одной сессии `--session` не нужен. Общий на проект маркер запирал бы сессию, которая правила совсем другие файлы. Обратная ошибка дороже: снятие чужого гейта объявляет проверенной работу, которую ты не видел, и перехватывает чужую сессию. ## Файлы вне корня и сторонние каталоги Файл вне корня проекта взводит гейт как обычный, ключом служит абсолютный путь — в журнале прогонов та же форма, сверка покрытия сходится. Путезависимые проверки (bsl-lint, query-lint, гигиена, валидаторы XML) работают по нему штатно; привязанные к проекту — статический анализатор по конфигурации, индекс кода — его не увидят и закрываются записью `not_verified` с точной причиной: заявленное приближение, а не пропуск. Корень проекта команды определяют сами: переменная окружения, иначе подъём до `.1c-quality-gate.json`, иначе до `.git`. Запуск из каталога другого репозитория уводит корень к его `.git`: журнал и состояние уезжают туда, и «гейт не взведён» звучит честно — но про другой проект. Поэтому `status` и отказы печатают корень со способом опознания, а при работе из стороннего каталога корень задаётся явно: `QG_PROJECT_DIR=<корень>` в окружении команды.
-
-
SKILL.md 14 KB
--- 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`, а не удалением файла состояния. 9. **План печатает `gate.mjs plan`**; модель вправе поднять глубину с причиной, понизить — нет. --- ## Шаг 1. План ### Путь к инструментам плагина (`$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`). ### Профиль и план прогона ```bash node "$QG/tools/gate.mjs" plan --files <f> [<f> ...] [--no-analyzer] ``` Без `--files` — состав из взведённой сессии (`gate.mjs status`). Печатает профиль по трём осям, строку `scope` (с `config=...`), инструменты по порядку, что закрыть в следе. `--no-analyzer` — когда анализатор недоступен или падает; ось сложности тогда `not_computed`, `driver` не бывает `complexity:*`. **Строку `[qg scope: ...]` перенеси в отчёт дословно** — валидатор пересчитывает профиль сам. Расхождение — сверь порог: `node "$QG/tools/config.mjs" show`. **Профиль можно поднять, нельзя понизить**: увидел то, чего план не видит (правка тише архетипа, но трогает деньги) — подними, добавь «глубина поднята: <причина>»; обратное валидатор не пропустит. Почему оси устроены так — `references/profile-axes.md`; данные — `tools/profile.mjs`, `tools/gate.mjs`. --- ## Шаг 2. Инструменты Выполни команды раздела «## Инструменты» плана по порядку — файлы уже подставлены. У части есть исполняемый инструмент: он печатает строку следа (`[qg applied: ...]` / `[qg skipped: ...]`) сам — переноси дословно, не сочиняй. Прогон отмечается в журнале `qg-runs.jsonl`: валидатор сверяет по нему каждую запись `applied` — инструмент видел **весь** состав правки, а не файл из десяти; `skipped ... reason=not_applicable` — тоже утверждение о работе. Прогоны по частям складываются. `platform-context-run.mjs`, когда включён планом, обязателен: анализатор знает имена конфигурации, но не платформы. `query-lint` берёт и изменённые XML, не только `.bsl`. **Перед повторным прогоном слоя проверь `gate.mjs status`:** отработавший по этому содержимому слой пропускается с причиной `verified_earlier` (`references/evidence-format.md`). --- ## Шаг 3. Контуры Запускай контуры и глубины из `resolved: code=... arch=... xml=... hygiene=...` плана. Каждый контур обязан вернуть `applied` либо `skipped` — **молчание не допускается**. | Контур | Навык | |---|---| | `code` | `bsl-code-review` | | `arch` | `bsl-architecture-review` | | `xml` | `xml-structure-review` | | `hygiene` | `file-hygiene` | Передавай контуру профиль целиком: класс, архетипы, файлы. ### Контур исполняется вызовом навыка, а не по памяти Таблица называет навыки, а не проверки: чем проверяется признак — в SKILL.md контура. Прогон без открытия навыка — чеклист для чтения глазами, не проверка (`references/run-environment.md`). ### Субагенты в составе прогона Четыре штатных, **только читающие**: возвращают факты, запись формирует контур. Три дешёвые; `antipattern-reader` — на модели уровня контура: семантика LLM-антипаттернов не механизируется до haiku. | Субагент | Вызывает | Факты | Спавнов | |---|---|---|---| | `bsl-verifier` | `code` | сигнатуры платформы, экспортность модулей, метаданные | на список файлов | | `antipattern-reader` | `code` | кандидаты с цитатами; аттестует `tools/catalog.mjs` | на контур | | `bsl-scout` | `arch` | вызывающие, экспорты, триггеры XML | по вопросу | | `xml-runner` | `xml` | «диск ↔ состав», валидаторы структуры | на контур | Недоступность субагента, контура или инструмента проверку не отменяет — она проходит сама либо получает `skipped` с причиной (`references/run-environment.md`). --- ### Слой 3: состязательный аудит Самая дорогая проверка, никогда не запускается сама: контуры предлагают её при классе C3 с находками 🔴/🟠, запуск — только по явному согласию пользователя (`references/adversarial-audit.md`). --- ## Шаг 4. Sentinel — проверка живости источника стандартов Один раз за прогон запроси через MCP `v8std` стандарт, чей номер — `sentinel.id` настройки (умолчание `std454`): `v8std_get_page("<id>")`, ожидание — страница найдена. Без неё «нарушений не найдено» неотличимо от «сервис недоступен»: неподтверждённый часовой делает прогон недостоверным (`references/run-environment.md`). --- ## Шаг 5. Отчёт и след Отчёт для человека — находки по важности (🔴 Critical / 🟠 Major / 🟡 Minor). Ниже, в `## quality evidence`, — машиночитаемый след: строка на проверку (`references/evidence-format.md`). **Каждая запись `not_verified` повторяется в человекочитаемой части фразой**: «Ошибок: 0» рядом с невидимым `not_verified` читается как «проверено». Минимум следа: `scope` (Шаг 1), `sentinel` (Шаг 4), запись от каждого контура и всё из плана «Закрыть в следе» — включая `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 <файл отчёта> # по результатам прогона node "$QG/tools/gate.mjs" release --class C0 --reason "<почему>" # C0/C1 без прогона ``` Отчёт — во временный каталог, не в проект: `release` копирует его в архив вне git; ссылайся на путь `Копия отчёта:` (`references/run-environment.md`). НЕ снимай гейт, если прогон прерван на полпути и отчёт не сформирован: он должен остаться для повторной проверки. **Чужие сессии не трогай.** Гейт разделён по сессиям: несколько в `gate.mjs status` — `verify`/`release` без `--session <id>` отказывают, «самая свежая» может быть чужой. Идентификатор — в подсказке при взводе и в сообщении о блокировке (`references/run-environment.md`).
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.