Merge updated dev for v1.63.0

This commit is contained in:
Sergey Matyunin
2026-08-13 22:10:28 +03:00
4 changed files with 82 additions and 11 deletions
+62 -7
View File
@@ -48,6 +48,7 @@ jobs:
BLOCKED: ${{ contains(github.event.issue.labels.*.name, 'blocked') }} BLOCKED: ${{ contains(github.event.issue.labels.*.name, 'blocked') }}
EXHAUSTED: ${{ contains(github.event.issue.labels.*.name, 'review-4') }} EXHAUSTED: ${{ contains(github.event.issue.labels.*.name, 'review-4') }}
SMALL: ${{ contains(github.event.issue.labels.*.name, 'small') }} SMALL: ${{ contains(github.event.issue.labels.*.name, 'small') }}
TRIVIAL: ${{ contains(github.event.issue.labels.*.name, 'trivial') }}
NUM: ${{ github.event.issue.number }} NUM: ${{ github.event.issue.number }}
run: | run: |
# Этап определяется первым: от него зависит, какие вердикты считать. # Этап определяется первым: от него зависит, какие вердикты считать.
@@ -58,8 +59,9 @@ jobs:
*) echo "метка $LABEL конвейер не запускает" ;; *) echo "метка $LABEL конвейер не запускает" ;;
esac esac
# Лимит циклов: 4 обычный, 2 на лёгком треке (PROCESS.md §4). # Лимит циклов: 4 обычный, 2 на лёгком и коротком треке (PROCESS.md §4).
limit=4; [ "$SMALL" = "true" ] && limit=2 limit=4
if [ "$SMALL" = "true" ] || [ "$TRIVIAL" = "true" ]; then limit=2; fi
# Счётчик считает вердикты ТОЛЬКО своего этапа. Раньше он брал все # Счётчик считает вердикты ТОЛЬКО своего этапа. Раньше он брал все
# подряд, и вердикт по ТЗ съедал цикл из бюджета код-ревью: на #89 # подряд, и вердикт по ТЗ съедал цикл из бюджета код-ревью: на #89
@@ -134,8 +136,15 @@ jobs:
fetch-depth: 0 fetch-depth: 0
ref: dev ref: dev
# Окружение готовит workflow, а не модель своими ходами. Раньше промпт
# велел ревьюеру самому выполнить `npm ci`: минуты уходили на установку без
# кэша, платились из бюджета 45 минут и из лимитов подписки, а ходы модели
# тратились на работу инфраструктуры. В validate.yml кэш стоит на всех
# тяжёлых job, здесь его не было.
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: { node-version: 22 } with:
node-version: 22
cache: npm
# Материал ревью живёт в ветке задачи: ТЗ в docs/specs/ и код коммитятся # Материал ревью живёт в ветке задачи: ТЗ в docs/specs/ и код коммитятся
# в issue/<NN>-slug. Если ветка запушена — переключаемся на неё, иначе # в issue/<NN>-slug. Если ветка запушена — переключаемся на неё, иначе
@@ -156,6 +165,24 @@ jobs:
echo "МАТЕРИАЛ НЕ ЗАПУШЕН" >> "$GITHUB_STEP_SUMMARY" echo "МАТЕРИАЛ НЕ ЗАПУШЕН" >> "$GITHUB_STEP_SUMMARY"
fi fi
# Зависимости ставятся ПОСЛЕ переключения на ветку задачи: lockfile мог
# измениться именно в ней, и установка по копии из dev дала бы не то дерево.
- name: Установить зависимости
run: npm ci
# Браузер нужен не всякому ревью (см. правило выбора гейтов в промпте),
# но когда нужен — качать его заново дороже, чем держать в кэше.
- name: Кэш браузеров Playwright
id: pw
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
- name: Установить Chromium
if: steps.pw.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
- name: Review - name: Review
id: review id: review
uses: anthropics/claude-code-action@v1 uses: anthropics/claude-code-action@v1
@@ -205,10 +232,38 @@ jobs:
По каждому AC: либо он доказан автотестом и ты убедился, что тест По каждому AC: либо он доказан автотестом и ты убедился, что тест
умеет падать, либо разобран по коду с явной записью «проверено умеет падать, либо разобран по коду с явной записью «проверено
чтением, не исполнением». «Verified» без названной команды и её чтением, не исполнением». «Verified» без названной команды и её
результата доказательством не является. Зависимостей в рабочей результата доказательством не является. Зависимости уже установлены
копии нет: перед гейтами выполни `npm ci`. Проверь трейлеры Issue и workflow, Chromium тоже — `npm ci` выполнять не нужно. Проверь
User-Visible, при User-Visible: yes — правки в оба changelog в том же трейлеры Issue и User-Visible, при User-Visible: yes — правки в оба
коммите. changelog в том же коммите.
**Объём гейтов соразмерен задаче.** Прогонять весь набор на каждой
правке — не тщательность, а потеря времени: полные наборы это
предрелизный гейт (PROCESS.md §8), а не гейт ревью.
Всегда, они дешёвые:
`npx tsc --noEmit`, `npm test`, `npm run build` со сверкой трёх
копий бандла.
По необходимости, и «необходимость» определяется diff'ом и AC:
- браузерные смоки `demo/smoke_*.mjs` — названные в AC плюс
относящиеся к тронутым поверхностям. Их 127; прогон всех уместен
только когда задача действительно задевает всё;
- `npm run golden:verify` — если diff может изменить видимый
результат: рендер, геометрия, стили, слои;
- `python -m pytest tests_backend -q` — если тронут
`custom_components/**/*.py`;
- performance-профили — если названы в AC либо тронуты
чувствительные к перфу пути.
Дисциплина «тест должен уметь падать» не отменяется, но применяется к
тем тестам, которые ты прогонял.
**В комментарии обязателен перечень: какие гейты прогнал, какие нет и
почему.** Это условие честности такого сужения: непрогнанный гейт
становится видимым решением, а не молчаливым пропуском. Раздел «чего
не проверял» в документе ревью — не формальность, а главный его
раздел на коротких задачах.
Ты НЕ правишь ни ТЗ, ни продуктовый код. Только оцениваешь. Ты НЕ правишь ни ТЗ, ни продуктовый код. Только оцениваешь.
+9
View File
@@ -41,6 +41,15 @@ of a status and `rejected` on a closed issue. Exactly one `S*` label per open
issue. [GitHub Projects (v2)](https://github.com/users/Matysh/projects/1) is a issue. [GitHub Projects (v2)](https://github.com/users/Matysh/projects/1) is a
human-facing view synchronised from the labels, not the source of truth. human-facing view synchronised from the labels, not the source of truth.
Two shortcuts exist for small work. `small` — the light track: the spec lives in
the issue body and its review is a comment. `trivial` — the short track: no spec
stage at all, `S2-analysis` straight to `S5-ready`, with the AC written into the
issue body first. `trivial` requires a bug confined to one surface with no new UX
contract, no migration, no i18n, no perf or touch impact, at most three checkable
AC, **and expected behaviour already on record** — nothing left to decide. Code
review is never skipped on either track; it is what stands in for testing.
`PROCESS.md` §5 and §5.1 hold the criteria.
An issue filed by an outsider is worked exactly like one of the owner's own, once An issue filed by an outsider is worked exactly like one of the owner's own, once
the owner has decided to take it. The check sits **at the entrance**, not on every the owner has decided to take it. The check sits **at the entrance**, not on every
step: while an issue carries no status label it is outside the process and the step: while an issue carries no status label it is outside the process and the
+9 -3
View File
@@ -67,6 +67,12 @@ const CLASS_C = [
const CHANGELOGS = ['docs/CHANGELOG.md', 'docs/CHANGELOG.ru.md']; const CHANGELOGS = ['docs/CHANGELOG.md', 'docs/CHANGELOG.ru.md'];
// Метки, при которых файла ТЗ в docs/specs/ быть не должно: на лёгком треке ТЗ
// живёт в теле issue (§5), на коротком — там же, и ревью ТЗ вообще не проводится
// (§5.1, issue #128). Офлайн эти случаи неотличимы от «ТЗ не написано», поэтому
// проверка 3 краснеет только когда метки прочитаны.
export const NO_SPEC_FILE = ['small', 'trivial'];
export const ALLOWED_STATUS = ['S5-ready', 'S6-in-progress', 'S7-code-review', 'S8-merged']; export const ALLOWED_STATUS = ['S5-ready', 'S6-in-progress', 'S7-code-review', 'S8-merged'];
export const STRICT_STATUS = ['S5-ready', 'S6-in-progress', 'S7-code-review']; export const STRICT_STATUS = ['S5-ready', 'S6-in-progress', 'S7-code-review'];
@@ -235,12 +241,12 @@ export function checkSpecs(commits, specFiles, labelsOf = null) {
if (labels === null) { if (labels === null) {
out.push({ out.push({
level: 'warn', rule: 3, sha: c.short, level: 'warn', rule: 3, sha: c.short,
msg: `класс A по ${t}, но ТЗ docs/specs/${nn}-*.md не найдено — допустимо только при метке small`, msg: `класс A по ${t}, но ТЗ docs/specs/${nn}-*.md не найдено — допустимо при метке small или trivial`,
}); });
} else if (!labels.includes('small')) { } else if (!labels.some((l) => NO_SPEC_FILE.includes(l))) {
out.push({ out.push({
level: 'fail', rule: 3, sha: c.short, level: 'fail', rule: 3, sha: c.short,
msg: `класс A по ${t}: ТЗ docs/specs/${nn}-*.md нет, и метки small на issue нет — код без ТЗ`, msg: `класс A по ${t}: ТЗ docs/specs/${nn}-*.md нет, и метки ${NO_SPEC_FILE.join(' / ')} на issue нет — код без ТЗ`,
}); });
} }
} }
+2 -1
View File
@@ -134,8 +134,9 @@ test('a class A commit without a spec warns offline and fails with labels', () =
assert.equal(offline[0].level, 'warn'); assert.equal(offline[0].level, 'warn');
assert.equal(offline[0].rule, 3); assert.equal(offline[0].rule, 3);
// С метками: small оправдывает отсутствие файла, его отсутствие — нет. // С метками: small и trivial оправдывают отсутствие файла, их отсутствие — нет.
assert.deepEqual(checkSpecs([c], [], () => ['small', 'S5-ready']), []); assert.deepEqual(checkSpecs([c], [], () => ['small', 'S5-ready']), []);
assert.deepEqual(checkSpecs([c], [], () => ['trivial', 'S5-ready']), []);
const strict = checkSpecs([c], [], () => ['S5-ready']); const strict = checkSpecs([c], [], () => ['S5-ready']);
assert.equal(strict.length, 1); assert.equal(strict.length, 1);
assert.equal(strict[0].level, 'fail'); assert.equal(strict[0].level, 'fail');