18 KiB
Решения по гейтам и тестам
Статус: active. Журнал решений «что сознательно НЕ вводим» и «как читаем результат CI». Только про гейты, линтеры, coverage, e2e и CI — для обычных правок production-кода не нужен. Читать, когда проектируется или меняется сам гейт, либо когда спрашивают «почему так, а не иначе». Оперативные команды и пороги — в
docs/quality-gates.md, правила нагрузки на машину — вdocs/agent-workflow.md, раздел 3.
Здесь лежат решения, чтобы не пересматривать их заново. Для каждого: причина и
то, что решение пересмотрело бы. В docs/backlog.md такие пункты не
дублируются: backlog живёт задачами, а не историей решений.
Числа ниже — разовые замеры, а не текущие значения: помечены датой. Актуальные
цифры даёт прогон (docs/quality-gates.md), по правилу AGENTS.md в документах
нет величин, которые меняются вместе с кодом.
1. Детектор дублирования разметки — не вводим
Ни свой AST-правило, ни готовая зависимость (jscpd, eslint-plugin-sonarjs).
- Замер по корпусу
.svelteвcomponents/**иroutes/**(сентябрь 2026): при пороге 5 нормализованных строк — одна осмысленная пара, при пороге 3 — 17 пар, и все они шум (</label>,{/each},{#if children}). Семейство schema-контролов делит максимум 4 строки — законное сходство by design. - Причина: построчное сравнение Svelte-разметки принципиально ненадёжно — одинаковый контейнер с разным содержимым даёт ложное срабатывание, а намеренно общий класс — ложную тишину. Чтобы не ловить шум, порог пришлось бы поднять до 12+ строк, и тогда правило срабатывает ровно один раз.
- Контроль остаётся на ревью (расхождение видно в диффе) и на поведенческих
e2e-проверках; извлечение общей разметки закреплено конвенцией в
AGENTS.md. - Пересмотреть, если появится реально «уехавшая» копипаста в компонентах или библиотека разрастётся на уровень композиций страниц.
2. Линтер согласованности комментариев — не вводим
Описания инструментов различаются осознанно, и ловить это текстовым правилом
дороже, чем смотреть на ревью. Действенная часть выполнена вручную: все
TODO/FIXME перенесены в backlog, незарегистрированных пометок в коде нет;
новые запрещены правилом в AGENTS.md.
3. Линтер «изменяемых чисел» в документации — не вводим
Правило «не фиксировать числа, которые меняются вместе с кодом» живёт в
AGENTS.md и держится на ревью. Автоматизация отклонена по замеру (сентябрь
2026): regex «число + существительное» на корпусе Markdown дал 15 совпадений, из
которых 14 — ложные («30 дней» политики архива, «9 опций» описания поля, «4
проекта» факт из `playwright.config.ts», цитата самого правила).
- Правило всё равно неполно: ловит «M инструментов», но не «инструментов — M», не «более 100», не «≈120»; на каждое обходное движение нужна правка правила, а allowlist легитимных чисел становится вторым источником долга. Худший исход — не «шумно», а «вроде гейт, а толку нет»: срабатывания на пустом месте забейзелят, и через месяц правило перестанет ловить настоящие нарушения. Видимое блокирующее правило, которое на деле ничего не проверяет, хуже отсутствия правила.
- Соблюдение правила дешевле обхода: «список — в
docs/tools-map.md» стоит столько же символов, сколько цифра, но не требует обновления. - Пересмотреть, если появится источник истины, из которого число можно вычислить и сравнить автоматически, без разбора прозы. Такой случай есть и решён отдельно — следующий пункт.
4. Сверка docs/tools-map.md с реестром — автоматизирована
Правило про числа не закрывает более опасный класс расхождений: документ не
устарел числом, а перечисляет то, чего нет в коде. В разделе «Реализовано»
карты лежали несуществующие страницы (png-info, color-wheel-generator,
jpg-to-png, watermark-image-png), которые агент принял бы за готовые
инструменты. Счёт при этом был верный — число такой класс не ловит.
Сверка множеств отвечает на вопрос однозначно и без allowlist, поэтому
автоматизирована: web/src/lib/registry/tools-map-doc.test.ts парсит раздел
«Реализовано» и сравнивает slug с PAGES в обе стороны (slug без страницы и
страница без записи), плюс ловит повтор slug между буллетами. Формат строки
карты — всё до первого тире, slug через / или , — закреплён в самом
документе и в тесте. Сверяются именно страницы, а не инструменты: карта
описывает то, что видит пользователь, а один инструмент может обслуживать
несколько страниц (см. docs/architecture.md, раздел 3).
Чтобы сломанный парсер не выглядел как успех, у теста есть отдельная проверка
«разбор не пустой»: если формат карты снова поменяется, тест упадёт с сообщением
про парсер, а не молча перестанет что-либо проверять. Входит в обычный
pnpm --dir web test; новых гейтов и конфигов не добавляет.
Граница автоматизации: парсер читает разделы «Реализовано», «Можно добавить» и «Идеи», но проверяет их в разных направлениях. Раздел 1 сверяется с реестром обеими сторонами (slug без страницы и страница без записи). Разделы 2 и 3 сверяются только в одну сторону: там slug'и намеренно несуществующие, поэтому проверяется, что ни один из них не является реальной страницей — реализованный инструмент, оставшийся в плане, роняет тест. Проверять «у плана есть запись в реестре» нельзя по построению: список плановый, а не фактический.
Плановые разделы устроены прозой, поэтому их разбор другой: берутся все токены нужной формы из всего буллита, а не только из «головы» до тире. Следствие для авторов карты: реализованные страницы в планах называются прозой между буллитами, а не внутри них.
5. Visual snapshots — не вводим
toHaveScreenshot сравнивает страницу с PNG-baseline из прошлого коммита, а не
с замыслом дизайна. При активно меняющемся визуальном языке это превращает
каждый дизайн-PR в регенерацию и ревью картинок, а baseline платформенные
(снимок с Windows не совпадёт на ubuntu-latest). Потребность закрывают
e2e-поведение, lint:tokens с token audit, i18n-гейт и ручной чеклист
docs/checklist-manual-testing.md.
Вернуться стоит, когда визуальный язык стабилизируется; тогда первым идёт /kit
в двух темах, а baseline рождаются на Linux.
6. Fail-режим для unused-token аудита — не вводим
Остаётся предупреждением: verify входит в deploy-workflow, поэтому стилевой
долг блокировал бы деплой сайта. Кроме того, аудит видит только ссылки
var(--x), и чтение токена из JS дало бы ложное срабатывание. Правило «новый
токен — вместе с потребителем» живёт в AGENTS.md.
7. Per-project expect.timeout — не задаём
Firefox и WebKit отдают результат так же быстро, как Chromium: расхождения
объясняются памятью, а не скоростью движка. Таймаут под конкретную машину —
магическое число, которое придётся подстраивать на каждом раннере, поэтому
Playwright-конфиг остаётся с дефолтами, а проблема решается лимитом воркеров.
Практические правила — docs/agent-workflow.md, раздел 3.
8. Дублирование verify между workflow — сознательно
Deploy to GitHub Pages и Quality оба зовут verify: deploy проверяет
быстрый gate и собирает сайт, Quality прогоняет тот же verify и browser
matrix. По замеру (сентябрь 2026) verify — единицы минут, matrix — около трёх,
поэтому дублирование дешевле условных job-и, а matrix дёшево держать на каждом
PR. Актуальные длительности — в логе прогона. Ручной
gh workflow run quality.yml нужен только для перепрогона старого коммита.
9. Политика e2e в CI
Текущее состояние: e2e — non-blocking baseline, запускается на каждом пуше в
main и на каждом PR целиком (все проекты matrix). Ночного разделения нет: при
стоимости job около трёх минут оно не окупается, поэтому новые browser-critical
спецификации просто добавляются в browserContractTests.
e2e станет blocking, когда выполнятся три условия:
- Условия matrix пересмотрены: решено, какие спецификации обязаны идти в
browserContractTestsиmobileTests, нужен лиpng-fixturesвне Chromium и что делать с engine-специфичными расхождениями декодеров. - Накопились стабильные прогоны на раннере без падений по таймингу.
- Вместе с
continue-on-errorснимается и сам trick с общим conclusion: иначе зелёный прогон при красномe2eостанется возможен.
Пока условия не выполнены, e2e остаётся non-blocking, а его статус читается по
отдельному job — см. следующий раздел.
10. Чтение результатов CI после пуша
Первый реальный прогон browser matrix происходит на runner, а не на локальной машине: локально Firefox и WebKit проходят на 1 воркере, но состав, скорость и память Linux-раннера отличаются. Поэтому после пуша разработчика:
- дождаться завершения workflow и посмотреть, какие job вообще запустились;
- смотреть статусы отдельных job, а не общий conclusion прогона: у
e2eстоитcontinue-on-error: true, поэтому прогон с упавшим e2e целиком помечается какsuccess— проверено на run36225579363(E2E: failureприconclusion: success); - если
e2eзапускался и зелёный — сообщить фактические числа passed, failed, skipped; - если
e2eне запускался, сказать это прямо: matrix на раннере не проверена, и её можно прогнать черезgh workflow run quality.yml; - красный
e2eтолько из-за ресурсов раннера фиксируется как ограничение окружения: сначала проверяются один проект и один spec (docs/agent-workflow.md, раздел 3), а не увеличиваются таймауты.
11. Тексты страниц: в базовой локали секция pages пустая
Заголовки и описания страниц переводятся (pages.<slug>.*), а EN-строка лежит в
реестре страниц — дублировать её в en.ts нельзя, иначе при правке реестра
английский текст разъедется со страницей, а dict-consistency этого не увидит.
Поэтому en.ts объявляет pages: {} — пустую секцию, а линтер правила
i18n/dict-consistency не считает это нарушением: в базовой локали отсутствие
pages.*.title/description разрешено опцией baseLocaleFallback, а полнота
перевода для остальных локалей проверяется строго и юнит-тестом
(tools-i18n.test.ts).
Альтернатива — allowPaths: ["pages"] — была бы слабее: она разрешила бы пустую
секцию pages в любой локали, то есть пропущенный перевод страницы прошёл бы
линтер молча. Пустая секция в EN и есть то, что делает правило точным.
12. Бамп версии и changelog — при пуше в main, а не в PR
Версионирование и changelog генерирует бот (bump.yml, cocogitto) на push в
main, а не на pull request.
- При PR-боте параллельные PR бампили бы одну и ту же версию и конфликтовали по
CHANGELOG.md; коммиты бота внутри чужого PR — шум, а версия росла бы за ещё не влитый код. Репозиторий trunk-based: коммиты идут прямо вmain, деплой и так запускается на push. - Цикл: push в
main→verify.yml(переиспользуемый gate) →cog bump --autoбампит версии в обоихpackage.json(футер сайта читаетweb/package.json), обновляетCHANGELOG.md, ставит тег и пушит без--skip-ci, чтобы версионный коммит заново запустил деплой и сайт показал актуальную версию. Зацикливание исключено dry-run проверкой: после версионного коммита conventional-коммитов после тега нет, бот выходит. - Пуш идёт токеном GitHub App (
XBOCT_BOT_ID/XBOCT_BOT_PRIVATE_KEY), а неGITHUB_TOKEN: пуш стандартным токеном не запускает workflow, и деплой с новой версией не случился бы. concurrency: bumpбезcancel-in-progress: два бампа параллельно запушили бы конфликтующие версии.- Пересмотреть, если появятся внешние контрибьюторы и поток пойдёт через PR — тогда боту понадобится режим «черновик релиза в PR» или squash-стратегия с conventional-заголовком PR.