mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 14:39:22 +00:00
process: mutants run only in the nightly full registry; speed rules for ship/show (#709)
Owner's decision 2026-09-29: mutants check the tests, not the product. During development they are not run at all — not locally, not in CI, not by the reviewer. The whole registry is the nightly run (mutation-gate.yml, #513); a survivor files an issue (#472). The #693 post-mortem: 36 of 57 minutes of a one-line fix went to optional work. - process-track.mjs: `mutants` is always false (no track, no label). - classify-changes.mjs: Validate requests no diff mutants on any event; the `mutants` input stays so old `-f mutants=…` calls do not fail. - _process.yml: the default for the gate and the merge is false. - pre-push-gate.mjs: the manual run no longer runs mutants. - Canon: PROCESS §2.7 (a mutant is written, not run; `--check` keeps the anchors), §5.1 (`ci:mutants` retired), §8 (ship/show: nothing beyond gate:small and the spec — one proof per item, no `--smokes` on ship, a stray flake is an issue, not an investigation), §10.4; AUTHOR, REVIEWER, AGENTS, TESTING. - Registry: four mutants of the old request rules replaced by dev-mutants-requested-again and track-pays-for-mutants-again. Issue: #709 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
@@ -413,8 +413,8 @@ jobs:
|
||||
echo "МАТЕРИАЛ НЕ ЗАПУШЕН" >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
|
||||
# Трек решает цену захода (PROCESS.md §5, §10.4, #696): мутанты по диффу —
|
||||
# только `track:ask` и метка `ci:mutants`; `ship` сливается без модели,
|
||||
# Трек решает цену захода (PROCESS.md §5, §10.4, #696): мутантов в
|
||||
# разработке нет ни на одном треке (#709); `ship` сливается без модели,
|
||||
# если правка укладывается в механические рамки. Рамки судят дифф от
|
||||
# merge-base с dev — работу задачи, а не то, что принёс dev, — поэтому шаг
|
||||
# стоит до ребейза. Выход за рамки повышает трек до `show` здесь же:
|
||||
@@ -702,9 +702,8 @@ jobs:
|
||||
--add-label S6-in-progress --remove-label S7-code-review
|
||||
echo "S7-code-review -> S6-in-progress (ревью не запускалось)"
|
||||
|
||||
# Мутанты по диффу бегут только по запросу (#510): до ревью конвейер
|
||||
# запускает Validate на материале и ждёт его — с мутантами на `ask` и по
|
||||
# метке `ci:mutants`, лёгкий на `show`/`ship` (#696). Лёгкому годится и
|
||||
# До ревью конвейер запускает Validate на материале и ждёт его — всегда
|
||||
# лёгкий: мутантов в разработке нет (#709), весь реестр — ночью. Лёгкому годится и
|
||||
# завершённый push-прогон на том же SHA: dispatch повторил бы его. Красный или
|
||||
# пропавший прогон возвращает задачу автору без ревью — цикл не
|
||||
# тратится на код, который CI уже отверг (08.09: #437 дважды ушёл в S6
|
||||
@@ -737,7 +736,7 @@ jobs:
|
||||
# прогон гейт и без ожидания возвращает сразу.
|
||||
set +e
|
||||
node scripts/validate-gate.mjs --repo="${{ github.repository }}" --ref="$BRANCH" --sha="$SHA" --no-wait \
|
||||
--mutants="${MUTANTS:-true}" --full="${FULL:-false}"
|
||||
--mutants="${MUTANTS:-false}" --full="${FULL:-false}"
|
||||
code=$?
|
||||
set -e
|
||||
case "$code" in
|
||||
@@ -1753,7 +1752,7 @@ jobs:
|
||||
exit 0
|
||||
fi
|
||||
node scripts/merge-candidate.mjs --branch="$BRANCH" --material="$MATERIAL_SHA" \
|
||||
--issue="$NUM" --repo="${{ github.repository }}" --mutants="${MUTANTS:-true}"
|
||||
--issue="$NUM" --repo="${{ github.repository }}" --mutants="${MUTANTS:-false}"
|
||||
|
||||
- name: Переставить метку
|
||||
if: steps.ready.outputs.proceed == 'true'
|
||||
|
||||
@@ -21,12 +21,11 @@ on:
|
||||
description: 'Полный набор: смоки, golden, performance_smoke (без мутантов — #601)'
|
||||
type: boolean
|
||||
default: true
|
||||
# Мутанты по диффу бегут только по явному запросу (#510, #601):
|
||||
# ревью-конвейер и слияние кандидата запускают `-f full=false
|
||||
# -f mutants=true` на материале. `full=true`, трейлер `Release:` и ночь
|
||||
# их не включают — ночью идёт полный реестр (mutation-gate.yml).
|
||||
# #709: мутанты в разработке не гоняются — вход оставлен, чтобы старые
|
||||
# вызовы `-f mutants=…` не падали, но job мутантов по диффу он больше не
|
||||
# включает (classify-changes.mjs). Весь реестр — ночью (mutation-gate.yml).
|
||||
mutants:
|
||||
description: 'Мутанты по диффу на этом SHA (кандидат ревью или слияния)'
|
||||
description: 'Не действует с #709: мутанты — только ночной полный прогон'
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
@@ -650,6 +649,10 @@ jobs:
|
||||
# слияние кандидата запускают Validate по кнопке с `mutants=true` на
|
||||
# материале; PR берёт её сам. За 08–09.09 на промежуточных пушах она стоила
|
||||
# 48 из 56 часов job-минут и в основном отменялась следующим пушем. С #601
|
||||
# С #709 job не запрашивает никто: мутанты в разработке не гоняются, весь
|
||||
# реестр проверяет ночь (mutation-gate.yml). Job оставлен до отдельной уборки
|
||||
# вместе с контрактом имён job (#622); `mutants_requested` всегда false.
|
||||
# Ниже — история до #709.
|
||||
# её не запрашивают ни `full=true`, ни кандидат беты (`Release:`): к бете
|
||||
# каждая задача уже прогнана мутантами на ревью и на слитом кандидате, а
|
||||
# ночь покрыта полным реестром. Когда мутанты запрошены, job бежит даже при
|
||||
|
||||
@@ -84,8 +84,9 @@ a spec review. Any agent may raise a track with a reason; only the owner lowers
|
||||
class A file — skips analysis and spec and enters at `S7-code-review`
|
||||
(`PROCESS.md` §1). Every change is code-reviewed; on `ship` the review moves to a
|
||||
batch review of the beta range before the tag (`ship-review.yml`, `PROCESS.md`
|
||||
§11.7). The review pipeline prices each round by track (§10.4): diff mutants
|
||||
only on `ask` or with `ci:mutants`; a rebase before review only on `ask` or when
|
||||
§11.7). The review pipeline prices each round by track (§10.4): no mutants run
|
||||
during development on any track — the whole registry runs nightly (#709); a
|
||||
rebase before review only on `ask` or when
|
||||
the branch does not merge cleanly into `dev`. Review checks scope, risks and the
|
||||
evidence from executed tests, but does not replace executing them.
|
||||
|
||||
|
||||
+47
-37
@@ -245,20 +245,25 @@ S1-new → S2-analysis → S3-spec → S4-spec-review ⟲ → S5-ready →
|
||||
- **Защитный AC доказывается таблицей «чем краснеет» (#435).** Для каждого AC,
|
||||
заявляющего защиту — валидация, гард, лимит, отказ, инвариант, — в документе
|
||||
ревью обязательна строка из трёх столбцов: **AC · чем доказан** (точная
|
||||
команда или имя теста) **· чем краснеет** — мутация, снятая защита или
|
||||
отрицательная проба, с результатом прогона. Пустой третий столбец — находка
|
||||
Medium, а не примечание.
|
||||
команда или имя теста) **· чем краснеет** — мутант в реестре или
|
||||
отрицательный случай в самом тесте; прогон мутанта даёт ночь (#709). Пустой
|
||||
третий столбец — находка Medium, а не примечание.
|
||||
|
||||
«Тест умеет падать» без названной мутации и её вывода доказательством не
|
||||
является. Аудит v1.71.0-beta.1 нашёл пять контрактов #51 и #423, где тест
|
||||
«Тест умеет падать» без названной мутации доказательством не является; её
|
||||
поимку подтверждает ночной прогон (#709). Аудит v1.71.0-beta.1 нашёл пять контрактов #51 и #423, где тест
|
||||
оставался зелёным на снятой защите; все пять прошли код-ревью как доказанные,
|
||||
а два теста были записаны в закрытие coverage-ratchet под именами, обещавшими
|
||||
то, чего они не проверяли (#430).
|
||||
|
||||
Мутант в реестре `scripts/mutation-registry.mjs` обязателен, когда защита живёт в
|
||||
продуктовом коде и проверяется дорогим гейтом (смок, бэкенд, golden): там
|
||||
ревьюер не воспроизведёт отрицательный прогон второй раз. Для чистых юнитов
|
||||
достаточно прогона со снятой защитой, приведённого в документе.
|
||||
продуктовом коде и проверяется дорогим гейтом (смок, бэкенд, golden). Для
|
||||
чистых юнитов достаточно отрицательного случая в самом тесте.
|
||||
|
||||
**Мутант пишется, но в разработке не гоняется** — ни локально, ни в CI, ни
|
||||
ревьюером (#709, решение владельца 2026-09-29). Мутанты проверяют тесты, а не
|
||||
продукт, и их цена — десятки минут на задачу. Якоря реестра сверяет статический
|
||||
`mutation-gate --check`. Поимку проверяет только ночной полный прогон
|
||||
(`mutation-gate.yml`, #513); выживший мутант ночью заводит issue (#472).
|
||||
|
||||
Новый мутант с browser-smoke guard допустим только когда инвариант нельзя
|
||||
доказать без браузера: `because` обязан назвать конкретную зависимость от
|
||||
@@ -620,10 +625,10 @@ patch-id кандидата слияния: вердикт к работе за
|
||||
|---|---|
|
||||
| `ci:full` | полный Validate на ветке задачи |
|
||||
| `ci:golden` | golden на ветке и приёмку сдвинутых кадров в самой задаче |
|
||||
| `ci:mutants` | мутанты по диффу на кандидате ревью и слияния |
|
||||
|
||||
Как конвейер читает метки, описывает §10.4: `ci:mutants` (#696), `ci:full` и
|
||||
`ci:golden` — dispatch Validate с `full=true` на материале ревью (#697).
|
||||
Как конвейер читает метки, описывает §10.4: `ci:full` и `ci:golden` — dispatch
|
||||
Validate с `full=true` на материале ревью (#697). Метка `ci:mutants` снята
|
||||
(#709): мутанты в разработке не гоняются ни на каком треке.
|
||||
|
||||
**Прежние метки.** `trivial` и `small` читаются как `track:show`; продуктовая
|
||||
задача без трековой метки — как `track:ask`; инфраструктурная задача (§1) без
|
||||
@@ -759,6 +764,17 @@ typecheck, юниты, целостность и бюджет бандла, `no-
|
||||
`lint:unused` и вывод `smoke-select`. Команды ниже — то же самое по отдельности
|
||||
плюс то, что по диффу и AC:
|
||||
|
||||
**Цена `ship` и `show` — без добровольных надбавок** (#709, разбор #693: из 57
|
||||
минут однострочного фикса 36 ушли на необязательное). Сверх `gate:small` и
|
||||
названного в ТЗ автор ничего не прогоняет:
|
||||
|
||||
- мутанты не гоняются (§2.7);
|
||||
- на пункт ТЗ — одно доказательство, дублей вида «смок и юнит на одно и то же» нет;
|
||||
- `gate:small -- --smokes` на `ship` не нужен, на `show` — только смоки из вывода
|
||||
`smoke-select` и названные в AC;
|
||||
- флак, найденный по ходу, — отдельное issue одной записью: что упало, где,
|
||||
как часто. Расследование в рамках задачи не ведётся.
|
||||
|
||||
```
|
||||
npm run gate:small # обязательная часть одной командой
|
||||
npx tsc --noEmit
|
||||
@@ -930,7 +946,7 @@ Performance зелёные на точном SHA, плюс зелёный E2E н
|
||||
| `rejected` | Отклонено, issue закрыт |
|
||||
|
||||
Модификаторы: трек `track:ship`/`track:show`/`track:ask` (§5), тяжёлые проверки
|
||||
`ci:full`/`ci:golden`/`ci:mutants` (§5.1), `hotfix`, `process`, `review-4`;
|
||||
`ci:full`/`ci:golden` (§5.1), `hotfix`, `process`, `review-4`;
|
||||
приоритет `P1`/`P2`/`P3`; тип `bug`/`feature`/`tech-debt`. Прежние `small` и
|
||||
`trivial` читаются как `track:show` (§5.1).
|
||||
Тематические метки (`polish`, `infra`, `tests`, `docs`, `security`, `vacuum`)
|
||||
@@ -1166,12 +1182,12 @@ Matysh/houseplan-card/.github/workflows/_<имя>.yml@dev` с `secrets: inherit`
|
||||
|
||||
| | `ship` | `show` | `ask` |
|
||||
|---|---|---|---|
|
||||
| Validate на материале | лёгкий; годится завершённый push-прогон на этом SHA | лёгкий, как у `ship` | с мутантами по диффу, dispatch |
|
||||
| Validate на материале | лёгкий; годится завершённый push-прогон на этом SHA | лёгкий, как у `ship` | лёгкий, как у `ship` (#709) |
|
||||
| Ребейз до ревью | нет, если `git merge-tree` с `dev` чистый | нет, если чистый | да (#257) |
|
||||
| Ревью модели | нет — пакетное ревью перед бетой (§11.7) | корректность и AC; окружение — по нужде | полное |
|
||||
| Проверка кандидата слияния | ждёт push-прогон кандидата; dispatch — только если его нет | как у `ship` | dispatch с мутантами |
|
||||
| Проверка кандидата слияния | ждёт push-прогон кандидата; dispatch — только если его нет | как у `ship` | как у `ship` (#709) |
|
||||
|
||||
Метка `ci:mutants` возвращает мутанты по диффу на любом треке. Рамки `ship`
|
||||
Мутантов по диффу нет ни на одном треке (#709, §2.7). Рамки `ship`
|
||||
(§5) проверяет тот же шаг; выход за них — комментарий в issue и замена
|
||||
`track:ship` на `track:show` в этом же заходе. Слияние `ship` оставляет в issue
|
||||
комментарий с машинным маркером `hp:ship-merge`. Это не вердикт ревью и так себя
|
||||
@@ -1186,14 +1202,14 @@ Matysh/houseplan-card/.github/workflows/_<имя>.yml@dev` с `secrets: inherit`
|
||||
**Ревью `show` судит корректность и AC.** Medium — дефект поведения, который
|
||||
увидит пользователь, или невыполненный AC. Бухгалтерия — нет мутанта или записи
|
||||
в реестре, нечувствительный тест на побочный вызов, формулировка в документе —
|
||||
Low и цикла не открывает. Отсутствие мутантов по диффу на `show` не находка:
|
||||
полный реестр гоняется ночью. Ревью `show` не ставит Chromium, если тело issue
|
||||
Low и цикла не открывает. Отсутствие прогона мутантов не находка ни на каком
|
||||
треке: полный реестр гоняется ночью (#709). Ревью `show` не ставит Chromium, если тело issue
|
||||
не называет смоук или браузер. Ревью ТЗ не ставит ни `npm ci`, ни браузер: кода
|
||||
оно не исполняет.
|
||||
|
||||
**Ревью не начинается на красном коде** (#510). После фиксации материала конвейер
|
||||
запускает Validate с мутантами по диффу на этом SHA (`scripts/validate-gate.mjs`:
|
||||
`workflow_dispatch validate.yml -f mutants=true`). **Ждёт его не раннер, а событие**
|
||||
берёт завершённый push-прогон Validate на этом SHA или запускает лёгкий
|
||||
(`scripts/validate-gate.mjs`: `workflow_dispatch validate.yml`). **Ждёт его не раннер, а событие**
|
||||
(#636): подготовка убеждается, что dispatch встал на материал, кладёт запечатанный
|
||||
маркер ожидания `review-pending-…` и завершается; по завершении Validate
|
||||
`process-resume.yml` (`workflow_run`) переставляет метку `S7-code-review`, и новый
|
||||
@@ -1202,18 +1218,13 @@ Low и цикла не открывает. Отсутствие мутантов
|
||||
завершённым Validate он будит повторной меткой, без маркера — как прежде, только
|
||||
диагностика. Будить без маркера нельзя: это второй вызов модели. Красный или
|
||||
пропавший прогон возвращает задачу в `S6-in-progress` с комментарием и ссылкой —
|
||||
код никто не читал, цикл ревью не израсходован. Мутанты по диффу вообще бегут
|
||||
только по явному запросу: на кандидате ревью, кандидате слияния (#492) — оба
|
||||
диспатчат Validate с `mutants=true` — и на PR, где Validate единственный сигнал;
|
||||
обычный push обходится дешёвыми гейтами (~3 минуты). За 08–09.09 мутанты на
|
||||
каждом промежуточном пуше стоили 48 из 56 часов job-минут Validate и в основном
|
||||
отменялись следующим пушем. **Кандидат беты (`Release:`), `full=true` и ночь
|
||||
мутантов не запрашивают** (#601, решение владельца 20.09): мутационный гейт
|
||||
проверяет тесты, а не продукт (#513), к бете каждая задача прогнана им дважды —
|
||||
на ревью и на слитом после ребейза кандидате, — а ночью идёт полный реестр
|
||||
(`mutation-gate.yml`, 00:43 UTC). Релизный гейт (#541) требует полного Validate,
|
||||
но не mutant-jobs; для ревью и слияния шесть исполненных mutant-jobs остаются
|
||||
обязательными.
|
||||
код никто не читал, цикл ревью не израсходован. **Мутанты по диффу в
|
||||
разработке не бегут вовсе** (#709): ни на кандидате ревью, ни на кандидате
|
||||
слияния, ни на PR, ни по кнопке. До #709 их запрашивали кандидаты ревью и
|
||||
слияния на `ask` (#510, #601), и заход стоил около 28 минут; к бете каждая
|
||||
задача прогонялась ими дважды. Мутанты проверяют тесты, а не продукт (#513), —
|
||||
весь реестр проверяет ночь (`mutation-gate.yml`, 00:43 UTC). Ни ревью, ни
|
||||
слияние, ни релизный гейт (#541) mutant-jobs не требуют.
|
||||
|
||||
Ожидание gates, работа модели и публикация/интеграция — три независимых jobs
|
||||
(#551) с отдельными бюджетами 55, 45 и 55 минут. Поэтому долгий Validate не
|
||||
@@ -1298,12 +1309,11 @@ npm ci, Python и Chromium, оставаясь исполненной job: до
|
||||
- если двигался — ребейз (конфликт — `S6-in-progress`, как раньше), сравнение
|
||||
patch-id проверенного и получившегося диффа (различие — `S7-code-review`: вердикт
|
||||
к другому диффу не применим, §2.10; метка снимается и ставится заново, чтобы
|
||||
событие запустило новый заход, #706), публикация кандидата в ветку задачи, запуск
|
||||
Validate с мутантами на ней (#510) и ожидание зелёного dispatch-прогона **на этом
|
||||
SHA** — push-прогон мутантов не несёт — и только затем push в `dev` с lease на ту
|
||||
вершину, поверх которой кандидат собран. На `show`/`ship` мутантов нет, и
|
||||
лёгкий Validate кандидата уже запустил сам push в ветку: слияние ждёт этот
|
||||
push-прогон, а dispatch шлёт, только если его нет за три минуты (#696). Отклонённый lease — `dev` двинулся снова
|
||||
событие запустило новый заход, #706), публикация кандидата в ветку задачи и
|
||||
ожидание зелёного лёгкого Validate **на этом SHA** — его запускает сам push в
|
||||
ветку, dispatch — только если прогона нет за три минуты (#696, #709), — и только
|
||||
затем push в `dev` с lease на ту вершину, поверх которой кандидат собран.
|
||||
Отклонённый lease — `dev` двинулся снова
|
||||
— новая попытка; после третьей — `S6-in-progress` с комментарием;
|
||||
- красный Validate на кандидате или прогон, не появившийся за три минуты, —
|
||||
`S6-in-progress` с ссылкой; `S8-merged` ставится только после push;
|
||||
|
||||
+12
-6
@@ -111,8 +111,15 @@ browser/backend guard нельзя. Все недоказанные исходы
|
||||
рантайме, но не статически: `if (eps < 0)`, `String(mode) === 'mutant-never-…'`,
|
||||
`if (walls.length >= 0) return walls.slice();`. В `.mjs` этого ограничения нет.
|
||||
|
||||
В CI `changed_mutants` бежит не на каждом пуше, а по явному запросу (#510,
|
||||
сужено в #601): `workflow_dispatch validate.yml -f mutants=true` (его делают
|
||||
**С #709 мутанты в разработке не гоняются вовсе** — ни локально, ни в CI, ни
|
||||
ревьюером; весь реестр проверяет ночной прогон (`mutation-gate.yml`), выживший
|
||||
мутант заводит issue. Мутант своей защиты пишется в реестр, якоря сверяет
|
||||
`mutation-gate --check`; `--id` и `--changed` остаются инструментами разбора
|
||||
ночного сбоя, а не гейтом задачи. Job `changed_mutants` в Validate не
|
||||
запрашивает никто (`mutantsRequested` всегда `false`). Ниже — история до #709.
|
||||
|
||||
До #709 в CI `changed_mutants` бежал не на каждом пуше, а по явному запросу (#510,
|
||||
сужено в #601): `workflow_dispatch validate.yml -f mutants=true` (его делали
|
||||
ревью-конвейер на материале ревью и слияние на кандидате) и PR. `full=true`
|
||||
(ночь, кнопка), кандидат беты (трейлер `Release:`) и обычный push мутантов не
|
||||
запрашивают: за 08–09.09 мутанты на промежуточных пушах стоили 48 из 56 часов
|
||||
@@ -257,7 +264,7 @@ c._drag = { id, sx, sy }; // private-ok: #NNN состояние жеста, о
|
||||
`scripts/gate-small.mjs` и нигде больше не переписывается (#701). Хук `pre-push`
|
||||
гоняет его сам для веток задач — таблица «В хуке» ниже. Ручной
|
||||
`node scripts/pre-push-gate.mjs` из этого раздела — расширенный прогон:
|
||||
типы, юниты, смоки и мутанты по диффу.
|
||||
типы, юниты и смоки; мутантов в нём с #709 нет.
|
||||
|
||||
Красный CI — дорогой способ узнать о проблеме: пять минут ожидания, а при
|
||||
код-ревью ещё и лишний раунд. Прецедент назван в задаче: находка r2-H1 в #329
|
||||
@@ -298,8 +305,7 @@ node scripts/pre-push-gate.mjs --max-smokes=3 --max-mutants=1
|
||||
свежесть проверяет сам продукт — `assertFreshDemoBundle` внутри каждого смока
|
||||
сверяет вшитый отпечаток с исходниками дерева и скажет, если нужна пересборка.
|
||||
|
||||
Лимиты по умолчанию — шесть смоков и два мутанта. Мутант дорог: каждый
|
||||
пересобирает бандл, а правка `src/houseplan-card.ts` задевает их 62. Превышение
|
||||
Лимит по умолчанию — шесть смоков; мутантов ручной режим с #709 не гоняет. Превышение
|
||||
лимита не проглатывается — набор печатает точную команду для полного прогона.
|
||||
|
||||
Три вида ответа `smoke-select` различаются и здесь: дифф без исполняемого кода —
|
||||
@@ -332,7 +338,7 @@ node scripts/pre-push-gate.mjs --max-smokes=3 --max-mutants=1
|
||||
командой, а пушат с `HP_PREPUSH_GATE=0` — и пишут об этом в хендоффе.
|
||||
|
||||
Ручной режим выше (`node scripts/pre-push-gate.mjs` без `--hook`) остался как был:
|
||||
tsc, юниты, смоки и мутанты по диффу. Решение хука покрыто
|
||||
tsc, юниты и смоки (мутантов с #709 нет). Решение хука покрыто
|
||||
`test/pre-push-gate.test.mjs`, включая настоящий `.githooks/pre-push` на
|
||||
временном репозитории.
|
||||
|
||||
|
||||
+11
-4
@@ -59,8 +59,8 @@
|
||||
- `track:ship`: `S1-new` → `S5-ready`, в теле issue строка «что меняется и чем
|
||||
проверить». Рамки: дифф `src/**` до 30 строк, без новых файлов, i18n, полей
|
||||
конфига и Python ([§5](../../PROCESS.md#5-треки-ship-show-ask--метка-владельца)).
|
||||
- Тяжёлые проверки на любом треке — метками `ci:full`, `ci:golden`,
|
||||
`ci:mutants`; прежние `small` и `trivial` читаются как
|
||||
- Тяжёлые проверки на любом треке — метками `ci:full` и `ci:golden`;
|
||||
прежние `small` и `trivial` читаются как
|
||||
`track:show` ([§5.1](../../PROCESS.md#51-метки-тяжёлых-проверок-и-прежние-метки)).
|
||||
|
||||
## ТЗ (`S3-spec`)
|
||||
@@ -150,6 +150,13 @@
|
||||
`pytest tests_backend`, junction parity; `golden:verify` — только с меткой
|
||||
`ci:golden` ([§8](../../PROCESS.md#8-гейты); `docs/TESTING.md`, «Локальный
|
||||
набор перед пушем»).
|
||||
- Мутанты не гоняются ни локально, ни в CI: мутант своей защиты пишется в
|
||||
реестр, якорь сверяет `mutation-gate --check`, поимку — ночь (#709,
|
||||
[§2.7](../../PROCESS.md#27-код-ревью)).
|
||||
- На `ship` и `show` сверх `gate:small` и названного в ТЗ ничего не гоняется:
|
||||
одно доказательство на пункт ТЗ; `--smokes` на `ship` не нужен; попутный флак —
|
||||
отдельное issue одной записью, без расследования в задаче
|
||||
([§8](../../PROCESS.md#8-гейты)).
|
||||
- Бандл в коммит задачи не идёт: сборка переписывает отслеживаемый `dist/`,
|
||||
перед коммитом — `npm run bundle:clean`; хук `commit-msg` отклоняет пути
|
||||
бандла без трейлера `Release:` (#657, [§1](../../PROCESS.md#1-основное-правило)).
|
||||
@@ -179,8 +186,8 @@
|
||||
`node scripts/process-gate.mjs --issues`; после `S7-code-review` в ветку не
|
||||
пушить до вердикта; `S7` ставится один раз на заход
|
||||
([§10.4](../../PROCESS.md#104-событийный-конвейер-метка-как-триггер)).
|
||||
- Ревью не начинается на красном коде: конвейер сам гоняет Validate — с
|
||||
мутантами на `ask`, лёгкий на `show`/`ship` — и возвращает красный в
|
||||
- Ревью не начинается на красном коде: конвейер сам гоняет лёгкий Validate
|
||||
и возвращает красный в
|
||||
`S6-in-progress` без траты цикла
|
||||
([§10.4](../../PROCESS.md#104-событийный-конвейер-метка-как-триггер)).
|
||||
- Ветка приводится к `dev` до ревью, а не после: конфликт — возврат в
|
||||
|
||||
@@ -50,10 +50,10 @@
|
||||
([§2.7](../../PROCESS.md#27-код-ревью),
|
||||
[§2.6](../../PROCESS.md#26-в-разработке--реализация)).
|
||||
- Защитный AC доказывается таблицей «чем краснеет»: AC · чем доказан ·
|
||||
чем краснеет — мутация, снятая защита или отрицательная проба с
|
||||
результатом прогона. Пустой третий столбец — находка Medium, а не
|
||||
примечание. «Тест умеет падать» без названной мутации и её вывода
|
||||
доказательством не является ([§2.7](../../PROCESS.md#27-код-ревью)).
|
||||
чем краснеет — мутант в реестре или отрицательный случай в самом тесте.
|
||||
Пустой третий столбец — находка Medium, а не примечание. «Тест умеет
|
||||
падать» без названной мутации доказательством не является
|
||||
([§2.7](../../PROCESS.md#27-код-ревью)).
|
||||
- Вердикт привязан к SHA (#312): числа и факты сверяются с
|
||||
`git rev-parse HEAD` перед итогом; более новый коммит, которого нет в
|
||||
материале, — находка, а не повод его подтянуть
|
||||
@@ -138,8 +138,10 @@
|
||||
записи в реестре, нечувствительный тест на побочный вызов — Low и цикла не
|
||||
открывает ([§10.4](../../PROCESS.md#104-событийный-конвейер-метка-как-триггер),
|
||||
[§5](../../PROCESS.md#5-треки-ship-show-ask--метка-владельца)).
|
||||
- Мутанты по диффу на `show` не запрашиваются; их отсутствие не находка
|
||||
([§10.4](../../PROCESS.md#104-событийный-конвейер-метка-как-триггер)).
|
||||
- Мутанты в разработке не гоняются ни на каком треке — ревьюер их тоже не
|
||||
применяет; проверяет, что защита названа мутантом в реестре. Отсутствие прогона
|
||||
не находка: поимку проверяет ночь (#709,
|
||||
[§2.7](../../PROCESS.md#27-код-ревью)).
|
||||
- Ветка `show` с чистым слиянием к `dev` до ревью не приводится: материал —
|
||||
ветка как есть, кандидат проверит Validate при слиянии
|
||||
([§10.4](../../PROCESS.md#104-событийный-конвейер-метка-как-триггер)).
|
||||
|
||||
@@ -28,8 +28,8 @@ export const CI_PROOF_STATES = Object.freeze([
|
||||
//
|
||||
// #696: треки `show` и `ship` (PROCESS §5) мутантов по диффу до слияния не
|
||||
// запрашивают — их доказательство лёгкое, `reviewLight`/`mergeLight`. Мутанты
|
||||
// у них остаются в ночном полном реестре; `track:ask` и метка `ci:mutants`
|
||||
// идут по прежним `review`/`merge`.
|
||||
// у них остаются в ночном полном реестре. С #709 мутантов в разработке нет
|
||||
// ни на одном треке: `review`/`merge` с мутантами конвейер больше не выбирает.
|
||||
export const CI_PROOF_POLICIES = Object.freeze({
|
||||
review: Object.freeze({ name: 'review', full: false, mutants: true }),
|
||||
merge: Object.freeze({ name: 'merge', full: false, mutants: true }),
|
||||
|
||||
@@ -121,9 +121,10 @@ export function isTaskBranch(refName) {
|
||||
* CLI по-прежнему передаёт headMessage и fullInput вместе с остальным —
|
||||
* функция их не читает, и тест закрепляет, что они НЕ влияют на ответ.
|
||||
*/
|
||||
export function mutantsRequested({ eventName, mutantsInput } = {}) {
|
||||
if (eventName === 'pull_request') return true;
|
||||
if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';
|
||||
export function mutantsRequested() {
|
||||
// #709: мутанты проверяют тесты, а не продукт — в разработке Validate их не
|
||||
// гоняет ни на dispatch (`mutants=true` больше ничего не включает), ни на PR.
|
||||
// Весь реестр проверяет только ночной прогон (mutation-gate.yml).
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -10389,8 +10389,8 @@ const MUTANT_DEFINITIONS = [
|
||||
+ 'that softens it must fail, not pass as a shorter paraphrase (#634)',
|
||||
patches: [{
|
||||
file: 'docs/process/REVIEWER.md',
|
||||
find: ' результатом прогона. Пустой третий столбец — находка Medium, а не\n примечание.',
|
||||
replace: ' результатом прогона. Пустой третий столбец желательно заполнить.',
|
||||
find: ' Пустой третий столбец — находка Medium, а не примечание. «Тест умеет',
|
||||
replace: ' Пустой третий столбец желательно заполнить. «Тест умеет',
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -12102,39 +12102,14 @@ const MUTANT_DEFINITIONS = [
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'mutants-run-on-every-push',
|
||||
guard: 'node --test --test-name-pattern="#510" test/classify-changes.test.mjs',
|
||||
because: 'mutants by diff belong to the review candidate, the merge candidate and the PR — an '
|
||||
+ 'ordinary push must not spend 3×8 runner minutes on them (#510 AC1, narrowed in #601)',
|
||||
id: 'dev-mutants-requested-again',
|
||||
guard: 'node --test --test-name-pattern="#709" test/classify-changes.test.mjs',
|
||||
because: '#709 (owner, 2026-09-29): mutants check the tests, not the product; Validate runs no diff '
|
||||
+ 'mutants on any event — the whole registry is the nightly run (mutation-gate.yml)',
|
||||
patches: [{
|
||||
file: 'scripts/classify-changes.mjs',
|
||||
find: " if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';\n return false;\n}",
|
||||
replace: " if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';\n return true; // mutant: every push\n}",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'mutants-run-on-beta-candidate',
|
||||
guard: 'node --test --test-name-pattern="#601" test/classify-changes.test.mjs',
|
||||
because: 'the `Release:` trailer requests the heavy gates, not the diff mutants (#601 AC1): by the '
|
||||
+ 'beta candidate every issue has already been mutated on its review and merge candidates, and '
|
||||
+ 'the trailer also lands on class-D baseline commits inside task branches (f342ccce) — six '
|
||||
+ 'mutant jobs there prove nothing about tests that did not change',
|
||||
patches: [{
|
||||
file: 'scripts/classify-changes.mjs',
|
||||
find: "export function mutantsRequested({ eventName, mutantsInput } = {}) {\n if (eventName === 'pull_request') return true;\n if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';\n return false;\n}",
|
||||
replace: "export function mutantsRequested({ eventName, headMessage, mutantsInput } = {}) {\n if (eventName === 'pull_request') return true;\n if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';\n return hasReleaseTrailer(headMessage); // mutant: beta candidate\n}",
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'mutants-run-on-full-dispatch',
|
||||
guard: 'node --test --test-name-pattern="#601" test/classify-changes.test.mjs',
|
||||
because: '`full=true` is the heavy set — smokes, golden, performance — and the nightly dispatch '
|
||||
+ '(#601 AC1). Tying the diff mutants to it made a manual full run for a golden artifact pay six '
|
||||
+ 'jobs the review conveyor then cancelled by concurrency, and duplicated the nightly registry',
|
||||
patches: [{
|
||||
file: 'scripts/classify-changes.mjs',
|
||||
find: "export function mutantsRequested({ eventName, mutantsInput } = {}) {\n if (eventName === 'pull_request') return true;\n if (eventName === 'workflow_dispatch') return String(mutantsInput) === 'true';",
|
||||
replace: "export function mutantsRequested({ eventName, fullInput, mutantsInput } = {}) {\n if (eventName === 'pull_request') return true;\n if (eventName === 'workflow_dispatch') return String(fullInput) === 'true' || String(mutantsInput) === 'true'; // mutant: full requests",
|
||||
find: ' // Весь реестр проверяет только ночной прогон (mutation-gate.yml).\n return false;',
|
||||
replace: ' // Весь реестр проверяет только ночной прогон (mutation-gate.yml).\n return true; // mutant',
|
||||
}],
|
||||
},
|
||||
{
|
||||
@@ -13599,14 +13574,14 @@ const MUTANT_DEFINITIONS = [
|
||||
},
|
||||
// #696: цена захода по треку — мутанты, рамки ship, пакетное ревью перед бетой.
|
||||
{
|
||||
id: 'track-show-pays-for-mutants',
|
||||
guard: 'node --test --test-name-pattern="мутанты по диффу — только ask" test/process-track.test.mjs',
|
||||
because: '#696: show/ship request no diff mutants before merge; only ask and the ci:mutants '
|
||||
+ 'label do — otherwise every small task pays the 28-minute mutant run again',
|
||||
id: 'track-pays-for-mutants-again',
|
||||
guard: 'node --test --test-name-pattern="#709" test/process-track.test.mjs',
|
||||
because: '#709: no track and no label buys a mutant run during development — the 28-minute '
|
||||
+ 'run on ask and ci:mutants was the cost the owner removed',
|
||||
patches: [{
|
||||
file: 'scripts/process-track.mjs',
|
||||
find: " const mutants = track === 'ask' || labels.includes('ci:mutants');",
|
||||
replace: ' const mutants = true; // mutant: every track pays for mutants',
|
||||
find: ' const mutants = false;',
|
||||
replace: " const mutants = track === 'ask' || labels.includes('ci:mutants'); // mutant",
|
||||
}],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
* node scripts/pre-push-gate.mjs --hook # из .githooks/pre-push, строки git на stdin
|
||||
* node scripts/pre-push-gate.mjs
|
||||
* node scripts/pre-push-gate.mjs --base origin/dev --head HEAD
|
||||
* node scripts/pre-push-gate.mjs --no-smokes --no-mutants
|
||||
* node scripts/pre-push-gate.mjs --max-smokes=3 --max-mutants=1
|
||||
* node scripts/pre-push-gate.mjs --no-smokes
|
||||
* node scripts/pre-push-gate.mjs --max-smokes=3
|
||||
*
|
||||
* Зачем. Красный CI — дорогой способ узнать о проблеме: пять минут ожидания, а
|
||||
* при код-ревью ещё и лишний раунд. Прецедент назван в задаче: r2-H1 в #329
|
||||
@@ -203,7 +203,6 @@ function manualGate(argv) {
|
||||
const base = value('base', 'origin/dev');
|
||||
const head = value('head', 'HEAD');
|
||||
const maxSmokes = Number(value('max-smokes', 6));
|
||||
const maxMutants = Number(value('max-mutants', 2));
|
||||
|
||||
const run = (label, command, args, options = {}) => {
|
||||
const started = Date.now();
|
||||
@@ -307,24 +306,11 @@ function manualGate(argv) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---- мутанты по диффу -----------------------------------------------------
|
||||
if (flag('no-mutants')) {
|
||||
skipped.push('мутанты — запрошено --no-mutants');
|
||||
} else {
|
||||
const list = capture('node', ['scripts/mutation-gate.mjs', '--changed=' + `${base}..${head}`, '--check']);
|
||||
const touched = Number(/мутантов затронуто (\d+)/.exec(list.stdout || '')?.[1] ?? -1);
|
||||
if (touched === 0) {
|
||||
console.log('\n── Мутанты\n дифф не задевает ни одного patch.file');
|
||||
} else if (touched < 0) {
|
||||
skipped.push('мутанты — не удалось определить выборку по диффу');
|
||||
} else if (touched > maxMutants) {
|
||||
skipped.push(`мутанты (${touched}) — больше лимита ${maxMutants};`
|
||||
+ ` каждый пересобирает бандл. Прогон: node scripts/mutation-gate.mjs --changed=${base}..${head}`);
|
||||
} else {
|
||||
steps.push(run('Мутанты по диффу', 'node',
|
||||
['scripts/mutation-gate.mjs', `--changed=${base}..${head}`]));
|
||||
}
|
||||
}
|
||||
// ---- мутанты ---------------------------------------------------------------
|
||||
// #709: мутанты проверяют тесты, а не продукт, и в разработке не гоняются —
|
||||
// весь реестр проверяет ночной прогон (mutation-gate.yml). Флаги
|
||||
// --no-mutants и --max-mutants приняты для совместимости и ничего не меняют.
|
||||
skipped.push('мутанты — в разработке не гоняются (#709), весь реестр проверяет ночь');
|
||||
|
||||
// ---- вердикт --------------------------------------------------------------
|
||||
const failed = steps.filter((step) => !step.ok);
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
*
|
||||
* `resolve` печатает `track=ship|show|ask`, `mutants=true|false` и
|
||||
* `full=true|false` — то, что конвейер ревью читает, решая, сколько стоит
|
||||
* заход: мутанты по диффу нужны только `ask` и метке `ci:mutants`; полный
|
||||
* заход: мутантов в разработке нет ни на одном треке (#709) — `mutants`
|
||||
* всегда `false`, весь реестр проверяет только ночной прогон; полный
|
||||
* набор (смоки, golden, perf) на ветке задачи — только меткам `ci:full` и
|
||||
* `ci:golden` (#697). Инфраструктурная задача без трековой
|
||||
* метки — `show` (§5.1); признак инфраструктуры механический, как в §1: в
|
||||
@@ -47,13 +48,15 @@ export const hasTrackLabel = (labels = []) => ['track:ship', 'track:show', 'trac
|
||||
/**
|
||||
* Трек, по которому конвейер оценивает заход. Явная метка решает всё; без неё
|
||||
* инфраструктурная задача (ни одного файла класса A в диффе) — `show`, прочие —
|
||||
* `ask`. Мутанты по диффу — только на `ask` или по метке `ci:mutants`.
|
||||
* `ask`. Мутанты проверяют тесты, а не продукт: в разработке их не гоняют ни
|
||||
* локально, ни в CI (#709, решение владельца 2026-09-29) — только ночной полный
|
||||
* реестр (`mutation-gate.yml`, #513). Поле остаётся для совместимости выхода.
|
||||
* Полный набор — по меткам `ci:full` и `ci:golden` на любом треке (#697).
|
||||
*/
|
||||
export function resolveTrack({ labels = [], files = [] } = {}) {
|
||||
const infrastructure = files.length > 0 && files.every((file) => classify(file) !== 'A');
|
||||
const track = hasTrackLabel(labels) ? trackFromLabels(labels) : (infrastructure ? 'show' : 'ask');
|
||||
const mutants = track === 'ask' || labels.includes('ci:mutants');
|
||||
const mutants = false;
|
||||
const full = labels.includes('ci:full') || labels.includes('ci:golden');
|
||||
return { track, mutants, full, infrastructure };
|
||||
}
|
||||
|
||||
@@ -184,10 +184,9 @@ test('CLI --heavy читает событие и сообщение из окр
|
||||
// #601: кандидат беты и полный набор берут тяжёлые job, но не мутантов
|
||||
assert.equal(run({ EVENT_NAME: 'push', HEAD_MESSAGE: 'x\n\nRelease: v1.2.3' }), 'heavy=true\nmutants_requested=false');
|
||||
assert.equal(run({ EVENT_NAME: 'workflow_dispatch', FULL_INPUT: 'true', HEAD_MESSAGE: '' }), 'heavy=true\nmutants_requested=false');
|
||||
// #510: мутанты по кнопке без полного набора — вызов конвейера ревью и слияния
|
||||
assert.equal(run({ EVENT_NAME: 'workflow_dispatch', FULL_INPUT: 'false', MUTANTS_INPUT: 'true', HEAD_MESSAGE: '' }), 'heavy=false\nmutants_requested=true');
|
||||
// обе кнопки разом — полный набор И мутанты; так делает только человек, конвейер шлёт full=false
|
||||
assert.equal(run({ EVENT_NAME: 'workflow_dispatch', FULL_INPUT: 'true', MUTANTS_INPUT: 'true', HEAD_MESSAGE: '' }), 'heavy=true\nmutants_requested=true');
|
||||
// #709: `mutants=true` по кнопке мутантов больше не включает — только ночь
|
||||
assert.equal(run({ EVENT_NAME: 'workflow_dispatch', FULL_INPUT: 'false', MUTANTS_INPUT: 'true', HEAD_MESSAGE: '' }), 'heavy=false\nmutants_requested=false');
|
||||
assert.equal(run({ EVENT_NAME: 'workflow_dispatch', FULL_INPUT: 'true', MUTANTS_INPUT: 'true', HEAD_MESSAGE: '' }), 'heavy=true\nmutants_requested=false');
|
||||
});
|
||||
|
||||
test('#586: режим гейта скриншотов приходит одним значением и на кандидате строгий', () => {
|
||||
@@ -218,11 +217,11 @@ test('#586: preflight спрашивает режим одним значени
|
||||
assert.match(workflow, /check-docs\.mjs "\$external" --screenshots=\$mode/);
|
||||
});
|
||||
|
||||
test('#510 AC1 / #601 AC1: мутанты по диффу запрашиваются только кнопкой mutants=true и PR — не пушем, не кандидатом беты, не full', () => {
|
||||
test('#709: Validate не запрашивает мутантов по диффу ни на одном событии — весь реестр ночью', () => {
|
||||
const t = (env) => mutantsRequested(env);
|
||||
assert.equal(t({ eventName: 'push', headMessage: 'fix: x\n\nIssue: #1\nUser-Visible: no' }), false, 'обычный push');
|
||||
assert.equal(t({ eventName: 'pull_request' }), true, 'на PR Validate — единственный сигнал');
|
||||
assert.equal(t({ eventName: 'workflow_dispatch', fullInput: 'false', mutantsInput: 'true' }), true, 'конвейер ревью и слияния');
|
||||
assert.equal(t({ eventName: 'pull_request' }), false, 'PR — тоже нет');
|
||||
assert.equal(t({ eventName: 'workflow_dispatch', fullInput: 'false', mutantsInput: 'true' }), false, 'кнопка mutants=true больше ничего не включает');
|
||||
assert.equal(t({ eventName: 'workflow_dispatch', fullInput: 'false', mutantsInput: 'false' }), false, 'кнопка без запроса');
|
||||
// #601: мутанты проверяют тесты, а не продукт (#513); к бете задача прогнана
|
||||
// ими дважды, а ночь покрыта полным реестром — эти входы их не включают.
|
||||
@@ -231,7 +230,7 @@ test('#510 AC1 / #601 AC1: мутанты по диффу запрашивают
|
||||
assert.equal(t({ eventName: 'workflow_dispatch', fullInput: 'true' }), false, 'полный набор по кнопке — без мутантов');
|
||||
assert.equal(t({ eventName: 'workflow_dispatch', fullInput: 'true', mutantsInput: 'false' }), false);
|
||||
assert.equal(t({ eventName: 'schedule' }), false, 'расписание Validate — не место мутантов, реестр идёт в mutation-gate.yml');
|
||||
assert.equal(t({ eventName: 'workflow_dispatch', fullInput: 'true', mutantsInput: 'true' }), true, 'явный запрос действует и рядом с full');
|
||||
assert.equal(t({ eventName: 'workflow_dispatch', fullInput: 'true', mutantsInput: 'true' }), false, 'и рядом с full');
|
||||
// #601 AC2: тяжёлые гейты на тех же входах не изменились
|
||||
assert.equal(heavyGatesRequested({ eventName: 'push', headMessage: 'x\n\nRelease: v1.2.3' }), true);
|
||||
assert.equal(heavyGatesRequested({ eventName: 'workflow_dispatch', fullInput: 'true' }), true);
|
||||
|
||||
@@ -80,7 +80,7 @@ const KEY_RULES = {
|
||||
['27-код-ревью', 'проверено чтением, не исполнением'],
|
||||
['27-код-ревью', 'Защитный AC доказывается таблицей «чем краснеет»'],
|
||||
['27-код-ревью', 'Пустой третий столбец — находка Medium, а не примечание'],
|
||||
['27-код-ревью', '«Тест умеет падать» без названной мутации и её вывода доказательством не является'],
|
||||
['27-код-ревью', '«Тест умеет падать» без названной мутации доказательством не является'],
|
||||
['27-код-ревью', 'Вердикт привязан к SHA (#312)'],
|
||||
['27-код-ревью', 'новое имя в нём — находка ревью, а не запись в список'],
|
||||
['8-гейты', 'ревьюер обязан перечислить, какие гейты прогнал, какие нет и почему'],
|
||||
|
||||
@@ -21,7 +21,7 @@ test('пакет задачи и конвейер читают трек одно
|
||||
|
||||
test('явная трековая метка главнее признака инфраструктуры (#696)', () => {
|
||||
const infra = ['scripts/x.mjs', '.github/workflows/y.yml'];
|
||||
assert.deepEqual(resolveTrack({ labels: ['track:ask'], files: infra }), { track: 'ask', mutants: true, full: false, infrastructure: true });
|
||||
assert.deepEqual(resolveTrack({ labels: ['track:ask'], files: infra }), { track: 'ask', mutants: false, full: false, infrastructure: true });
|
||||
assert.deepEqual(resolveTrack({ labels: ['track:ship'], files: ['src/a.ts'] }), { track: 'ship', mutants: false, full: false, infrastructure: false });
|
||||
assert.equal(resolveTrack({ labels: ['small'], files: ['src/a.ts'] }).track, 'show');
|
||||
});
|
||||
@@ -34,11 +34,10 @@ test('без трековой метки инфраструктура — show,
|
||||
assert.equal(hasTrackLabel(['trivial']), true);
|
||||
});
|
||||
|
||||
test('мутанты по диффу — только ask и метка ci:mutants (#696)', () => {
|
||||
assert.equal(resolveTrack({ labels: ['track:show'], files: ['src/a.ts'] }).mutants, false);
|
||||
assert.equal(resolveTrack({ labels: ['track:show', 'ci:mutants'], files: ['src/a.ts'] }).mutants, true);
|
||||
assert.equal(resolveTrack({ labels: ['track:ship', 'ci:mutants'], files: ['src/a.ts'] }).mutants, true);
|
||||
assert.equal(resolveTrack({ labels: [], files: ['src/a.ts'] }).mutants, true);
|
||||
test('#709: мутантов в разработке нет ни на одном треке и ни по какой метке', () => {
|
||||
for (const labels of [['track:ship'], ['track:show'], ['track:ask'], ['track:show', 'ci:mutants'], ['ci:mutants'], []]) {
|
||||
assert.equal(resolveTrack({ labels, files: ['src/a.ts'] }).mutants, false, labels.join(',') || 'без меток');
|
||||
}
|
||||
});
|
||||
|
||||
test('рамки ship: строки src/** считаются вместе, граница включительна (#696)', () => {
|
||||
@@ -141,7 +140,7 @@ test('конвейер: трек снимается до ребейза, мут
|
||||
assert.match(rebaseStep, /if \[ "\$TRACK" = "show" \] \|\| \[ "\$TRACK" = "ship" \]; then\n\s+if git merge-tree --write-tree origin\/dev HEAD/,
|
||||
'show/ship не ребейзятся при чистом слиянии');
|
||||
const gateStep = workflow.slice(gate, at(' - name: Validate идёт — раунд продолжит событие\n'));
|
||||
assert.match(gateStep, /--mutants="\$\{MUTANTS:-true\}"/, 'по умолчанию — с мутантами');
|
||||
assert.match(gateStep, /--mutants="\$\{MUTANTS:-false\}"/, 'по умолчанию — без мутантов (#709)');
|
||||
assert.match(gateStep, /MUTANTS: \$\{\{ steps\.track\.outputs\.mutants \}\}/);
|
||||
});
|
||||
|
||||
@@ -162,7 +161,7 @@ test('конвейер: ship в рамках сливается без моде
|
||||
assert.doesNotMatch(decide.slice(decide.indexOf('SHIP" = "true'), decide.indexOf('elif [ "$REUSE"')), /Вердикт:/,
|
||||
'слияние без ревью не называет себя вердиктом ревью');
|
||||
const merge = integrate.slice(integrate.indexOf('- name: Слить ветку в dev'), integrate.indexOf('- name: Переставить метку'));
|
||||
assert.match(merge, /--mutants="\$\{MUTANTS:-true\}"/);
|
||||
assert.match(merge, /--mutants="\$\{MUTANTS:-false\}"/);
|
||||
const env = modelJob.slice(modelJob.indexOf('- name: Что ревьюеру нужно из окружения'), modelJob.indexOf('- name: Установить Claude Code'));
|
||||
assert.match(env, /if \[ "\$STAGE" = "spec" \]; then deps=false; browser=false; fi/, 'ревью ТЗ не ставит окружение');
|
||||
assert.match(env, /if: steps\.env_needs\.outputs\.deps == 'true'\n\s+run: npm ci/);
|
||||
|
||||
Reference in New Issue
Block a user