17 KiB
CODE-REVIEW-737-r1
Материал ревью: ветка issue/737-review-usage @ 10a1c0013395e2720242dec70baab27e149e5684
(рабочая копия уже на нём; git diff origin/dev...HEAD — 13 файлов, 809 вставок / 28 удалений).
Трек: ask (критерии §5 complexity, surfaces). Заход r1, блокирующих циклов
израсходовано 0 из 4 (предыдущий красный Validate на d1954183 ревью не запускал —
цикл не потрачен, #510).
Скоуп
Инфраструктурная задача без файлов класса A: конвейер снимает расход модели
(execution_file шага Review) и пишет его одной машинной строкой в документы
ревью (CODE-REVIEW/SPEC-REVIEW через блок якорей, SHIP-REVIEW — после
машинного блока), а «Метрики процесса» читают эту строку вместо прежнего
ненадёжного регэкспа по прозе документа. Три поверхности по ТЗ: К1–К2
(scripts/model-usage.mjs, новый), К3–К5 (_process.yml, _ship-review.yml,
review-doc-guard.mjs, ship-review.mjs), К6 (process-metrics.mjs), К7
(PROCESS.md §10.4). Второй коммит — точечный фикс пайплайна (красный Validate
на d1954183, причина названа и устранена без ревью, см. ниже).
Эта задача не обслуживает ни один Job из docs/SCOPE.md напрямую — она
процессная (измерение самого конвейера ревью), что соответствует её
инфраструктурному классу и треку ask, поднятому по complexity/surfaces,
а не по продуктовому критерию.
Как проверялось
Полный разбор (не дельта): это первый код-ревью задачи, предыдущая попытка была остановлена гейтом Validate до начала ревью модели и ревью не потребляла.
- Прочитан диф целиком (
git diff origin/dev...HEAD) — оба workflow-файла,PROCESS.md, новыйscripts/model-usage.mjs, правкиprocess-metrics.mjs,review-doc-guard.mjs,ship-review.mjs, все изменённые тесты. - Прочитано ТЗ (тело issue #737) целиком, сверены все 7 AC с кодом и тестами построчно, а не на веру по комментарию автора.
- Прочитаны оба коммита (
ada91e31,10a1c001) — трейлеры, сообщение, причина фикса во втором коммите. - Проверено, что диффу не нужны:
src/**не тронут (node scripts/smoke-select.mjs --base origin/dev --head HEAD→ «Исполняемого frontend-диффа нет… браузер-смоки не выбираются»), Python не тронут, геометрия/инварианты не тронуты,ci:goldenне назначен, performance в AC не назван. - Дешёвые гейты (
tsc,npm test,npm run build+ сверка бандла) на этом SHA подтверждены зелёным Validate (run 36859565821) — не перегонялись заново, по инструкции раунда.
Разбор по AC
| AC | Проверено | Вывод |
|---|---|---|
| AC1 (К1: снятие) | Код usageFromExecutionFile/usageFromMessages прочитан построчно; сверен с фикстурой test/model-usage.test.mjs (сумма по modelUsage двух моделей, откат на result.usage, no-result/no-execution-file/unreadable, секрет из tool_result не печатается, код выхода CLI 0 во всех ветках). Негативные случаи (дробь, отрицательное, пропущенное поле модели, 13 цифр) ведут к unreadable, а не к неверной сумме — проверено чтением и соответствием тесту. |
Доказан, тест умеет падать (конкретные мутации входа дают разные reason) |
| AC2 (К2: формат) | DATA_RE/NONE_RE построены с якорями ^…$, COUNT исключает ведущий ноль и ограничивает 12 цифрами. Таблица отказов в тесте (лишний/пропущенный ключ, другой порядок, знак, дробь, 13 цифр, 40 hex, --> внутри, перевод строки) — все дают null/invalid. Совместимость с регэкспом #728, вписанным в тест буквально, подтверждает, что hp:usage-none им не ловится (пробел после hp:usage — осознанный разделитель). |
Доказан |
| AC3 (К4: документ ТЗ/кода) | materialAnchorBlock дописывает строку последней, withMaterialAnchors не дублирует её при повторной публикации (ребейз + второй push — реальный bash/git в publish-push-refusal.test.mjs, строка одна). CLI --usage= отличает «флага нет» (вызов до #737) от «флаг пуст» (missing) через argv.some(...) — проверено чтением и отдельным тестом с anchor() без флагов. Прежние функции чтения блока (anchorVerdictFrom и др.) не задеты — тест сравнивает их результат до/после добавления строки. |
Доказан |
| AC4 (К5: документ ship) | anchorBlock кладёт строку сразу после закрывающего ``` блока, содержимое блока (which читают гейт беты и покрытие) не меняется — тест сравнивает fenced(withUsage) === fenced(plain) и shipCoverage на обоих вариантах документа. parseAnchorBlock берёт lastUsageIn после последнего маркера — строка в прозе выше блока не в счёт (отдельная проверка). |
Доказан |
| AC5 (К3: проводка) | Прочитаны оба workflow — шаг «Снять расход модели» идёт сразу за id: review, if: always(), continue-on-error: true, несекретный EXEC, выход line. execution_file нигде не выгружается и не печатается (grep по обоим файлам без строк-комментариев — только один EXEC: … на файл). REQUIRED_FILES (review-result-gate.mjs) не менялся — сверено импортом в тесте. Контрактный тест разбирает реальный YAML (срез job/шага текстом) плюс исполняет извлечённое тело на настоящем bash (bash -n и реальный прогон с GITHUB_OUTPUT/GITHUB_STEP_SUMMARY) — не заявление, а исполнение. |
Доказан, в т.ч. исполнением |
| AC6 (К6: читатель) | reviewDocUsage берёт строку только после ANCHOR_MARKER (ревью ТЗ/кода) или последнего SHIP_REVIEW_ANCHOR (ship) — прочитан код и тест, где документ r2 цитирует r1 в прозе и сумма r1 не задваивается. hp:usage-none увеличивает missing, не входит в totals, не печатается нулём (`doesNotMatch(/input_tokens |
\b0\b/)). Старый USAGE_LINE_RE` (ловил любую строку в прозе) полностью удалён — копии формата у читателя больше нет. |
| AC7 (К7: канон) | Абзац «Расход модели» в PROCESS.md §10.4 прочитан и сверен с кодом: источник, формат, 6 причин (no-execution-file, unreadable, no-result, no-usage + missing, invalid у публикации) в том же порядке, что USAGE_REASONS в коде; «выход job, а не artifact» и причина — совпадают с К3. docs/process/* не тронуты — соответствует «ни автор, ни ревьюер ничего нового не делают». |
Доказан чтением |
Точки, проверенные отдельно (не только по заявлению автора)
- Кавычки и границы данных.
--usage="$USAGE"в_process.ymlиprocess.env.USAGEв_ship-review.yml— оба способа передачи переживают строку с пробелами внутри (формат К2 — пять ключей через пробел) без разбиения на слова: первое — потому что значение в кавычках как один аргумент shell, второе — потому что это не shell, аprocess.env. - Разбор флага
--usage=.value(name)вreview-doc-guard.mjsвырезает фиксированный префикс длинойname.length+3, а не до первого=, поэтому значение, само содержащее=(оно содержит, формат —key=N), не обрезается на первом вхождении. Проверено чтением реализации и тестом с реальной строкой данных. - Два отдельных трейлера
Issue: #737/User-Visible: noна обоих коммитах — на месте; оба changelog не тронуты, что и требуется приno. Видимого пользователю числа в этом диффе нет вообще (только внутренний машинный формат и число в еженедельном отчёте владельца) — повторного источника для одного и того же числа не возникает:tokenUsage— один читатель одного формата, который пишет один модуль (model-usage.mjs). - Второй коммит (фикс Validate). Причина (
pre-push-gate.test.mjs:HOOK_FILESне содержал новыйscripts/model-usage.mjs, импортируемыйreview-doc-guard.mjsчерез цепочкуprocess-gate) подтверждена чтениемtest/pre-push-gate.test.mjs:144-147— правка (одна строка в списке) соразмерна причине, лишнего не задела. - Риск подмены рабочей копии моделью перед шагом снятия расхода (признан
автором в ТЗ, «Граница доверия»). Шаг
node scripts/model-usage.mjsвыполняется в том же job, что и предыдущий шагreview-result-gate.mjs— это не новый класс риска, а то же самое уже принятое архитектурное решение (#556): недоверенная стадия модели выполняет локальные скрипты в своей же рабочей копии до перехода наintegrate/publish, где нет Bash/Write у модели. Новый шаг ничего не меняет в границе доверия, только добавляет источник отчётной (не решающей) величины.
Находки
Высоких и средних находок нет.
Низкое, не моё: автор сам отметил в комментарии (не относится к этому диффу,
предсуществующий косметический дефект) — withMaterialAnchors копит ---
при повторном вызове, и обещал завести отдельный issue. Эта задача его не
трогает и не обязана чинить.
Что проверено и корректно
- Все 7 AC — см. таблицу выше, каждый либо доказан исполняемым тестом с содержательными негативными случаями, либо (контракт по YAML) частично исполнением извлечённого тела на bash.
- Граница доверия #556 не нарушена:
REQUIRED_FILESне изменился, расход — выход job, а не запечатанный artifact; публикация разбирает строку строго (invalid/missingвместо падения или мусора в документе). - Старый небезопасный регэкс читателя (
USAGE_LINE_RE, ловил строку в прозе и задваивал цитаты) полностью убран, новый формат имеет одного автора и одного читателя (model-usage.mjs). - Трейлеры корректны,
User-Visible: noобоснован (видимого пользователю поведения нет, только процессная телеметрия для еженедельного отчёта). - Второй коммит — точечный, по делу, причина явно названа и совпадает с кодом.
- PROCESS.md §10.4 (К7) синхронизирован с кодом по перечню причин и формату.
Чего не проверял
- Не перегонял
npx tsc --noEmit,npm test,npm run build+ сверку трёх копий бандла — Validate на этом же SHA (10a1c001, run 36859565821) уже зелёный, бюджет раунда не тратится на повтор. - Браузерные смоки не прогонял —
smoke-select.mjs --base origin/dev --head HEADподтвердил, чтоsrc/**не тронут и выбирать нечего; в AC браузер прямо назван ненужным («Браузер не нужен, мутанты не гоняются»). python -m pytest tests_backendне прогонял — диф не касаетсяcustom_components/**/*.py.npm run invariantsне прогонял — геометрия и ссылки на неё не затронуты.npm run golden:verifyне прогонял — меткиci:goldenнет, рендер карточки не изменён.- Performance-профили не прогонял — в AC не названы.
- Мутанты по диффу не запрашивались и не применялись (трек
ask, но мутанты в разработке не гоняются ни на одном треке — #709, ночной реестр отдельно). - Не выполнял вживую
gate:small,mutation-gate --check,entry-cost --check,reviews-index --check,process-digests— заявлены автором как зелёные и совпадают по косвенным признакам с прочитанным кодом (списки причин, число слов промпта не менялось — правки вне текста промпта модели), но не перепрогонялись отдельно, так как входят в состав уже подтверждённого Validate/gate:small.
Вердикт
Зелёный. Все AC доказаны исполняемыми тестами с содержательными негативными случаями (кроме части AC5, где контракт YAML частично доказывается чтением и частично — исполнением извлечённого тела на bash, что отдельно отмечено). Находок High/Medium нет.
Материал раунда
- Ветка:
issue/737-review-usage, коммит10a1c0013395— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
9682f3784bc6a18bc6327a0b0752e58042a2cc9bgit log --all --format='%H %T' | grep 9682f3784bc6 - Тело issue:
cd1cf82917e9f72b048ec5382cfacfe8d1d1609e4b972a18c1dad2912a3987c3 - Вердикт конвейера:
green· High 0 · маршрутfix