diff --git a/.github/workflows/_process.yml b/.github/workflows/_process.yml index 2f2f023d..291026f5 100644 --- a/.github/workflows/_process.yml +++ b/.github/workflows/_process.yml @@ -1170,9 +1170,9 @@ jobs: ничего не вернул на правки и цикла не образует (#227). Номер захода нужен для имени документа — два документа с одинаковым номером затёрли бы друг друга. - Трек: ${{ needs.prepare.outputs.track || 'ask' }} (PROCESS.md §5) · мутанты по диффу на материале: ${{ needs.prepare.outputs.mutants == 'false' && 'не запрашивались' || 'прогнаны Validate' }} + Трек: ${{ needs.prepare.outputs.track || 'ask' }} (PROCESS.md §5) - ${{ needs.prepare.outputs.track == 'show' && needs.guard.outputs.stage == 'code' && 'Трек show: объём и серьёзность находок — docs/process/REVIEWER.md, «Трек show» (#696). Отсутствие мутантов по диффу — не находка.' || '' }} + ${{ needs.prepare.outputs.track == 'show' && needs.guard.outputs.stage == 'code' && 'Трек show: объём и серьёзность находок — docs/process/REVIEWER.md, «Трек show» (#696).' || '' }} ${{ needs.prepare.outputs.rebase_note }} @@ -1212,17 +1212,8 @@ jobs: FILTERING.md, STAIRS.md, RADAR.md, PDF-EXPORT.md, STYLING-HOOKS.md. - **Если цикл не первый — объём разбора по дельте, а не заново** - (PROCESS.md §2.10, issue #214): найди вердикт и материал - предыдущего раунда (блок «Материал раунда» его документа), объяви - дельту `git diff <тот SHA>..HEAD` (для spec — дифф тела issue или - файла ТЗ), по каждой находке покажи, чем именно она закрыта — - строка кода или текста, а не заявление автора, — и заново проверяй - только AC, чьё доказательство дельта задевает. Разбор остаётся - ПОЛНЫМ, если дельта не локальна: ребейз на ушедший вперёд dev, - смена контракта, новая подсистема, объём сопоставим с задачей. - Сомневаешься — разбирай полностью и скажи почему. Сокращается - объём РАЗБОРА, а не строгость. + Цикл не первый — разбор по дельте (PROCESS.md §2.10): + docs/process/REVIEWER.md, «Повторный раунд». Для этапа spec: ТЗ живёт в теле issue (#517) — читай его, а не файл. Файлы docs/specs/-*.md — архив ТЗ до 2026-09-10: если такой файл @@ -1265,22 +1256,8 @@ jobs: `npm run build` со сверкой трёх копий бандла. Свежесть скриншотов документации — не гейт задачи (#697): её обновляет бот на dev. ${{ needs.guard.outputs.stage == 'spec' && 'Зависимости и Chromium на этапе spec не ставились (#696).' || (needs.prepare.outputs.track == 'show' && 'Зависимости установлены (`npm ci` не нужен); Chromium — только если тело issue называет смоук (#696).' || 'Зависимости уже установлены workflow, Chromium тоже — `npm ci` выполнять не нужно.') }} - По диффу и AC: браузерные смоки — названные в AC - плюс вывод `node scripts/smoke-select.mjs --base --head `, - приложенный к комментарию с решением по каждой строке: прогнал либо - не прогнал и почему. Три вида ответа инструмента разные: «прямое - совпадение», «зарегистрированная связь», «НЕОПРЕДЕЛЁННОСТЬ» — связь - не доказана, и это не разрешение ничего не прогонять; слабые связи — - повод посмотреть, а не обязанность прогонять; - `npm run golden:verify` при метке `ci:golden`; - `python -m pytest tests_backend -q` при правке - `custom_components/**/*.py`; инварианты модели - `npm run invariants -- --config <экспорт>` при правке геометрии или - ссылок на неё (#254) — задача меняет геометрию, а инварианты в - отчёте не названы, это непрогнанный гейт, а не мелочь; - performance-профили, если названы в AC. Дисциплина «тест должен - уметь падать» не отменяется, но применяется к тем тестам, которые - ты прогонял. + По диффу и AC — смоки, golden, pytest, инварианты, performance: + docs/process/REVIEWER.md, «Объём гейтов». **В комментарии обязателен перечень: какие гейты прогнал, какие нет и почему.** Раздел «чего не проверял» в документе ревью — не @@ -1288,13 +1265,9 @@ jobs: Ты НЕ правишь ни ТЗ, ни продуктовый код. Только оцениваешь. - Серьёзность: High блокирует; Medium В СКОУПЕ задачи чинится в ней - же — без High это жёлтый вердикт и возврат автору, отдельный issue - НЕ заводится (#202); Low либо правится, либо снимается с записью. - Жёлтый вердикт допустим и при полностью выполненных AC, если - изменение не решает заявленный сценарий или ухудшает смежный. - Продуктовое рассуждение расширяет вопросы, но не отменяет AC и не - даёт права менять скоуп. + Серьёзность и вердикт — docs/process/REVIEWER.md, «Находки и вердикт»: + Medium В СКОУПЕ — жёлтый вердикт и возврат автору, отдельный issue + НЕ заводится (#202). Только Medium-находку ВНЕ скоупа задачи (попутный дефект соседнего поведения, который в этой ветке чинить нельзя) заведи отдельным diff --git a/docs/process/REVIEWER.md b/docs/process/REVIEWER.md index 14328028..ec285894 100644 --- a/docs/process/REVIEWER.md +++ b/docs/process/REVIEWER.md @@ -84,6 +84,15 @@ при правке Python; инварианты модели при правке геометрии; performance — если назван в AC. Полные наборы — предрелизный гейт, а не гейт ревью ([§8](../../PROCESS.md#8-гейты)). +- Три ответа `smoke-select` разные: прямое совпадение и зарегистрированная + связь — прогон; «НЕОПРЕДЕЛЁННОСТЬ» — связь не доказана, и это не разрешение + ничего не прогонять; слабая связь — повод посмотреть, а не обязанность + прогонять ([`docs/TESTING.md`](../TESTING.md#локальный-набор-перед-пушем-343), + [§8](../../PROCESS.md#8-гейты)). +- Инварианты модели — `npm run invariants -- --config <экспорт>` при правке + геометрии или ссылок на неё (#254): задача меняет геометрию, а инварианты в + отчёте не названы — это непрогнанный гейт, а не мелочь + ([§8](../../PROCESS.md#8-гейты)). - Условие честности сужения: ревьюер обязан перечислить, какие гейты прогнал, какие нет и почему ([§8](../../PROCESS.md#8-гейты)). - Зелёный `pytest tests_backend` без Home Assistant `test_ha_*.py` не @@ -95,8 +104,9 @@ - Предмет повторного раунда — дельта, а не задача целиком: найти вердикт и материал предыдущего раунда (блок «Материал раунда»), объявить - `git diff <тот SHA>..HEAD`, по каждой находке показать, чем она закрыта, - заново проверить только AC, которые дельта задевает + `git diff <тот SHA>..HEAD` (для ТЗ — дифф тела issue или архивного файла ТЗ), + по каждой находке показать, чем она закрыта — строкой кода или текста, а не + заявлением автора, — заново проверить только AC, которые дельта задевает ([§2.10](../../PROCESS.md#210-повторный-раунд-ревью--объём-по-дельте)). - Если SHA не резолвится — это не находка, а обычное дело: материал ищется по дереву и блобу. Находка — SHA, мёртвый уже в момент публикации отчёта @@ -106,7 +116,8 @@ ([§2.10](../../PROCESS.md#210-повторный-раунд-ревью--объём-по-дельте)). - Разбор остаётся полным, если дельта не локальна: ребейз на ушедший вперёд `dev`, смена контракта, новая подсистема, объём сопоставим с задачей. - Сокращается объём разбора, а не строгость + Сомнение в локальности — полный разбор с названной причиной. Сокращается + объём разбора, а не строгость ([§2.10](../../PROCESS.md#210-повторный-раунд-ревью--объём-по-дельте)). - Перед разбором подсистемы — её строки в `docs/reviews/INDEX.md` ([§2.10](../../PROCESS.md#210-повторный-раунд-ревью--объём-по-дельте)). diff --git a/test/process-digests.test.mjs b/test/process-digests.test.mjs index d866f6d1..4ab30ee4 100644 --- a/test/process-digests.test.mjs +++ b/test/process-digests.test.mjs @@ -194,7 +194,42 @@ test('#634 промпт ревьюера: конспект вместо пере 'Затем верни JSON по схеме', ]) assert.ok(flat.includes(required), `промпт потерял: ${required}`); // Правила живут в каноне; промпт, снова набравший пересказ, — возврат к 60–90 k - // контекста до первого git diff (аудит 22.09). До #634 — 1 642 слова. + // контекста до первого git diff (аудит 22.09). До #634 — 1 642 слова, к #750 — + // ровно 1 400, после #750 — 1 130: порог прежний, разница — запас. const words = prompt.split(/\s+/).filter(Boolean).length; assert.ok(words <= 1400, `промпт ревьюера ${words} слов > 1400`); + // #750 AC1: мёртвая строка мутантов (с #709 `mutants` всегда false) и правило + // всех треков, которое REVIEWER.md уже несёт, в промпт не возвращаются. + assert.doesNotMatch(prompt, /needs\.prepare\.outputs\.mutants/, 'промпт читает мёртвый выход mutants'); + assert.doesNotMatch(flat, /Отсутствие мутантов/, 'правило «отсутствие мутантов — не находка» — в REVIEWER.md, не в промпте'); +}); + +// #750 AC2: промпт вместо пересказа ссылается на разделы конспекта — ссылка +// без раздела оставила бы ревьюера без правила, которое промпт больше не несёт. +test('#750 AC2: ссылки промпта на REVIEWER.md ведут в существующие разделы; перенесённое на месте', () => { + const workflow = read('.github/workflows/_process.yml'); + const prompt = workflow.slice(workflow.indexOf(' prompt: |\n'), workflow.indexOf(' claude_args: |')); + const flat = prompt.replace(/\s+/g, ' '); + const reviewer = read('docs/process/REVIEWER.md'); + const sections = new Set(headings(reviewer).filter((h) => h.level === 2).map((h) => h.text)); + const cited = [...flat.matchAll(/docs\/process\/REVIEWER\.md, «([^»]+)»/g)].map((m) => m[1]); + for (const name of ['Трек show', 'Повторный раунд', 'Объём гейтов', 'Находки и вердикт']) { + assert.ok(cited.includes(name), `промпт не ссылается на «${name}»`); + } + for (const name of cited) assert.ok(sections.has(name), `REVIEWER.md: нет раздела «## ${name}», на который ссылается промпт`); + // Запрет на отдельный issue для Medium в скоупе остаётся в промпте: модель + // заводит issue сама, и ошибка дорогая. + assert.ok(flat.includes('отдельный issue НЕ заводится (#202)'), 'промпт потерял запрет на issue для Medium в скоупе'); + + const bullets = (anchor) => topLevelBullets(sectionText(reviewer, anchor) ?? ''); + const gates = bullets('объём-гейтов'); + const uncertain = gates.find((bullet) => bullet.includes('НЕОПРЕДЕЛЁННОСТЬ')); + assert.ok(uncertain, '«Объём гейтов»: нет трёх ответов smoke-select с «НЕОПРЕДЕЛЁННОСТЬ»'); + assert.match(uncertain, /связь не доказана/); + assert.ok(markdownLinks(uncertain).some((link) => link.file.endsWith('TESTING.md')), 'ответы smoke-select ссылаются на docs/TESTING.md'); + assert.ok(gates.some((bullet) => /инвариант/i.test(bullet) && bullet.includes('непрогнанный гейт')), + '«Объём гейтов»: нет строки «геометрия без инвариантов в отчёте — непрогнанный гейт»'); + const delta = norm(bullets('повторный-раунд').join('\n')); + assert.ok(delta.includes(norm('дифф тела issue')), '«Повторный раунд»: дельта ТЗ — дифф тела issue'); + assert.ok(delta.includes(norm('Сомнение в локальности — полный разбор')), '«Повторный раунд»: сомнение — полный разбор'); });