diff --git a/.github/workflows/process.yml b/.github/workflows/process.yml index b346a7c2..274aaa59 100644 --- a/.github/workflows/process.yml +++ b/.github/workflows/process.yml @@ -48,6 +48,7 @@ jobs: BLOCKED: ${{ contains(github.event.issue.labels.*.name, 'blocked') }} EXHAUSTED: ${{ contains(github.event.issue.labels.*.name, 'review-4') }} SMALL: ${{ contains(github.event.issue.labels.*.name, 'small') }} + TRIVIAL: ${{ contains(github.event.issue.labels.*.name, 'trivial') }} NUM: ${{ github.event.issue.number }} run: | # Этап определяется первым: от него зависит, какие вердикты считать. @@ -58,8 +59,9 @@ jobs: *) echo "метка $LABEL конвейер не запускает" ;; esac - # Лимит циклов: 4 обычный, 2 на лёгком треке (PROCESS.md §4). - limit=4; [ "$SMALL" = "true" ] && limit=2 + # Лимит циклов: 4 обычный, 2 на лёгком и коротком треке (PROCESS.md §4). + limit=4 + if [ "$SMALL" = "true" ] || [ "$TRIVIAL" = "true" ]; then limit=2; fi # Счётчик считает вердикты ТОЛЬКО своего этапа. Раньше он брал все # подряд, и вердикт по ТЗ съедал цикл из бюджета код-ревью: на #89 @@ -134,8 +136,15 @@ jobs: fetch-depth: 0 ref: dev + # Окружение готовит workflow, а не модель своими ходами. Раньше промпт + # велел ревьюеру самому выполнить `npm ci`: минуты уходили на установку без + # кэша, платились из бюджета 45 минут и из лимитов подписки, а ходы модели + # тратились на работу инфраструктуры. В validate.yml кэш стоит на всех + # тяжёлых job, здесь его не было. - uses: actions/setup-node@v4 - with: { node-version: 22 } + with: + node-version: 22 + cache: npm # Материал ревью живёт в ветке задачи: ТЗ в docs/specs/ и код коммитятся # в issue/-slug. Если ветка запушена — переключаемся на неё, иначе @@ -156,6 +165,24 @@ jobs: echo "МАТЕРИАЛ НЕ ЗАПУШЕН" >> "$GITHUB_STEP_SUMMARY" 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 id: review uses: anthropics/claude-code-action@v1 @@ -205,10 +232,38 @@ jobs: По каждому AC: либо он доказан автотестом и ты убедился, что тест умеет падать, либо разобран по коду с явной записью «проверено чтением, не исполнением». «Verified» без названной команды и её - результата доказательством не является. Зависимостей в рабочей - копии нет: перед гейтами выполни `npm ci`. Проверь трейлеры Issue и - User-Visible, при User-Visible: yes — правки в оба changelog в том же - коммите. + результата доказательством не является. Зависимости уже установлены + workflow, Chromium тоже — `npm ci` выполнять не нужно. Проверь + трейлеры 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 либо тронуты + чувствительные к перфу пути. + + Дисциплина «тест должен уметь падать» не отменяется, но применяется к + тем тестам, которые ты прогонял. + + **В комментарии обязателен перечень: какие гейты прогнал, какие нет и + почему.** Это условие честности такого сужения: непрогнанный гейт + становится видимым решением, а не молчаливым пропуском. Раздел «чего + не проверял» в документе ревью — не формальность, а главный его + раздел на коротких задачах. Ты НЕ правишь ни ТЗ, ни продуктовый код. Только оцениваешь. diff --git a/AGENTS.md b/AGENTS.md index 9c0c5754..fd6e29ca 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 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 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 diff --git a/scripts/process-gate.mjs b/scripts/process-gate.mjs index 0d3bd9b2..24af7265 100644 --- a/scripts/process-gate.mjs +++ b/scripts/process-gate.mjs @@ -67,6 +67,12 @@ const CLASS_C = [ 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 STRICT_STATUS = ['S5-ready', 'S6-in-progress', 'S7-code-review']; @@ -235,12 +241,12 @@ export function checkSpecs(commits, specFiles, labelsOf = null) { if (labels === null) { out.push({ 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({ 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 нет — код без ТЗ`, }); } } diff --git a/test/process-gate.test.mjs b/test/process-gate.test.mjs index 01556f25..761b2f13 100644 --- a/test/process-gate.test.mjs +++ b/test/process-gate.test.mjs @@ -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].rule, 3); - // С метками: small оправдывает отсутствие файла, его отсутствие — нет. + // С метками: small и trivial оправдывают отсутствие файла, их отсутствие — нет. assert.deepEqual(checkSpecs([c], [], () => ['small', 'S5-ready']), []); + assert.deepEqual(checkSpecs([c], [], () => ['trivial', 'S5-ready']), []); const strict = checkSpecs([c], [], () => ['S5-ready']); assert.equal(strict.length, 1); assert.equal(strict[0].level, 'fail');