13 KiB
CODE-REVIEW · issue #637 · заход r1
Материал: 966928135b6a40fd6a73c7631bd514a79015d48a (единственный коммит поверх
dev=ee0f97c8). Класс B: scripts/process-metrics.mjs,
test/process-metrics.test.mjs, .github/workflows/process-metrics.yml,
scripts/mutation-registry.mjs. Файлов класса A и документов — нет.
Трейлеры: Issue: #637, User-Visible: no — верно, видимого поведения
продукта изменение не касается, правки changelog не требуются.
Скоуп
Разовый инструмент вместо ручного сбора метрик процесса (аудит 22.09):
чистые функции над снимком GitHub (issue + таймлайн меток, прогоны Actions,
имена документов ревью) → Markdown-отчёт; тонкий CLI на gh; workflow —
понедельник 05:00 UTC + ручной запуск, только чтение, отчёт в step summary
и артефакт. Это инфраструктура процесса, не продуктовая фича — к
docs/SCOPE.md (Core user jobs) не апеллирует, что для класса B корректно.
Как проверялось
- Полное чтение
scripts/process-metrics.mjs,test/process-metrics.test.mjs,.github/workflows/process-metrics.yml, диффаscripts/mutation-registry.mjs. node --test test/process-metrics.test.mjs— 7 pass (см. ниже, «Гейты»).- Оба новых мутанта применены вручную к рабочей копии и прогнаны под своим
guard-тестом: оба ловятся (тест падает), рабочая копия возвращена в
исходное состояние (
git status --porcelainпуст после отката). - Точечный ручной прогон
buildReportна синтетическом таймлайне с быстрыми (< 3 мин) переходами — искал регресс в вычислении медиан. - Сверка трейлеров коммита и путей файлов с хендофф-комментарием
(§2.10 material/blobs совпадают с
git show).
Находки
Medium (в скоупе задачи) — ложный «—» вместо настоящего «0 ч» в медианах
scripts/process-metrics.mjs:162-165 (buildReport):
medianLeadToS7Hours: hours(median(completed.map((i) => i.leadToS7Ms)) ?? NaN) || null,
medianReviewToMergeHours: hours(median(completed.map((i) => i.reviewToMergeMs)) ?? NaN) || null,
medianLeadToS8Hours: hours(median(completed.map((i) => i.leadToS8Ms)) ?? NaN) || null,
medianSpecLeadHours: hours(median(completed.map((i) => i.specLeadMs)) ?? NaN) || null,
?? корректно отличает «нет данных» (median вернул null) от валидного
результата, но финальное || null — нет: если медиана и правда равна 0 ч
(переход занял меньше ≈3 минут, что при автоматической простановке меток
конвейером — обычное дело для быстрых/тривиальных задач), hours(0) даёт
0, а 0 || null схлопывает его в null. В отчёте (и в Markdown, и в
JSON-артефакте) настоящий «0 ч» неотличим от «данных нет» — оба рендерятся
как «—».
Воспроизведение (не тестовый файл, ручной прогон в песочнице ревью):
const T = (h) => new Date(Date.UTC(2026,8,15,0,Math.round(h*60))).toISOString();
const issues = [{number:1, title:'t', closed_at:T(1), state_reason:'completed'}];
const timelines = new Map([[1,[
{event:'labeled', label:{name:'S1-new'}, created_at:T(0)},
{event:'labeled', label:{name:'S7-code-review'}, created_at:T(0.01)},
{event:'labeled', label:{name:'S8-merged'}, created_at:T(0.02)},
]]]);
buildReport({since:T(0), until:T(1), issues, timelines, reviewFiles:[], runs:[]}).issues.medianLeadToS7Hours
// → null (ожидалось 0)
Существующие тесты этот путь не задевают: во всех фикстурах (buildReport,
аудит-фикстура) переходы занимают часы, медиана никогда не равна 0.
Почему это релевантно именно здесь: единственная цель инструмента — «без регулярного замера решения об ускорении (#620, #636) нельзя проверить» (текст issue). Если ускорение конвейера доходит до долей часа, самый успешный случай — самый быстрый — окажется замаскирован под «нет данных», то есть баг бьёт ровно по метрике, ради которой скрипт написан. Это не крайний случай на бумаге: автоматическая простановка меток конвейером (#636) делает переходы короче секунд для части событий уже сегодня.
В скоупе задачи (правка внутри тех же чистых функций, что уже покрыты тестами), High-находок нет → жёлтый вердикт, возврат автору.
Что проверено и корректно
issueMetrics: вход по первой статусной метке (не любой), различениеentered/s4/s5/s7/s8, счётчик повторных постановок S7/S4 отдельно от времени первой — тест воспроизводит ровно кейс сunlabeled+повтором, прочитан и логика соответствует.reviewRounds: регэксп^(CODE|SPEC)-REVIEW-(\d+)-r(\d+)\.md$строгий, не матчитREADME.md/CODE-REVIEW-issue-5.md— верно по тесту.runMetrics: прогоны конвейера (display_titleвидаprocess #NN · …) сведены в одну строкуprocess, остальные — по имени workflow; отменённый прогон без длительности не учитывается в wall-time — прочитано и соответствует правке, которая решает проблему из хендоффа («имена прогонов конвейера — на каждое событие метки, не сотни строк»). Здесь та же группировка использована для оценки инструмента: ложноположительного объединения разных workflow с совпадающим префиксом не нашёл,process #\d+ ·— достаточно специфичный якорь для этого репозитория.pipelineMetrics: skipped-прогоны не считаются — подтверждено мутантомmetrics-count-skipped-pipeline-runs(тест падает при снятии условия, проверено запуском).jobMinutes: доля «Мутанты» по имени job, сломанные (started_at: 'x') записи не портят сумму — по тесту и чтению кода.buildReport/renderMarkdown: раунды считаются по документам, а не по событиям S7-метки — подтверждено мутантомmetrics-rounds-by-s7-events(тест падает при подмене источника, проверено запуском). Причина в хендоффе (конвейер #636 переставляет S7 сам) соответствует коду.fetchSnapshot: только чтение (gh apiна issues/timeline/actions/runsgit ls-tree), пагинация с разумными пределами (5×100 issue, 3×100 событий таймлайна на issue, 15×100 прогонов); фильтр поclosed_atвнутри окна — корректен, посколькуsinceв GitHub Issues API фильтрует поupdated_at, а закрытие всегда обновляетupdated_at, так что ни один закрытый в окне issue не потеряется на этом шаге.
.github/workflows/process-metrics.yml:permissions— толькоcontents/actions/issues: read, нетissues: write; код иref: dev— согласовано с хендоффом (расписание сработает после зеркалирования вmain, что явно оставлено как задача владельцу после мержа); экшены запиненны по SHA. Тестtest/process-metrics.test.mjs:108проверяет это структурно (регэкспы по тексту workflow) — само по себе слабое доказательство, но здесь избыточно: я прочитал файл и подтвердил то же глазами.- Трейлеры коммита (
Issue: #637,User-Visible: no) и заявленные в §2.10-блоке SHA/blob-хэши соответствуютgit show.
Гейты
node --test test/process-metrics.test.mjs— прогнал сам: 7 pass, 0 fail.npx tsc --noEmit,npm test(полный),npm run build— не гонял: Validate на этом самом SHA (96692813) уже зелёный (https://github.com/Matysh/houseplan-card/actions/runs/35828853731), дифф с тех пор не менялся.node scripts/check-docs.mjs— не требуется: диф не трогаетsrc/**(толькоscripts/,test/,.github/workflows/).npm run invariants— не требуется: диф не касается геометрии,layout, толщины стен,marker.space/open_spans.demo/smoke_*.mjs,npm run golden:verify— не требуется: диф не меняет рендер, геометрию, стили или слои; смок-селектор не запускал — никакой продуктовый рендер-путь не тронут (правка целиком в CI-инструментарии, работающем наgh api-снимках, а не на карточке).python -m pytest tests_backend -q— не требуется:custom_components/**не тронут.- Мутанты
metrics-count-skipped-pipeline-runsиmetrics-rounds-by-s7-events— проверил лично (патч → тест красный → откат), оба ловятся. test/single-source-numbers.test.mjs— не относится: диф не добавляет пользовательских чисел (превью/подпись/подсветка), это внутренние метрики процесса, а не карточка.
Чего не проверял
- Живой прогон
fetchSnapshot/CLI против реальногоgh apiза 15–22.09 — не выполнял: это ручное тестирование сети, вне цикла ревью, и сам автор прямо пишет, что сверка с таблицей аудита §7.1 состоится первым живым запуском после мержа и зеркалирования вmain. Логика самой функции (пагинация, фильтрация, только-чтение) прочитана и корректна. - Точность
wallMs/job-минутна реальных исторических прогонах — не считал вручную, доверяю арифметике, подтверждённой тестами на фикстурах.
Материал раунда
- Ветка:
issue/637-process-metrics, коммит966928135b6a— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
0687eddba719e669212123bf80bf8c6d683bbf50git log --all --format='%H %T' | grep 0687eddba719 - Тело issue:
187c1bf4bbdea65f4114112ea0ee62c313cb015d343fbb44d68507420f204e41 - Вердикт конвейера:
yellow· High 0