7 Commits
Author SHA1 Message Date
Claude 696f5a789f docs(hygiene): сократить вход агента, у правила — один дом (#680)
Волна 3 эпика #674. AGENTS.md 650 → 187 строк: карта пакета, маршрут чтения,
правило №1, классы и треки одной строкой со ссылками, трейлеры, рабочие
деревья, хендофф и ожидание вердикта; пересказы PROCESS.md — ссылками на
разделы. Неверный список «Gate jobs» снят (списки jobs не копируются в прозу,
шапка PROCESS.md). Правила, жившие только в AGENTS, получили дом: жёлтый
вердикт при выполненных AC — PROCESS §2.7; свежесть бандла, съёмка только в
Linux (#455, HP_ALLOW_FOREIGN_CAPTURE) и смоки из AC до S7 (#151) —
TESTING.md; причуда демо-стенда и среда-зависимый smoke_opening_measure —
DEVELOPMENT › Smoke tests; отказ публикации без `Release:` и при несвежем
отпечатке бандла, отмена Validate новым пушем, кандидат беты не
promotion-only, fail-closed реестра Labs — DEVELOPMENT; предупреждение и
ошибка свежести скриншотов — CONTRIBUTING.

PROCESS.md: §13 (внедрение с открытым ⏳), §14 (блок со ссылкой на
несуществующий docs/PROCESS.md) и §7.3 (история) удалены. Ссылки «§7.2» на
правило полного разбора после ребейза ведут в §2.10, на сверку SHA перед
выводом — в §2.7; то же в сообщениях scripts/branch-state.mjs,
merge-candidate.mjs, review-doc-guard.mjs, pre-push-gate.mjs, в промпте
_process.yml и TESTING.md. Число `any` в прозе → `node scripts/no-new-any.mjs
--total` (новый режим, юнит-тест; было «1034 в 49 файлах», сейчас 862 в 52),
дата-число замороженного списка якорей монолита снято. Устаревшая команда
пересъёмки скриншотов в §8 заменена ссылкой на действующий путь.

STATUS.md 113 → 61 строка: сгенерированный снимок, текущий цикл и девять
строк решений; Workflow, CI, Toolchain, Tests, Scope, open items и политика
документации — ссылками (PROCESS §2.6, DEVELOPMENT › Release, TESTING);
локали en/ru/de/fr; закрытые «coverage, mypy strict» сняты.

DEVELOPMENT.md: file-sync и «Reproducible scripts» (прототип) удалены;
раздел Release — единственный дом релизной механики: введение, правила
тела стабильного релиза (#328, release:notes), шаг continuity:screencast,
источники версии по release-contract. CONTRIBUTING: ссылка на Release вместо
пересказа, замеры клона без чисел. TESTING: any-гейт — ссылкой на PROCESS §8.

entry-cost: автор 11 125 → 5 407 слов, ревьюер 8 464 → 4 285.

Issue: #680
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
2026-09-27 22:33:03 +03:00
Claudeandclaude[bot] fa319e37b3 test(harness): тестовый фасад window.__hpTest и гейт no-new-private-writes (#629)
- scripts/no-new-private-writes.mjs: смоки и demo/helpers/** не добавляют
  записей в приватное состояние карточки (присваивание, ++/--, delete по
  цепочке с сегментом _x; от this — нет) и вызовов _setMode/_openRoomEdit/
  _openMarkerDialog/_openSpaceDialog. Зачёт правки по полю, перенос блока —
  movedLinesByFile из no-new-any; исключение // private-ok: <причина>.
  --count — остаток на HEAD. Подключён в gate:small и в шаг frontend рядом с
  no-new-any, с той же базой.
- demo/helpers/hp-test.mjs: 10 операций через контрактные хуки и события
  фикстуры (setMode, setTool, switchSpace, openRoomEdit, openMarkerDialog,
  openSpaceDialog, setServerConfig, setLayout, input, close); ставится
  launch*() из demo/serve.mjs. В бандле фасада нет.
- demo/srv/demo.html: доставка houseplan_config_updated/_layout_updated,
  __pushServerConfig/__pushServerLayout; после доставки запись со старым
  expected_rev — conflict, как у настоящего сервера.
- HP_SMOKE_CHECKS=1 печатает имена проверок в finish().
- smoke_area_relocation, smoke_glow, smoke_grid_snap переведены на фасад без
  потери утверждений; новый smoke_test_facade доказывает каждую операцию.
- 7 мутантов, docs/TESTING.md (раздел + правило №6), PROCESS.md §2.7, AGENTS.md.

Issue: #629
User-Visible: no
2026-09-25 01:25:20 +00:00
Claude 1403fb440b test: давать послабление переноса блоку, а не строке (#592)
Находка M1 код-ревью r1, воспроизведена: первая редакция сопоставляла
одиночные строки по всему диффу, и этого хватало для обхода. Несвязанная
уборка удаляет где-то строку с `any`, новый код добавляет свою — текстуально
такую же, — и гейт молчит. Совпадение здесь не экзотика: в базе 887 явных
`any`, типовые однострочники повторяются буквально, и две такие строки
встретились в самом коммите переноса.

Теперь перенесённым признаётся только непрерывный кусок не короче пяти строк,
встречающийся подряд и целиком среди удалённых строк ОДНОГО файла. Случайно
совпасть пятью строками подряд практически невозможно, а настоящее извлечение
подсистемы из таких кусков и состоит: на этом диффе признано 1296 строк из
1395 — на одну меньше, чем при построчном сопоставлении, и эта одна была ровно
случайным совпадением.

Каждый удалённый кусок оплачивает ровно одно добавление: повторная вставка того
же блока остаётся новым кодом.

Мутант заменён на `no-new-any-forgives-a-single-matching-line` — он опускает
порог до одной строки, то есть открывает ровно найденную дыру; тест обхода на
нём краснеет. Тестов пять: перенос куска, обход одиночной строкой, кусок короче
порога, бюджет на повторную вставку, смена отступа.

Issue: #592
User-Visible: no
2026-09-18 22:17:25 +03:00
Claude 61c74a7012 test: не считать дословный перенос новым any (#592)
Validate вернул задачу с красным `no-new-any`: шесть строк в новых модулях
диалогов. Все шесть перенесены побайтово из редакторского рантайма — `as any`
в них написан годы назад и в этом диапазоне не менялся. Счёт долга это
подтверждает: явных `any` в `src/**` было 887 и осталось 887.

Гейт мерил «добавленные строки», и для извлечения подсистемы такая мера
ложна: она требует типизации ровно там, где ничего не изменилось. Причём
единственный способ её удовлетворить — дописать `any-ok` в перенесённое тело,
то есть сломать доказательство переноса (тела обязаны совпадать побайтово) ради
зелёного гейта. Это починка задом наперёд.

Теперь строка, дословно удалённая из одного файла диапазона и добавленная в
другой, новым кодом не считается. Бюджет ведётся мультимножеством: два
добавления при одном удалении оставляют одну находку, а перенос с изменённым
отступом переносом не считается — это уже правка. Отчёт печатает, сколько строк
признано перенесёнными, чтобы послабление было видно, а не подразумевалось.

Гейт #342 при этом остаётся собой: новый `any` без удаления-близнеца — находка,
и три новых теста проверяют обе стороны. Мутант
`no-new-any-counts-every-added-line-as-moved` признаёт перенесённой любую
добавленную строку — заявленный тест на этом краснеет.

Отклонение от скоупа ТЗ: `scripts/no-new-any.mjs` в нём не назван. Альтернатива
— шесть `any-ok` в перенесённом коде — стоила бы побайтовой сверки, на которой
держится вся приёмка задачи.

Issue: #592
User-Visible: no
2026-09-18 19:59:11 +03:00
Claude 24c1b723f9 infra: idempotent review controller, verdict reuse, single mutant build, current docs
Review pipeline (process.yml):
- concurrency moves from the workflow to the guard/review jobs and the guard
  runs only for S4-spec-review / S7-code-review. Any other label used to enter
  the issue's concurrency group and evict the pending review run (sample of
  150 runs since 2026-09-01: 92 empty guard-only runs, 30 cancelled).
- the guard reads the issue's current labels instead of the event snapshot; a
  label removed before the run starts is a withdrawn request, no comment.
- a green verdict is re-applied without calling the model when the latest
  review document carries the pipeline-recorded verdict `green`/High 0 and the
  tree differs from its anchor in nothing outside docs/reviews/** (#437 r4
  re-reviewed an unchanged tree for 7 minutes). The verdict from
  structured_output is now written into the anchor block for that purpose.
- the reviewer is pinned to the captured material SHA in the prompt; the
  broken escaping in the "merge cancelled" comment (empty SHAs) is fixed.

Mutation gate: nine browser guards started with `npm run bundle:sync` although
the runner already builds the mutant bundle — a second rollup plus a
`tsc --noEmit` that fails on a non-strict mutant before the smoke even runs.
Prefix removed; `--check` refuses guards that build the bundle themselves.

Docs: SCOPE (Project v2 dropped, three editors), STATUS (#437 merged, HACS zip
automated), USER-GUIDE ru/en (static card shows live states; kiosk double tap
on free background fits all), #34 → #425 references, #367 named as closed in
bundle-budget messages, PROCESS §10.4 and AGENTS.md describe the controller.

Issue: #499
User-Visible: no
2026-09-09 00:06:27 +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
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