mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 04:38:55 +00:00
fix(process): free the reviewer prompt budget, rules stay in REVIEWER.md (#750)
The code-review prompt sat at exactly 1 400 of its 1 400 words (#634), so any new line turned the budget test red, and #707/#726 already had to route their notes through job outputs. Part of the text was dead or a retelling of the reviewer digest, which #634 says the prompt must not repeat: - the mutants fragment of the track line: since #709 `mutants` is always false, so «прогнаны Validate» was unreachable; - «Отсутствие мутантов по диффу — не находка» in the show line: a rule of every track, already in REVIEWER.md «Трек show» and §10.4; - three retellings — the repeated round, the gate scope and the severity paragraph — now one-line references to the REVIEWER.md sections. The ban on a separate issue for an in-scope Medium stays in the prompt: the model files issues itself, and that mistake is expensive. What only the prompt said moves into REVIEWER.md with links to the canon: the spec delta is the diff of the issue body, a doubt about locality means a full review with a stated reason, the three smoke-select answers (docs/TESTING.md), and geometry without invariants in the report is an unrun gate. The prompt is now 1 130 words; the 1 400 threshold stays, the difference is headroom. A new test ties every «docs/process/REVIEWER.md, «X»» reference in the prompt to an existing `## X` section. Issue: #750 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:
@@ -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/<NN>-*.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 <base> --head <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-находку ВНЕ скоупа задачи (попутный дефект соседнего
|
||||
поведения, который в этой ветке чинить нельзя) заведи отдельным
|
||||
|
||||
@@ -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-повторный-раунд-ревью--объём-по-дельте)).
|
||||
|
||||
@@ -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('Сомнение в локальности — полный разбор')), '«Повторный раунд»: сомнение — полный разбор');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user