Commit Graph
115 Commits
Author SHA1 Message Date
Sergey Matyunin ee7d486924 feat: implement isometric stage 3
Issue: #160
User-Visible: no
2026-09-06 07:06:54 +03:00
Sergey Matyunin 04a1de091c fix: speed up intermediate wall-chain commits
Issue: #461
User-Visible: yes
2026-09-05 14:04:26 +03:00
Sergey Matyunin c0d61ca3d2 fix: keep large-plan interactions off the full render path
Issue: #451
User-Visible: yes
2026-09-04 22:03:45 +03:00
Codex d5e114524c fix: комментарий засчитывается вердиктом только по документу своей задачи
User-Visible: no
Issue: #454
2026-09-04 20:18:59 +03:00
Codexandclaude[bot] 4b443d8cf5 fix: не считать описание чужого раунда объявлением вердикта
User-Visible: no
Issue: #454
2026-09-04 16:58:31 +00:00
Codexandclaude[bot] 8d8263202f ci: считать раунды ревью по опубликованным документам
User-Visible: no
Issue: #454
2026-09-04 16:58:31 +00:00
Codex d4dd027b0a build: prepare v1.71.0-beta.2 candidate
Issue: #426
Issue: #427
Issue: #428
Issue: #431
Issue: #432
Issue: #434
User-Visible: no
2026-09-03 15:23:40 +03:00
Codex d59ceee905 feat(gates): measure capture drift between runs and anchor reachability (#422)
User-Visible: no
Issue: #422
2026-09-02 22:10:16 +03:00
Matysh 6fb7bbb6ab ci: anchor the review material to content, not to history
#413 закрыл класс «SHA мёртв уже в момент публикации». Остаётся более частый:
SHA был жив, а умер потом — по корпусу таких объявлений 98 из 804, потому что
ветку задачи после ревью перебазируют, сквошат или удаляют.

SHA коммита — свойство истории, а история переписывается. Содержимое не
переписывается: git адресует деревья и блобы их хешем. На #403 спец-коммит
переехал из 83005c3c в 94502d3d, а блоб ТЗ у обоих один — 56a92e12; по нему
материал находится одной командой независимо от ребейза.

Конвейер снимает якоря там, где читает материал — в шаге перехода на ветку
задачи, пока рабочая копия равна тому, что прочтёт ревьюер. В шаге публикации
спрашивать поздно: дерево уже сброшено на целевую ветку. При публикации якоря
дописываются машинным блоком: дерево материала и блоб каждого ТЗ, каждый со
своей исполнимой командой поиска.

Блок машинный и помечен как машинный. Ревьюер его не заполняет: дисциплина
ручного переписывания SHA здесь уже подвела, и заменять её другой ручной
дисциплиной смысла нет.

Гейт #413 смягчён ровно там, где обязан: осиротевший SHA при живых якорях —
предупреждение, а не отказ. Ронять раунд, который воспроизводим, было бы той
же ошибкой в другую сторону. Отказ остаётся, когда не работает ни один
объявленный способ найти материал.

Проверено на настоящем осиротевшем случае: блок, собранный для 94502d3d,
находит и дерево, и блоб ТЗ; тот же документ с якорями даёт предупреждение
вместо отказа, без якорей — отказ.

Issue: #416
User-Visible: no
2026-09-02 08:01:15 +03:00
Matysh 206732e9f5 ci: refuse a review round that cites an unreachable SHA
SPEC-REVIEW-403-r2 объявил материал раунда на `HEAD = 83005c3c`, и тот же SHA
независимо назвал автор ТЗ в комментарии issue. Разбор подтвердил находку и
уточнил её: коммит существовал, но к моменту публикации был осиротевшим.
Ветку перебазировали за пятнадцать минут ДО публикации документа — спец-коммит
переехал в 94502d3d с тем же сообщением и тем же содержимым (блоб ТЗ у обоих
56a92e12). Через раунд команда `git diff 83005c3c..HEAD` из §2.10 буквально не
работала, и r3 восстанавливал коммит по содержимому диффа руками.

Гейт судит только объявление материала в шапке документа, а не каждое
шестнадцатеричное слово: в прозе SHA упоминаются исторически, и обещания
воспроизводимости на них нет. Границы кандидата подобраны по корпусу — 7–40
знаков, хотя бы одна буква, не после `#`, не внутри длинного хеша; это
отсекает sha256, цвета и номера прогонов.

Достижимость считается от refs/remotes/origin, а не от локальных ссылок.
Разница не теоретическая: осиротевший 83005c3c до сих пор достижим в клоне
автора из необновлённой локальной ветки — локальная проверка сказала бы «всё в
порядке» ровно на той машине, где ошибку и совершили.

Шаг стоит ПОСЛЕ публикации и ДО перестановки метки. Артефакт ревью терялся
здесь трижды (#171, #220), и «вердикт без документа» дороже мёртвой ссылки:
документ сначала спасается, потом судится. Инвариант «метка не сменилась =
прогон упал» при этом сохраняется.

Проверено на настоящих документах: SPEC-REVIEW-403-r2 отказ, CODE-REVIEW-390-r1
проходит, документ без объявления материала не судится.

Issue: #413
User-Visible: no
2026-09-02 07:45:39 +03:00
Sergey Matyuninandclaude[bot] f5ba67d069 feat: add private help and feedback reports (#43)
Issue: #43
User-Visible: yes
2026-09-02 00:05:36 +00:00
Matysh 2d143b570f ci: stop truncating the numbers a failing smoke printed
smoke_grid_scale_invariance сообщает, НАСКОЛЬКО разошлись кадры: строка
pixel-diffs с changed, maxDelta и meanDelta по каждой паре. Введена в #302
ровно затем, чтобы падение не было голым boolean.

В логе прогона её не видно: шаг шарда печатает tail -20, а диагностика идёт до
вердикта и срезается. На #411 это и вышло — в логе осталось только «expected
true, got false», а числа, по которым видно, превышение порога это или
расхождение слоя, пришлось бы искать в артефакте.

Строки достаются адресно, перед хвостом лога. Они и есть разница между
«чинить» и «гадать».

Issue: #411
User-Visible: no
2026-09-01 23:52:36 +03:00
Matysh ca0b318e5e ci: make the stability measurement a gate, not a printout
Замер сделал свою работу — теперь он остаётся как проверка. Если кадр снова
начнёт зависеть от времени, шаг упадёт, а не напечатает число в лог. Стоит
перед съёмкой набора: публиковать артефакт, снятый недетерминированной
съёмкой, смысла нет.

Issue: #410
User-Visible: no
2026-09-01 23:20:09 +03:00
Matysh d945b03208 test: measure whether the frame drifts inside one page state (#410)
Флаги растеризации убрали один кадр из трёх, но device-editor и device-info
плавают по-прежнему. Дальше гадать нельзя: нужен ответ, плавает ли кадр внутри
одного состояния страницы или разница копится между подготовками сценария.

Режим --stability=N делает N снимков подряд без единой правки состояния и
сравнивает их попиксельно в самой странице — тем же приёмом, что у golden.
Печатает число различающихся пикселей, максимум по RGB, задета ли alpha и
bbox.

Ветка временная.

Issue: #410
User-Visible: no
2026-09-01 23:09:05 +03:00
Matysh 184e009806 ci: print frame hashes so two capture runs can be compared
Чтобы измерить недетерминированность съёмки, нужен текст, который видно с
экрана: артефакт для этого не годится — его надо скачать и распаковать. Шаг
печатает sha256 каждого кадра, и два прогона одного SHA сравниваются
построчно.

Сначала измерение, потом починка (#410).

Issue: #410
User-Visible: no
2026-09-01 22:52:00 +03:00
Matysh 2903374b72 fix: the exception guard reads its counter after delivery, not before
Гард «uncaught exception внутри карточки» жил в demo/serve.mjs с 2026-07-27 и
не срабатывал ни разу в самом частом случае. Счётчик читался синхронно, а
Playwright доставляет pageerror асинхронно по CDP: если исключение возникло
после последнего обращения смока к странице, счётчик к моменту проверки
нулевой, а browser.close() уносит недоставленное событие. В логе это видно
дословно — EXC печатается после результата и до OK.

finish() теперь делает round-trip по открытым страницам перед чтением
счётчика. Страницы регистрируются там, где создаются: ссылок на них у
finish(browser, out) нет, а менять сигнатуру нельзя — так её зовут 205
смоков.

Medium-1 жёлтого ревью ТЗ закрыт расширением, а не оговоркой. Страницы,
созданные смоком после launch(), регистрация в launchInternal не покрывает:
smoke_zoom_flash печатал своё EXC2 мимо счётчика, три страницы
smoke_svg_sandbox не имели слушателя вовсе. Документировать слепую зону в
задаче, которая существует ради устранения слепой зоны, значит закрыть issue,
оставив дефект. Наружу отдана одна функция watchPage(page): подписка и
регистрация неразделимы, иначе появится страница, чьи исключения считаются, а
доставки не ждёт никто.

Разрыв оказался шире, чем в ревью: проверка по всему набору нашла ещё два
файла со своей подпиской — smoke_cold_view_toggle и smoke_cold_view_vacuum.
Они не слепая зона, их страница приходит из launchColdView и уже
зарегистрирована, а свой счётчик они превращают в отдельное утверждение.
Поэтому инвариант сформулирован как «ни одна страница не создаётся мимо
гарда» и закреплён по всему набору, а не по двум названным файлам.

reportPageErrors() из #407 стал асинхронным: второй читатель счётчика обязан
ждать доставку так же, как finish(). Пять смоков получили await.

Фикстура smoke_danger_confirmation приведена к объявленному типу: без binding
и bindingMode _bindingHasHaPage падал на undefined.split(':') — два
исключения, которых гард не видел. Дефекта поведения нет, все 15 мест в src/,
создающих диалог, binding пишут; врала фикстура.

Два отступления от ТЗ, каждое по измеренной причине. Пробы лежат в
demo/guard/, а не demo/fixtures/: последний входит в корпус sourceFingerprint,
и каждый файл там объявил бы устаревшими бандл, скриншот-индекс и
golden-индекс — пробы же не касаются ни одного пикселя. Поведение
доказывается в job со браузером, а не в npm test: job «Фронтенд» браузеры не
ставит, и тест молча скипался бы — тот самый тихий успех, против которого вся
задача.

Issue: #404
User-Visible: no
2026-09-01 19:08:07 +03:00
Matysh 5c8cb58e1f ci: prove the screenshot environment instead of trusting the place
Правило приёмки скриншотов было про место: снимать только в CI. Обоснование
измерено — съёмка в другом окружении переписывает файлы без содержательных
изменений, в #231 два из девяти на 7–8 байт, набор с беты все девять. Но
держалось правило на комментарии, а не на механизме: кандидат проверялся на
самосогласованность и принимался целиком, ни разу не сравниваясь с тем, что
лежит в репозитории.

Цена видна на #390: правка типов, которая физически не может сдвинуть
пиксель, потребовала прогона workflow, а затем правки одиннадцати полей
манифеста руками.

Теперь правило про доказательство, и оно то же, что у golden с #334: среда
доказана, если каждый кадр, который менять не собирались, совпал с
закоммиченным байт-в-байт. Расхождение растеризации спрятать нельзя — оно
задевает все кадры с текстом сразу. Снимать можно где угодно, включая WSL;
принять получится только оттуда, где кадры воспроизводятся, и перестанет
получаться в тот день, когда обновятся шрифты.

Остальное следует из того же правила: намерение объявляется
--expect-change, необъявленное расхождение останавливает приёмку,
объявленное без расхождения — тоже (ложная декларация обесценивает список),
заменяются ровно объявленные файлы, а тотальная перерисовка требует
--no-witnesses --reason, и причина уезжает в манифест.

Частый случай закрылся сам: ничего не объявлено, все кадры совпали —
принимается один манифест, руками ничего писать не надо.

Проверено шестью сквозными прогонами на подделанном артефакте, не только
юнитами: идентичный кандидат, необъявленное расхождение, объявленное,
молчаливая декларация, тотальная перерисовка без причины и с ней.

Issue: #401
User-Visible: no
2026-08-31 09:40:52 +03:00
Codex dddbbe5522 ci: make the strict typing gate actually run (#42)
r6 Medium: AC4 was measurable only on a developer's machine — no
workflow invoked mypy, so a typing regression in any of the six
allowlist modules reached dev unnoticed while the issue claimed
measurable backend quality. Coverage and lint had continuous gates;
typing had a text comparison of a committed list.

The backend job now runs mypy right after ruff, from the same pinned
dependency file (mypy==2.3.1 — an unpinned checker would redden on code
that never changed). The step derives its module list from the
pyproject.toml strict allowlist instead of duplicating it, because a
drifted duplicate is a green step checking the wrong modules, and it
refuses an empty list rather than passing silently.

Guarded twice: a contract test pins all three facts (pinned checker,
a step that really invokes it, list read from pyproject) and the new
typing-gate-stops-running mutant reddens when the invocation is
neutered.

User-Visible: no
Issue: #42
2026-08-30 22:00:52 +03:00
Codex 323b7803e0 feat: measurable backend engineering quality — stage 1 (#42)
Tooling: requirements_test.txt becomes the single source of backend CI
dependencies; pyproject.toml configures ruff (E/F/B/I, E501 excluded by
decision) and mypy strict for a grow-only allowlist of six pure modules
(junction_limits annotated to pass). The 42 substantive ruff findings
are fixed — the B023 loop-variable closures bind their variables as
parameter defaults instead of hiding behind noqa, and every remaining
noqa carries a reason (guarded by a test).

Errors: const.ERROR_CODES / ERROR_CODE_FAMILIES formalise the stable
contract; the scanner test proves every emitted code across BOTH paths
(send_error literals; class attrs, literal and variable-passed
MarkerControlError codes, f-string families) is registered and has a
localized message — 22 missing backup.error.* keys added in all four
languages. invalid_passage_fields / invalid_partition_opening_jamb_margin
ship structured JSON details (legacy format read-compat for one beta),
and _errText renders code-first: unknown codes localize, raw English
messages go to the console.

CI: the backend job lints with ruff, refuses a silently skipped HA
harness (import + collect threshold), measures branch coverage over
pure+harness, fails below the committed baseline and uploads
coverage.xml. quality_scale: docs-troubleshooting/examples honestly
done, test-coverage/strict-typing carry staged progress.

User-Visible: yes
Issue: #42
2026-08-30 21:34:31 +03:00
Matysh a3dcee5241 ci: move the harness to the current Home Assistant
Замер по AC5 #392 изменил решение. Я собирался заморозить набор на том, что
резолвилось (HA 2026.2.3, февральский), и вынести обновление в отдельную
задачу «на потом» — потому что шесть месяцев дрейфа API вслепую в dev не
отправляют.

Проверил на ветке experiment/392-py314: Python 3.14, phcc 0.13.357,
homeassistant 2026.8.3 — `450 passed, 2 skipped`. Ровно то же, что на старом
наборе, ни одного падения. Обновление оказалось бесплатным, и держать гейт на
февральском HA после такого замера нельзя.

Взята 0.13.357, а не последняя: она последняя, которая пинует стабильный
2026.8.3, дальше пинуются беты. Гейт на бете невоспроизводим — бету могут
перевыпустить под тем же номером.

pytest не закреплён намеренно: phcc задаёт совместимый диапазон сам, жёсткий
пин с ним конфликтует (ResolutionImpossible на 9.0.0, проверено).

Issue: #392
User-Visible: no
2026-08-30 20:00:17 +03:00
Matysh eb5aa2a0f8 ci: pin the HA harness and stop tests from editing sys.path
Два независимых хвоста из разбора #389, оба про то, что «зелёный» значит не
то, что читается.

#392. Оба места, где поднимается HA-харнесс, ставили зависимости без единой
версии. Python при этом закреплён на 3.13, а pytest-homeassistant-custom-
component с 0.13.348 требует 3.14 — резолвер молча уезжал на 0.13.316, а та
тянет homeassistant 2026.2.3. Интеграция полгода проверялась против
февральского HA, и состав окружения мог измениться без нашего коммита.
Версии закреплены в tests_backend/requirements.txt ровно те, что резолвились
30.08; шаг печатает установленное в лог. Это остановка дрейфа, а не
обновление: переход на 3.14 и свежий phcc — отдельная задача с отдельным
измерением.

#393. test_trails.py клал каталог пакета в sys.path при коллекции — на всю
сессию, включая HA-харнесс. Модули интеграции становились импортируемыми ещё
и как модули верхнего уровня, то есть один файл мог оказаться в sys.modules
дважды. Вставка при этом не работала ни на что: TrailBook берётся чтением
текста и exec среза, а не импортом. Убрана вместе с мёртвым spec.

Гейт статический, по исходникам: он ловит намерение, а рантайм поймал бы
последствие и только при сегодняшнем порядке тестов.

Issue: #392
User-Visible: no
2026-08-30 19:49:37 +03:00
Matysh 66db451280 ci: prove the range by judgement, not by a green verdict
Правка #388 уронила dev, и виновата подмена предиката. Я взял доказательством
`conclusion=success`, то есть оправдательный вердикт. Гейтам диапазона нужен
другой факт: судили ли этот коммит вообще. Упавший прогон коммит судил —
вердикт вынесен, автор его видел; переоткрывать такой коммит диапазоном не
надо. Не судил только отменённый.

Цена ошибки была наглядной. Backend на dev красный несколько дней по своей
причине (test_ha_import_export), успешных прогонов нет вовсе, поэтому база
уезжала на десятки коммитов назад. На 83d646c — docs-коммите — гейт
«новый код не добавляет any» предъявил 5 чужих находок из e4e1e370 и
8d431d6d и уронил frontend. Ровно тот сценарий обвинения невиновного, ради
которого делался #386, только устроенный мной.

Теперь `judgedShas` считает судимыми завершённые прогоны с любым вердиктом,
кроме cancelled, а `greenShas` остаётся для классификации (#387): там вопрос
другой — доказано ли, что тяжёлые гейты на этом дереве ПРОШЛИ. Два вопроса,
два предиката, и путать их дорого.

Запрос к API стал `status=completed` — надмножество, нужный предикат
применяет скрипт.

Issue: #388
User-Visible: no
2026-08-30 17:11:53 +03:00
Matysh 65339f63ad ci: judge the push range from the last proven-green ancestor
#387 закрыл классификацию — какие job запускать. Здесь остаток того же
дефекта: гейты, которые судят сам диапазон коммитов. Провенанс, процессный
гейт и «новый код не добавляет any» брали диапазон от головы предыдущего
пуша, а concurrency отменяет прогон предыдущего пуша штатно. Тогда его
коммиты не судит никто: свой прогон отменён, а следующий пуш сравнивает уже
с ними. Окно не закрывается никогда.

Уязвим был прямой пуш в dev — основной режим конвейера. На ветках дефекта
нет: no-new-any там всегда считает от merge-base, а resolveValidationRange
подменяет осиротевший before на origin/dev (#315).

База стала последним предком с успешно завершённым Validate. Фолбэк, когда
такого нет, сознательно оставлен прежним — before, но с пометкой в summary
«диапазон недоказуем». Расширять диапазон здесь нельзя: гейт, который сам
красит прогон, лишил бы следующий пуш зелёного предка и запер dev в
красноте навсегда. Фолбэк обязан не зависеть от собственного успеха гейта.
Дыра сужается с «всегда, когда прогон предыдущего пуша отменён» до «когда
во всём окне обхода нет ни одного успешного прогона».

Находки no-new-any теперь называют коммит, добавивший строку: диапазон стал
шире, и без имени источника сообщение обвиняло бы того, кто пушнул
следующим, — ровно то, что чинили в #386 для golden.

Issue: #388
User-Visible: no
2026-08-30 15:55:08 +03:00
Matysh 50851dc3f9 ci: classify against the last proven-green ancestor
Диапазон классификации брался от `github.event.before` — головы предыдущего
пуша. Это допущение «до этого уже проверено», и оно неверно ровно тогда,
когда прогон предыдущего пуша не завершился. А не завершается он штатно:
concurrency отменяет его следующим пушем.

На #86 (r5) это дало ложный зелёный: push 04da7eb1 тронул dist/** и
frontend/**, его прогон отменили через три минуты; следующий push fa146fb1
тронул только docs/images/**, классификация сравнила эти два коммита и
выставила frontend=false. Job «Фронтенд», а за ней golden, smoke и backend
оказались skipped — прогон при этом success. Маркеры переиспользования эти
гейты тоже не подтверждали: `Cache not found` по всем четырём.

Теперь база — самый новый предок HEAD, для которого Validate ДЕЙСТВИТЕЛЬНО
завершился успешно; если такого нет, диапазон расширяется до merge-base с
dev, то есть до всего вклада ветки. Работает по индукции: цепочка узких
диффов покрывает всё изменённое с последней настоящей проверки, а одно
незавершённое звено теперь расширяет диапазон, а не сужает.

Недоступность API не роняет job: пустой ответ опускает базу до merge-base,
то есть в сторону большего объёма проверок.

Защита от force-push (#347) сохранена: механизм, из-за которого merge-base
врал на переписанной истории, до конца не разобран, и снимать защиту, не
объяснив её, — способ получить #347 второй раз.

Issue: #387
User-Visible: no
2026-08-30 15:35:56 +03:00
Matysh fbbea47545 ci: name the commit that actually broke golden
Упавший тяжёлый гейт не пишет маркер переиспользования — и правильно, иначе
починка осталась бы незамеченной. Но следствие в том, что следующий коммит
гонит ту же job на тех же входах, падает так же, и письмо «Run failed»
называет его. 29 августа так был назван 0f7b6f5, документ ревью, который не
может изменить ни одного пикселя: сцену без эталона добавил dbbe94ae.

Теперь падение оставляет второй маркер — с тем же ключом, что у маркера
успеха. Совпадение ключа доказывает равенство входов, поэтому повторное
падение может честно сказать «красная с такого-то SHA, этот коммит её не
ронял», а первое — «причина здесь». Само падение по-прежнему не кэшируется:
job прогоняется всегда, меняется только формулировка в notice и summary.

Первопричина записывается только на первом падении: иначе SHA съехал бы на
свидетеля и сообщение перевернулось бы смыслом.

Issue: #386
User-Visible: no
2026-08-30 11:23:35 +03:00
Sergey Matyunin 0dfc74244b feat: add opt-in Glow to space card
Issue: #374
User-Visible: yes
2026-08-29 14:46:52 +03:00
Claude 23d6681d3e ci: публикация ревью-дока не имеет права трогать ничего, кроме документа
28.08 коммит bb2919f уехал в dev с тридцатью файлами вместо одного markdown:
откатил отревьюженную реализацию #359, вернул старые чанки, оставил в dist/
двойной набор. dev держал откаченное дерево три часа. Сообщение коммита было
невинным, и от рутины инцидент отличался только диффом.

Механизм воспроизведён локально, а не предположен. `git checkout -- .`
восстанавливает рабочее дерево ИЗ ИНДЕКСА, `git clean -fd` убирает
неотслеживаемое — ни то, ни другое индекс не трогает. Ревьюер работает с Bash и,
проверяя «умеет ли тест падать», вполне может сделать git add; всё оставшееся у
него в индексе прежняя уборка сохраняла, и следующий git commit забирал это
вместе с документом.

Отсюда три рубежа, каждый закрывает свой отрезок пути.

База: reset --hard на свежий origin/$target снимает и индекс, и дерево разом.
Терять нечего — документ приезжает из RUNNER_TEMP, а не из рабочей копии.
Индексируется ровно один путь, а не каталог.

Индекс: перед коммитом дифф проверяется allowlist'ом docs/reviews/.

Диапазон: перед КАЖДЫМ push проверяется origin/$target...HEAD — то есть то, что
пуш добавит в ветку. Проверок две, потому что push делается из двух мест, и
второй путь срабатывает ровно тогда, когда dev ушёл вперёд — в тех самых
условиях, при которых случился bb2919f.

Пустой дифф — тоже отказ: публиковать нечего означает, что документа нет, а
прежняя редакция шага выходила тут с нулём и оставляла вердикт без артефакта
(#171). Сравнение по префиксу каталога, а не подстрокой: docs/reviews-old и
docs/reviewsx разрешёнными не считаются. Форс-пуш отсутствует и закреплён тестом.

Четыре мутанта проверены руками, два добавлены в реестр. Пятый — «убрать одну из
двух проверок диапазона» — сначала выжил: тест требовал наличия, а не количества.
Тест усилен до подсчёта, мутант убит.

Issue: #365
User-Visible: no
2026-08-29 10:38:36 +03:00
Claude 4d73774779 ci: ребейз виден автору до пуша, конфликт называет файлы, уход dev отмечается
Конвейер приводит ветку к dev сам (#257) и при конфликте возвращает задачу, не
тратя цикл ревью. Оставались три щели, и все три про то, что человек узнаёт
поздно и без подробностей.

Первое. Отставание теперь видно в scripts/pre-push-gate.mjs до пуша, с числом
коммитов и готовой командой. Это предупреждение, а не гейт: гейтом остаётся
конвейер, который забыть не может. Смысл в цене — после любого ребейза разбор
становится полным, а не по дельте (§7.2), а конфликт всё равно чинится на машине
автора. Отключается --no-rebase-check.

Второе. Конфликт называет файлы. Список снимается ДО `git rebase --abort`: abort
снимает состояние конфликта вместе с ним, и раньше автору доставалось «не
ребейзится» без единого имени. Логика проверена на настоящем конфликте в
одноразовом репозитории — два файла названы.

Третье. Если dev ушёл вперёд, пока шло ревью, это записывается в summary
прогона, а при зелёном вердикте ещё и комментарием: вердикт вынесен по дереву,
которое уже не совпадает с вершиной линии, и слияние приведёт ветку к dev.
Комментарий только при зелёном — шуметь на каждом прогоне ни к чему, а вот
молчать перед слиянием нельзя.

Чего задача не делает: не заставляет dev стоять на месте, пока идёт ревью. Если
возвраты частые именно из-за темпа, лечится очередью слияний, а это решение о
процессе, не о скрипте.

Два мутанта проверены руками — «советовать ребейз всегда» и «никогда не сообщать
про уход dev», — каждый убит.

Issue: #364
User-Visible: no
2026-08-29 10:06:41 +03:00
Claude 583637313f test: гейт «новый код не добавляет any»
В src/** сейчас 1034 вхождения явного any в 49 файлах — больше, чем называл
аудит (330), потому что монолит с тех пор разделился и его обвязка уехала в
houseplan-editor-runtime.ts. Разовая замена такого объёма — месяц риска ради
нуля пользовательской ценности, поэтому долг снимается при плановом извлечении
подсистем (#34). Задача гейта одна: не давать долгу расти.

Судятся только добавленные строки диапазона. Изменённая строка со старым any
выглядит в диффе добавленной, и это намеренно: тронул — либо типизируй, либо
обоснуй на той же строке `// any-ok: <причина>`. Голый маркер, пустая причина и
шаблоны вроде todo, hack, потом не проходят.

Ложных срабатываний нет по построению, а не по старанию: текст разбирается
парсером TypeScript, и нарушением считается узел AnyKeyword. Регулярка по строке
ловила бы слово any в прозе внутри шаблона html и в комментариях; здесь
комментарии, строковые литералы, многострочные шаблоны и идентификаторы
company, anyOf, manyRooms узлами такого вида не являются вовсе.

Проверено исполнением на настоящем дереве, а не только юнитами: пробные коммиты
в src/wall-thickness.ts показали, что добавленный any падает с файлом и строкой,
типизированная строка в файле с 122 старыми any проходит, any-ok с конкретной
причиной проходит, а голый и «todo» — нет, и что any в прозе, строке и
идентификаторах не даёт ни одного срабатывания.

В job frontend checkout получил полную историю без блобов: diff-aware проверке
нужен диапазон, а содержимое старых ревизий — нет.

Заодно закрыта ловушка в test/validate-workflow.test.mjs: имя job искалось через
indexOf('  frontend:'), а эта строка встречается внутри `      frontend: ${{ ...
}}` в outputs job changes, поэтому срез уходил не туда. Теперь имя ищется с
начала строки.

Четыре мутанта проверены руками, два добавлены в реестр: гейт, судящий все
строки, и гейт, принимающий голый маркер.

Issue: #342
User-Visible: no
2026-08-28 16:02:17 +03:00
Matysh 1179a02122 ci: restore smoke test modules and isolate resize camera
Issue: #336
User-Visible: no
2026-08-28 12:47:37 +03:00
ClaudeandSergey Matyunin 1ea460e467 ci: перепаковка кадров документации без потерь при съёмке
oxipng снимает с набора 19.4%: 2096 КБ становятся 1689 КБ, и все десять кадров
остаются пиксельно идентичными — декодированные RGBA совпадают по sha256. Это
выбор фильтров строки и уровня сжатия, а не квантование: визуального решения нет.

Внутри съёмки, а не отдельным проходом по закоммиченным файлам: манифест хранит
imageSha256 каждого кадра, поэтому жать их в репозитории руками нельзя —
check-docs покраснеет; а если жать после подсчёта хешей, следующая съёмка вернёт
неоптимизированные байты. Хеш считается после перепаковки.

Версия oxipng попадает в манифест рядом с версией браузера и по той же причине:
байты кадра зависят от того, чем жали. Отсюда же правка шага «Вердикт» — иначе он
объявил бы «тот же браузер, а картинки изменились — изменился продукт», хотя
изменился упаковщик.

Пин версии и контрольной суммы вместо apt-get: пакет из образа раннера может
пропасть, а падение шага съёмки стоит целого цикла приёмки (#175, #206).

Проверено исполнением на прежней базе: съёмка прогнана целиком с подставным
oxipng, 2096 -> 1689 КБ, хеши манифеста совпали с файлами, check-docs зелёный.

Issue: #345
User-Visible: no
2026-08-28 10:28:13 +03:00
Codex b573590a96 ci: a force-push runs everything — the classifier stops guessing a rewritten range (#347)
github.event.before dies with a force-push, and the merge-base fallback then
guessed a diff range: on issue/333 it reported two review-doc files while the
real diff touched custom_components/** — frontend and backend jobs silently
skipped and the run stayed success, the exact #171/#207 class of silent pass
that nearly hid a genuine backend regression from code review.

The classifier now distinguishes the two fallback cases instead of merging
them: a ZERO before is a genuinely new branch and keeps the merge-base
range; a NON-ZERO before that no longer exists is a rewritten history, and
the range is not provable — frontend/backend/integration all go true, with
a loud note in the step summary. A force-push is rare and almost always
follows a rebase, where the full run is what an honest signal costs.

The three branches of the decision are pinned by a workflow-contract unit
next to the existing performance-workflow contracts.

Issue: #347
User-Visible: no
2026-08-28 10:19:37 +03:00
Matyshandclaude[bot] 2c65fcec03 perf: lazy-load editor runtime
Issue: #337
User-Visible: yes
2026-08-28 05:40:28 +00:00
Claude 59ae6b1064 ci: полная история без блобов там, где содержимое старых файлов не нужно
Полный клон — 215 МБ .git, blobless — 26 МБ, история коммитов и теги в обоих
полные (замер в #345). Две job Validate качают историю целиком: preflight и
changes. Первой нужны сообщения коммитов, трейлеры и имена изменённых файлов,
второй — только `git diff --name-only`. Содержимое старых ревизий не читает ни
одна из них ни на одном шаге.

Коммиты и деревья по-прежнему скачиваются полностью, поэтому диапазоны и
merge-base работают как раньше. Единственная догрузка блоба по требованию —
`git show origin/main:.github/workflows/process.yml` в шаге сверки, один файл.

Браузерным job фильтр не нужен: у них глубина по умолчанию, истории они не
касаются вовсе.

Тест закрепляет и обратную сторону: --depth=1 сюда подставлять нельзя, он того
же размера, но без merge-base, а на нём стоят процессный гейт, smoke-select и
каждый диапазон origin/dev..HEAD. Два мутанта проверены руками — снятый фильтр и
подмена на depth=1, — каждый убит.

Issue: #345
User-Visible: no
2026-08-28 08:00:51 +03:00
Codex ddfca3a865 fix: close code-review 330-r1 — budgets from the slowest machine, the bench in Validate, AC1 through the execution thread (#330)
H2: the benchmark budgets were calibrated on the author's sandbox with a
1.14x margin — the review runner measured tsFullCandidateMs at 169-171 ms
against a 100 ms ceiling. Budgets now keep the spec's 2-3x allowance over
the SLOWEST observed machine, and the benchmark runs as a step of the
Validate perf job on every push (it needs no browser and no bundle), not
only inside the weekly mutation gate.

M1: the promised AC1 backend test exists now and does what AC1 means: it
patches validate_junction_limits with a thread-recording wrapper inside the
real HA harness — on the event loop that would be MainThread — and proves
the verdicts survived the move (a clean write is accepted, a write adding a
spike is refused with junction_limit_angle). Spec revision 4 rewrites AC1
around this invariant instead of a fragile millisecond assertion.

M2: §4.6 equivalence is now behavioural on both sides (three boundary
fixtures each: as-is counts equal through-migration counts, TS and python),
and the parity suite gained the §7 boundary fixtures (exact 15°, exact
20 cm, the thickness-step filler run, exact 5 cm).

H1 was already closed by 7513f93d (the review ran on the previous HEAD):
check-docs is green on this tree — the screenshots and their manifest come
from one capture run.

Issue: #330
User-Visible: no
2026-08-28 03:07:44 +03:00
Claude 1866c0921e ci: ревьюер не перегоняет зелёные гейты, локальный набор перед пушем
Ревьюер гонял tsc, юниты и сборку заново в каждом раунде, хотя Validate на том
же SHA уже зелёный. Промпт прямо это требовал. Теперь шаг `validated` спрашивает
у Validate состояние ровно этого SHA, и доказательство такое же строгое, как у
reuse-маркеров (#208): не «недавно было зелено», а completed success на этом
коммите. После ребейза SHA другой, прогона для него нет — ревьюер честно гоняет
сам, и промпт это говорит.

Что Validate не покрывает, в примечании названо отдельно: смоки по диффу,
golden при правке рендера, инварианты на конкретной конфигурации. Иначе
экономия превратилась бы в «CI зелёный, значит всё проверено».

scripts/pre-push-gate.mjs — локальный набор: tsc, юниты, смоки по диффу
(smoke-select), мутанты по диффу (mutation-gate --changed). Замер на реальном
диапазоне 953f675~1..953f675: 46 секунд на всё вместе с двумя смоками.

Три свойства, без которых набор бесполезен: не останавливается на первом
упавшем; громко перечисляет, чего не проверял; не претендует на полноту. Бандл
не собирает — раскладывает закоммиченный dist, а свежесть проверяет сам продукт
через assertFreshDemoBundle внутри смока.

В хуке выключен по умолчанию: 20-45 секунд на каждый пуш, включая пуш одной
строки документации, — цена осознанная, включается HP_PREPUSH_GATE=1.

Дельта-промпт для spec-ревью (пункт 2) уже существует: блок «объём разбора по
дельте» из #214 покрывает оба этапа и прямо называет «дифф файла ТЗ или тела
issue для spec». Ничего не добавлял.

Issue: #343
User-Visible: no
2026-08-28 02:15:03 +03:00
Claude 2b1964f973 ci: бандл собирается один раз, четыре лёгкие джобы стали одной
Бандл собирался пятью job независимо: три шарда смоков, golden, перф-смок —
каждая гоняла `bundle:sync`, то есть `tsc --noEmit` плюс rollup. Теперь его
собирает `frontend` и выкладывает артефактом, остальные скачивают и раскладывают
`bundle-sync.mjs`. Подмену артефакта отдельной проверкой ловить не нужно:
assertFreshDemoBundle сверяет вшитый в бандл отпечаток с sourceFingerprint
выкачанного дерева, и каждая браузерная job делает это перед первым кадром.

`npm ci` остаётся во всех: браузерным job нужен playwright из node_modules, а не
только бандл. Артефакт node_modules был бы медленнее `npm ci` с тёплым кэшем.

docs, process-workflow-sync, provenance и process-gate стали шагами одной job
`preflight`. Независимость сохранена намеренно: у каждого шага
continue-on-error, вердикт в конце падает и перечисляет всё упавшее сразу.
Прежняя запись «краснеет сам и не роняет остальные» продолжает действовать — на
уровне шагов, с той же гранулярностью в логе.

hacs и hassfest не тронуты: предложение сузить их до dev и тегов уже выполнено
классификатором `changes` — на ветках задач они и так идут только при правке
манифестов, а на dev фильтров нет намеренно (гейт беты требует, чтобы «зелёный
Validate» значил одно и то же).

test/validate-workflow.test.mjs закрепляет то, что в диффе строк не видно:
висячая зависимость `needs` не роняет YAML, а молча пропускает job навсегда.
Три мутанта проверены руками — висячая зависимость, вернувшаяся вторая сборка,
шаг без continue-on-error, — каждый убит.

Issue: #336
User-Visible: no
2026-08-28 02:04:40 +03:00
Codex 2c20f2dc35 perf: mutation-gate builds the bundle only for browser guards, compiles incrementally, shards and diffs (#332)
Four independent cuts into the 2-4 hour full run, none touching the contract
"a mutant must turn its guard red":

- guardNeedsBundle: rollup runs only for guards that open the built bundle
  (demo/ smokes, golden captures, bundle:sync) — 68 of 253 registry entries.
  Unit and backend guards never read dist/ as a build artifact (verified
  against every test that mentions dist/**: they read the git checkout or
  synthetic files), so 185 mutants skip the most expensive step entirely.
- seedTestBuild + incremental tsc: the mutant worktree starts from the main
  tree's warm test-build/ and .tsbuildinfo; tsc compares file hashes, not
  mtimes, so the fresh checkout stays warm and only the mutated delta is
  recompiled. This also speeds up the long guards that run tsc themselves.
- --changed[=range]: run only mutants whose patch files are touched by the
  diff (origin/dev..HEAD by default). An empty selection is an honest success
  with an explicit message — the full registry remains the pre-release
  contract, per the workflow comment.
- --shard=i/n: deterministic interleaved slices; the workflow runs a 4-way
  matrix, and a warm test-build step feeds every shard. Interleaving spreads
  the expensive browser mutants across shards instead of clumping them.

Measured per mutant on this machine: unit 12-13 s (was ~50-70 s), backend
6 s, browser 32 s (unchanged — the bundle is genuinely needed there). Full
run estimate drops to ~70 sequential minutes, ~20 on four shards.

Unit coverage: guard classification on real registry shapes, a floor on both
classes so the split cannot silently collapse, changed-selection semantics,
and shard completeness/disjointness with an anti-clumping bound.

Issue: #332
User-Visible: no
2026-08-28 01:33:36 +03:00
Codex 6a3ac52258 ci: the performance-workflow contract test follows the renamed labels
The test pins the literal workflow name and the release-gate label; both
moved to the Russian names of #327.

Issue: #327
User-Visible: no
2026-08-27 18:50:02 +03:00
Codex 1a8b480355 ci: every workflow and job carries a human-readable Russian name
Owner decision (chat, 2026-08-27): the running check's name must say what it
does, in Russian. Scripts locate workflows by file name (release-gate.mjs ->
validate.yml), so display names are free; job ids and needs are untouched.
The same content is cherry-picked to main because release workflows execute
from the default branch and process.yml must stay identical in main and dev.

Issue: #327
User-Visible: no
2026-08-27 18:46:53 +03:00
Matysh 8210af6472 ci: parallelize independent full performance profiles
Issue: #322
User-Visible: no
2026-08-27 12:13:26 +03:00
Matysh 6b9f5eea96 ci: support legacy performance baselines
Issue: #322
User-Visible: no
2026-08-27 11:04:38 +03:00
Codex b4cccfdf63 fix: pin DoR to the commit era and merge only the reviewed SHA (#311, #312)
Правило 10 гейта (#311): DoR сверяется с моментом НАПИСАНИЯ кода — authorDate
коммита класса A не может предшествовать первому labeled-событию S5-ready+
из timeline issue; продвижение метки больше не прячет нарушение, ребейзы
конвейера его не смывают (authorDate переживает их). Проверка вторичная к
правилу 8: недоступный timeline — warn, правило 8 остаётся fail-closed.
LOG_FORMAT несёт authorDate третьим полем (append-совместимо).

Шаг слияния конвейера (#312): сливается только проверенный SHA — вершина
ветки сверяется с материалом ревью (допустим ровно один doc-коммит публикации
с диффом только docs/reviews/ поверх); расхождение отменяет слияние с
возвратом в S6-in-progress тем же путём, что конфликт (инвариант «метка
меняется всегда» сохранён). PROCESS.md §2.7 фиксирует правило «вердикт
привязан к SHA» и для ревьюера.

Issue: #311
Issue: #312
User-Visible: no
2026-08-26 03:15:57 +03:00
Claude 2970b8091b ci(process): ask the reviewer about the wall key invariant
Issue: #259
User-Visible: no
2026-08-23 13:51:37 +03:00
Claude 10999a5c5c ci(process): привести ветку к dev до код-ревью, а не после
Ревью шло по ветке как есть, слияние делало ребейз: проверенный SHA и
слитый SHA были разными коммитами. Текстовое расхождение ловил конфликт,
смысловое git склеивал молча — так пришёл регресс #234. Заодно конфликт
обнаруживался после сорока минут работы ревьюера, хотя виден до них.

Новый шаг для этапа code, сразу после выбора ветки: потомок dev —
ничего; отстала и ребейзится — ребейз, push с --force-with-lease, ревью
приведённого состояния и запись о ребейзе в промпт (§7.2 требует полного
разбора); конфликт — возврат в S6-in-progress без запуска ревью.

Issue: #257
User-Visible: no
(cherry picked from commit 793a6486d8)
2026-08-23 10:55:44 +03:00
Matysh 6d0fa3b819 build: stop committing the stand copy of the bundle
Issue: #255
User-Visible: no
2026-08-23 10:15:45 +03:00
Matysh 7e69b4bb28 ci: shard the smoke suite and stop validating review docs
Issue: #254
User-Visible: no
2026-08-23 09:55:04 +03:00
Matysh 74d2285d1b ci: ask the reviewer about numbers seen twice
Issue: #254
User-Visible: no
2026-08-23 09:50:06 +03:00
Matysh 59be468506 test: run model invariants over every shipped model
Issue: #254
User-Visible: no
2026-08-23 09:40:50 +03:00
Matysh 5fa3733c7e ci: make the screenshot verdict compare browsers, not counts
Issue: #246
User-Visible: no
2026-08-22 19:30:48 +03:00