git:20260906.1f0fab4 to git:20260906.74b3426

1 added, 0 removed. Audit A to A.

---
name: bsl-code-review
description: >-
Контур проверки кода BSL: диагностики статического анализатора, антипаттерны производительности
и механики платформы, стандарты разработки #stdNNN, именование, верификация сигнатур API и
существования общих модулей. Уровень «внутри тела метода» — то, что чинится заменой строк.
Вызывается оркестратором quality-gate с готовым профилем изменения; напрямую — по запросу
«проверь код», «отревьюй что я написал», «проверь на антипаттерны».
license: MIT
---
# bsl-code-review — контур кода
Проверяет то, что чинится **внутри тела метода**: замена строк, без нового шва. Всё, что
требует выделения метода, переноса в другой модуль, нового экспорта или изменения «кто кого
вызывает», принадлежит контуру `bsl-architecture-review` — граница и правила отсева повторов
находок в `shared/routing-contract.md`.
<ЖЁСТКИЙ-ШЛЮЗ>
Только проверка и отчёт. НЕ переписывай логику, запросы, транзакции и права по своей
инициативе. В режиме `--fix` допустимы лишь безопасные категории (см. ниже).
</ЖЁСТКИЙ-ШЛЮЗ>
## Инварианты контура
Пять утверждений, без которых прогон контура недействителен.
1. **Оба файла антипаттернов прогоняются всегда** — и на мелкой правке тоже. Находка 🔴 из
любого блокирует вердикт «чисто».
2. **Строку следа инструментальной проверки печатает инструмент** — переноси дословно,
своих находок этого класса не добавляй: результат детерминирован.
3. **Каждое замечание доказуемо**: номер стандарта, код диагностики или название
антипаттерна плюс строка кода. «Так лучше» — не находка.
4. **Пропуск фиксируется.** Недоступный инструмент или субагент даёт `skipped` с причиной;
молчание неотличимо от выполнения.
5. **Файл, который анализатор не разобрал, не проверен** — вердикт «чисто» по нему
невозможен, и в отчёте он назван поимённо.
## Вход
От оркестратора: класс изменения (C0…C3), сработавшие архетипы, список изменённых файлов.
При прямом вызове — определи профиль сам по правилам `quality-gate`.
| Класс | Глубина |
|---|---|
| C0 | контур не запускается |
| C1 | Слой 1 |
| C2 | Слой 1 + Слой 2 |
| C3 | Слой 1 + Слой 2, предложить Слой 3 |
Архетип поднимает глубину независимо от класса: запрос, транзакция, запись наборов записей,
обработчик события объекта, интеграция, права, CFE-перехват, регламентное задание — минимум
Слой 2. Итоговая глубина — максимум из требований объёма, архетипов и сложности.
---
## Слой 1а — статический анализ
Одна команда: она находит корень конфигурации, прогоняет только изменённые файлы, проверяет
часового и формирует записи следа.
```bash
node "$QG/tools/analyzer-run.mjs" --changed <файл> [--changed <файл> ...]
```
Вывод — находки по файлам и готовый блок `## quality evidence`. Перенеси его в отчёт как
есть: записи следа по слою `code` сочинять руками не нужно и нельзя.
**Твоя работа здесь — триаж, а не припоминание.** Список нарушений детерминирован. От тебя
требуется отделить то, что надо чинить сейчас, от того, что является осознанной нормой этого
проекта, и назвать последствие каждой оставленной находки. Коды расшифровывай через
`v8std_explain_diagnostics` и привязывай к номеру стандарта.
Четыре режима вывода, каждый из которых меняет то, что можно утверждать по результату:
- **Информационные находки свёрнуты** в одну строку, полный список — флаг `--all`. В след
коды попадают в любом случае.
- **Проект без основной конфигурации** (репозиторий одного расширения): диагностики о
неразрешённых именах понижены до информационных — обратно **не поднимай**, отличить их от
настоящих ошибок в этом режиме нечем.
- **«НЕ РАЗОБРАНО файлов»** — по этим файлам не проверено **ничего**. Назови их в отчёте
поимённо: вердикт «чисто» по ним невозможен.
- **Часовой `status=not_found`** — прогон недостоверен, вердикт «чисто» запрещён; разберись
с анализатором и повтори.
Что стоит за каждым режимом и известные случаи — `references/analyzer-output.md`. Гейтовый
анализ идёт с конфигом из состава плагина: проектный `subsystemsFilter` вывести изменённые
файлы из проверки не может.
**Если анализатор недоступен** — команда сама запишет
`[qg skipped: layer=code, scope=static-analysis, planned=[bslls:*], reason=analyzer_unavailable]`
и вернёт код 1. Продолжай со Слоя 1б: он ловит другое и от анализатора не зависит.
### Второй движок — сверка со справочником платформы
```bash
node "$QG/tools/platform-context-run.mjs" --changed <файл> [--changed <файл> ...]
```
Ловит то, чего анализатор не видит вовсе: несуществующий член платформенного типа, значение
системного перечисления, конструктор, свойство объекта. Всё это компилируется и падает при
выполнении. Сервер справки движок заводит сам: ищет поднятый, а не найдя — ставит закреплённый
релиз и поднимает свой по установленной платформе. Где платформы на машине нет, пишет `skipped`
с причиной и возвращает код 1 — это законный исход.
Два правила: **`info` «низкая уверенность» не отбрасывать** (класс смешанный) и **часовой
`not_found` — «чисто» запрещено**. Остальное — `references/platform-api.md`.
## Слой 1б — то, чего анализатор не видит
### 1. Антипаттерны производительности и механики платформы
Источник: `references/bsl-anti-patterns.md` — прогоняется **всегда**, при любой глубине.
| Антипаттерн | Что искать | Важность |
|---|---|---|
| Запрос в цикле | `Новый Запрос` внутри `Для Каждого` | 🔴 |
| Чтение реквизита через точку | `.Реквизит` у ссылочного типа — грузит объект целиком | 🔴 |
| Подзапрос в списке полей | вложенный `ВЫБРАТЬ` в секции выборки (N+1) | 🔴 |
| Коррелированный подзапрос в условии | вложенный `ВЫБРАТЬ` в `ГДЕ`, ссылающийся на внешнее поле | 🔴 |
| Фильтр виртуальной таблицы в ГДЕ | условие на результате ВТ вместо её параметров | 🟠 |
| Временная таблица без индекса | `ПОМЕСТИТЬ` без `ИНДЕКСИРОВАТЬ ПО`, далее соединение по этому полю | 🟠 |
| Отсутствие ограничения выборки | большой запрос без `ПЕРВЫЕ N` | 🟠 |
| Множественные серверные вызовы | последовательные вызовы сервера с клиента | 🟠 |
| Контекстный вызов без нужды | `&НаСервере` там, где хватает `&НаСервереБезКонтекста` | 🟠 |
| Транзакция внутри Попытки | `НачатьТранзакцию()` внутри `Попытка`, а не наоборот | 🟠 |
| Транзакция внутри неявной транзакции | `НачатьТранзакцию()` в `ОбработкаПроведения`, `ПередЗаписью`, `ПриЗаписи` | 🟠 |
| `Сообщить()` как уведомление | сообщение без привязки к объекту и вне журнала регистрации | 🟠 |
| Отсутствие кеширования | повторные дорогие вызовы с теми же параметрами | 🟡 |
| Поиск перебором во вложенных циклах | сопоставление наборов циклом вместо `Соответствие` | 🟡 |
### 2. Антипаттерны кода, порождаемого моделью
Источник: `references/ai-antipatterns.md` — прогоняется **всегда, наравне с предыдущим**.
Ошибки, характерные для кода языковой модели: перепроверка контракта собственной функции,
форк парсера на каждый вариант ответа, отчёт о непрогнанной проверке. Типовые своды их не
покрывают — человек таких ошибок обычно не делает.
### 3. Проверки по тексту кода
```bash
node "$QG/tools/query-lint.mjs" <файл.bsl|файл.xml> [<файл> ...]
node "$QG/tools/bsl-lint.mjs" <файл.bsl> [<файл.bsl> ...]
node "$QG/tools/rename-check.mjs" <файл.bsl> [<файл.bsl> ...]
python "$QG/tools/xml/form-validate.py" -Path <Form.xml> # правился модуль формы
```
`query-lint` читает и запросы, лежащие в XML, — `<query>` схем компоновки данных и
`<QueryText>` динамических списков, — поэтому **изменённые XML передаются ему наравне с
модулями**; номер строки в такой находке считается от начала текста запроса, как в
сообщениях платформы. Оба инструмента печатают готовые записи следа и отмечаются в журнале
прогонов.
| Признак | Sev | Что ловит | Разбор |
|---|---|---|---|
| `qg:QRY-ALIAS-SHADOWS-FIELD`, `qg:QRY-ALIAS-SHADOWS-NESTED-TABLE` | 🔴 / 🟠 | псевдоним совпал с именем колонки ВТ пакета или табличной части, чей владелец в той же ветке: «Неоднозначное поле». Разыменование — 🔴, без обращения через точку — 🟠 | `references/bsl-query-reference.md` |
| `qg:QRY-TOP-WITHOUT-ORDER` | 🟡 | `ПЕРВЫЕ N` без `УПОРЯДОЧИТЬ ПО` — набор строк недетерминирован | `references/bsl-anti-patterns.md` п. 5 |
| `qg:BSL-TXN-IN-HANDLER` | 🟠 | своя `НачатьТранзакцию` внутри обработчика, который платформа уже выполняет в транзакции (#std783 п. 1.4) | `references/bsl-anti-patterns.md` п. 8б |
| `qg:BSL-ENUM-STRING-ASSIGN` | 🟠 | примитив в поле строго ссылочного типа: сборка молчит, падает при записи | `references/bsl-anti-patterns.md` п. 8в |
| `qg:BSL-STALE-LOCAL-CALL` | 🔴 | вызов метода, чьё объявление было в HEAD и исчезло в правке: переименование не доведено до точек вызова | `references/bsl-anti-patterns.md` п. 8г |
| `qg:BSL-UNBOUNDED-STRING-COLUMN` | 🟠 | строковая колонка без квалификатора у таблицы, уходящей в параметр запроса (#std432 п. 3.1) | `references/ai-antipatterns.md`, `qg:AI-16` |
| `qg:BSL-REF-DOT-ACCESS` | 🔴 / 🟠 | обращение к реквизиту ссылки через точку: объект читается целиком ради одного поля (#std437). Ссылочность доказывается присваиванием в методе или типом параметра из описания #std453 (🟠 — описание могло устареть), либо именем на «Ссылка» | `references/bsl-anti-patterns.md` п. 2 |
| `qg:BSL-FORM-ATTR-SHADOW` | 🔴 | имя реквизита формы у переменной | `references/bsl-anti-patterns.md` п. 8д |
| `qg:BSL-DISPATCH-NO-FALLBACK` | 🟠 | перебор значений перечисления или типов по трём и более веткам без `Иначе`: непредусмотренное значение проходит цепочку молча | `references/bsl-anti-patterns.md` п. 8е |
+ | `qg:BSL-DB-READ-IN-LOOP` | 🟠 | чтение базы, достижимое из тела цикла через вызов метода: то же N+1, что в п. 1, только распределённое по методам (#std436). Прямую форму ловит анализатор | `references/bsl-anti-patterns.md` п. 1а |
**`attribute-access` покрыт инструментом лишь частично.** Доказать ссылочность в пределах
одного файла удаётся не всегда: ссылка из чужой функции или из недокументированного параметра
остаётся неопознанной. `clean` здесь означает «механическая часть чиста» и разбора #std437
глазами не отменяет — инструмент задаёт нижнюю границу, а не верхнюю.
**Записи переносятся дословно, своих находок этого класса не добавляй.** Результат
детерминирован, а строка, составленная по прочтении кода, выглядит в отчёте точно так же —
поэтому валидатор следа её отвергает.
Оба инструмента видят один файл и графа вызовов не строят, а запрос, собранный конкатенацией
или `СтрШаблон`, виден им лишь частями. Эти области остаются непроверенными, и вердикт
«чисто» по инструменту их не закрывает — разбор приближений у каждого правила в его
справочнике.
### 4. Стандарты под архетип
Не весь свод подряд — только релевантное:
| Архетип | Справочники |
|---|---|
| Запрос | `bsl-query-optimization.md`, `bsl-query-reference.md` |
| Модуль формы | `bsl-form-module-rules.md` |
| Асинхронный клиент | `bsl-async.md` |
| Новый модуль, форматирование, транзакции | `bsl-coding-standards.md` |
| Кастомная утилита | `bsp-common-modules.md` — есть ли готовый метод библиотеки |
| Глубокая вложенность, длинные методы | `bsl-refactoring.md` |
Плюс `references/checklist-code.md` — 17 разделов по областям; бери разделы под затронутый
архетип. Тексты самих стандартов запрашивай через MCP `v8std` по номеру.
### 5. Именование
`#std454` — частая и легко пропускаемая ошибка: сокращения-префиксы, не-CamelCase,
булево не в утвердительной форме. Детали и примеры — в `references/checklist-code.md`.
### 6. Символы в исходнике
В коде и комментариях только ASCII-дефис. Длинное тире и его родственники дают у анализатора
ошибку недопустимого символа. Кавычки-ёлочки допустимы.
### 7. Верификация API — субагент `bsl-verifier`
Сигнатуры платформенных методов, существование и экспортность общих модулей, состав объектов
метаданных. Процедура — `references/api-verification.md`.
**Делегируй субагенту `bsl-verifier`**, передав ему список изменённых `.bsl`-файлов. Он
дешёвый, работает по той же процедуре и возвращает вердикт, список нарушений с локациями и
раздел «Не проверено». Вызов **один на весь список**: каждый лишний инстанс поднимает свою
сессию индекса кода, а справочник платформы на stdio-транспорте вдобавок не переносит
параллельных обращений.
Если прогнан второй движок слоя 1а, платформенная часть уже закрыта: субагенту остаются
общие модули, метаданные и контекст доступности.
Субагента в среде может не быть — тогда прогоняй `api-verification.md` сам. **Результат
обязан попасть в след одинаково в обоих случаях** (инвариант 4):
```
[qg applied: layer=code, scope=api-verification, ids=[qg:API-SIGNATURE,qg:API-MODULE], verdict=clean]
[qg skipped: layer=code, scope=api-verification, reason=platform_unavailable]
```
> Для класса C1 на этом контур завершается — переходи к отчёту.
---
## Слой 2 — ревью логики моделью
Вызови `advisor()`. Более сильная модель видит весь транскрипт: задачу, шаги, написанный код.
Ловит то, что статика не видит в принципе — неверную бизнес-логику, упущенные сценарии,
неучтённые состояния. Замечаниям давай весомый вес.
### Холодный читатель — второй взгляд с противоположным входом
Дополнительно к `advisor()`, когда цена ошибки высока: класс C3 либо затронуты проведение,
деньги, права, необратимые операции. Ценность даёт противоположность входов, а не второе
мнение — почему, разбирает `references/cold-reader.md`.
**Передавать:** только сравнение версий и содержимое изменённых файлов. **Не передавать:** формулировку
задачи, свои выводы, названия найденных проблем — узнавший намерение читатель перестаёт
быть холодным.
Три вопроса, на которые он отвечает:
1. Что этот код делает **как написан**, а не как задуман?
2. На каких входных данных он ломается или ведёт себя неожиданно?
3. Какое ожидаемое поведение из него не следует?
**Модель — не дешёвая:** выносится суждение о логике, уровень не ниже основной модели
сессии. Расхождение его выводов с `advisor()` — сигнал, а не шум: код допускает два прочтения.
## Слой 3 — состязательный аудит (только по подтверждению)
**Никогда не запускается сам** — контур лишь предлагает его в отчёте и ждёт явного согласия.
Суть: веер независимых ревьюеров по измерениям, затем по каждой находке несколько
проверяющих, которым поставлена задача её **опровергнуть**. Проходит только то, что
опровергнуть не удалось.
Состав измерений, пороги, правила голосования, асимметрия для находок 🔴 и порядок действий,
когда оркестрация недоступна, — в `../quality-gate/references/adversarial-audit.md`.
---
## Автофикс (`--fix`)
**Можно:** именование (через переименование символа анализатором, не текстовой заменой),
форматирование и отступы, канонические ключевые слова, магические литералы на системные
константы, очевидные quick-fix анализатора.
**Нельзя без подтверждения:** любая правка логики, проведения, запросов; транзакции и
блокировки; права и привилегированный режим; всё, помеченное 🔴; сигнатуры экспортных методов
(ломает вызывающих).
После автофикса прогони Слой 1 заново — правки могли внести новые диагностики.
---
## Выход
### Находки
```
[🔴/🟠/🟡] <краткая суть>
Где: <путь:строка>
Правило: #stdNNN п.X | антипаттерн «<название>» | #bslls:<Код>
Проблема: <что именно не так здесь>
Как исправить: <конкретно; для 🔴 — со ссылкой на пример из справочника>
```
Ключ локации `<путь>::<Метод>:<строка>` обязателен — по нему оркестратор дедуплицирует
находки с архитектурным контуром (правила — в `shared/routing-contract.md`).
### Записи следа
Минимум одна на каждый слой — выполненный или пропущенный:
```
[qg applied: layer=code, scope=query-in-loop, ids=[std436,bslls:QueryInLoop], 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=code, scope=static-analysis, planned=[bslls:*], reason=analyzer_unavailable]
```
Вторая строка — из тех, что печатает инструмент: `attribute-access` стал инструментальным, и
написанная руками, она валидатор больше не проходит.
Формат — `../quality-gate/references/evidence-format.md`.
Два измерения контур закрыть не может и обязан об этом сказать. **Компилируемость тел
модулей** проверяет только платформа: без запуска проверки конфигурации нужна запись
`[qg not_verified: dimension=compilation, reason=no_platform]`, иначе полностью чистый вердикт
валидатор отклонит. **Выполнимость запроса** — то же самое при сработавшем архетипе «Запрос»:
```
[qg applied: layer=code, scope=query-execution, ids=[qg:QRY-EXECUTED], verdict=clean]
[qg not_verified: dimension=query-execution, reason=no_platform]
```
Проверка по тексту кода (пункт 3 Слоя 1б) её не заменяет — «Поле не найдено» и
несовместимость типов в `ОБЪЕДИНИТЬ` всплывают только при выполнении. Почему оба измерения
устроены так — `../quality-gate/references/evidence-format.md`.