21 KiB
CODE-REVIEW-682-r1
- Issue: #682 (эпик #674, волна 5 гигиены)
- Этап: code (PROCESS.md §2.7)
- Заход: r1 · блокирующих циклов израсходовано 0 из 4 (бюджет тратят только жёлтые/красные вердикты)
- Материал: диапазон
origin/dev..HEAD, ровно97d192687f1990bfab12ee919d5818cf0d9f6f8e(три коммита поверхdev@dd3a5042:e9cfc25eинструмент,7feb6177перенос ТЗ,97d19268перенос документов ревью) - Трек: инфраструктурная задача (не единственный класс A файл) — входит сразу в
S7-code-review, спецификация — тело issue
Скоуп
Волна 5 архива выпущенного (#674): инструмент scripts/reviews-archive.mjs
(класс B) + два переноса класса C — docs/specs/* без живых ссылок →
legacy/specs/, документы ревью задач линий ≤ v1.77.0 → legacy/reviews/<тег>/.
Плюс правки инфраструктуры вокруг переноса: process-gate.mjs (класс путей),
reviews-index.mjs (порог теста), process-metrics.mjs (счёт раундов по двум
каталогам), review-doc-guard.mjs и task-packet.mjs (сравнение деревьев с
исключением обоих каталогов), PROCESS.md §2.10 и docs/DEVELOPMENT.md
(шаг чеклиста). Работа не product-кода: src/** не тронут, User-Visible: no
на всех трёх коммитах — корректно.
Как проверялось
Validate на 97d19268 зелёный (run 36350079008/36348494784) — дешёвые гейты
(typecheck, npm test, npm run build + bundle-policy) этим прогоном
подтверждены, повторно не гонял.
| Гейт | Прогнан | Результат |
|---|---|---|
node --test test/reviews-archive.test.mjs test/process-metrics.test.mjs test/process-gate.test.mjs test/reviews-index.test.mjs |
да | 61/61 зелёные |
node --test test/task-packet.test.mjs test/review-doc-guard.test.mjs |
да | 76+/76+ зелёные (косвенно задетые файлы) |
node scripts/mutation-gate.mjs --id=reviews-archive-moves-open-line-issue |
да | поймано 1/1 |
node scripts/mutation-gate.mjs --id=reviews-archive-first-line-wins |
да | поймано 1/1 |
node scripts/process-gate.mjs --range origin/dev..HEAD |
да | 0 нарушений, 3 коммита |
node scripts/check-docs.mjs --external --screenshots=warn |
да | passed (7 файлов, 12 внешних ссылок) — не проверяет docs/reviews/, docs/specs/, legacy/** |
node scripts/reviews-index.mjs --check |
да | индекс свеж |
node scripts/check-inputs.mjs --coverage |
да | чисто |
независимая сверка счёта раундов (reviewRounds по git ls-tree до/после) |
да | 694 пары «этап:задача» и до, и после — воспроизвёл заявленное число сам |
| независимая сверка количества перенесённых файлов/задач | да | legacy/reviews: 965 файлов, 14 каталогов v1.63.0…v1.77.0 (заявлено 965/332 — файлы совпали); docs/reviews: 155 (154 задачи + INDEX.md); legacy/specs: 219; docs/specs: 21 — все числа автора подтверждены прямым подсчётом |
git grep перенесённых имён docs/specs/* вне legacy/, docs/reviews |
да | пусто — подтверждает заявление автора |
полнотекстовая проверка markdown-ссылок в docs/reviews/, legacy/reviews/, docs/specs/, legacy/specs/ (собственный скрипт, ниже) |
да | 53 битых из 302 проверенных — находка H1 |
| обратная проверка «зачем каждый из 21 оставшихся ТЗ жив» | да | 19/21 объяснены прямой или транзитивной живой ссылкой; 2 — нет (находка M/L, см. ниже) |
npx tsc --noEmit, npm run build, полный npm test |
нет | Validate на этом SHA уже зелёный, диф не трогает src/**/сборку |
npm run golden:verify, смоки, инварианты модели, pytest tests_backend, performance |
нет | диф не трогает рендер, геометрию, Python — не применимо по AC задачи |
node scripts/smoke-select.mjs --base --head |
нет | нет исполняемого frontend-диффа (тот же вывод дал автор в «Сделано») — согласен, src/** и demo/** не менялись |
Находки
Medium (в скоупе, блокирует зелёный) — систематически битые относительные ссылки в перенесённых документах
Перенос добавляет документам ревью один уровень вложенности
(docs/reviews/X.md → legacy/reviews/<тег>/X.md), но скрипт переписывает
относительные ссылки только внутри legacy/specs/* (коммит 7feb6177); ссылки
внутри самих переносимых документов ревью (legacy/reviews/**) и ссылки
на только что перенесённые документы из ещё живых или уже перенесённых
соседей никто не трогает. Итог на финальном дереве (97d19268, а не на
промежуточном состоянии после 7feb6177, которое проверял автор) — 53 битые
ссылки в 46 файлах трёх разных категорий:
- Ссылки на ТЗ из архивных ревью (~40 шт.), например
legacy/reviews/v1.68.0/SPEC-REVIEW-223-r1.md→](../specs/223-optimize-coordinate-canonicalization.md)— было верно изdocs/reviews/, сталоlegacy/reviews/specs/…(не существует; нужно../../specs/…или../../../docs/specs/…, в зависимости от того, уехал ли сам ТЗ). - Самоссылки ревью на предыдущий раунд (~8 шт., линия v1.77.0), например
legacy/reviews/v1.77.0/SPEC-REVIEW-593-r3.md→](../../docs/reviews/SPEC-REVIEW-593-r2.md)— этот путь и раньше был избыточным, но резолвился в самуdocs/reviews/(два уровня вверх отdocs/reviews/возвращают в корень); после переноса на один уровень глубже он попадает в несуществующийlegacy/docs/reviews/…. - Ссылка вперёд, в документ, перенесённый позже в этом же PR:
legacy/specs/262-readd-child-entity-after-device-delete.md:6—](../../docs/reviews/SPEC-REVIEW-262-r1.md). Коммит7feb6177(перенос ТЗ) переписал эту ссылку и на тот момент она была верна; следующий коммит97d19268(перенос ревью) увёл цель вlegacy/reviews/v1.68.0/SPEC-REVIEW-262-r1.md, не обновив ссылку — итог на финальном SHA битый. - Ссылка из ещё живого документа на только что архивированный:
docs/reviews/CODE-REVIEW-635-r1.md:104—[CODE-REVIEW-594-r1.md](CODE-REVIEW-594-r1.md)(задача #635 ещё открыта и не переносится, но цитирует #594 как пример; #594 выпущен в v1.77.0 и уехал вlegacy/reviews/v1.77.0/этим же коммитом).
Чем воспроизведено (полный список 53 путей длиннее, ниже — воспроизводящая команда и точечные примеры):
$ python3 - <<'EOF'
import re, os
link_re = re.compile(r'\]\(([^)]+)\)')
for root in ['legacy/reviews', 'legacy/specs', 'docs/reviews', 'docs/specs']:
for dirpath, _, files in os.walk(root):
for fn in files:
if not fn.endswith('.md'): continue
fpath = os.path.join(dirpath, fn)
for m in link_re.finditer(open(fpath, encoding='utf-8', errors='replace').read()):
link = m.group(1).strip()
if link.startswith(('http', '#', 'mailto:')): continue
lp = link.split('#')[0].split(' ')[0]
if not lp or not (lp.endswith('.md') or '/' in lp): continue
target = os.path.normpath(os.path.join(dirpath, lp))
if not os.path.exists(target):
print(fpath, '->', link)
EOF
# 53 строки, например:
legacy/reviews/v1.68.0/SPEC-REVIEW-223-r1.md -> ../specs/223-optimize-coordinate-canonicalization.md
legacy/reviews/v1.77.0/SPEC-REVIEW-593-r3.md -> ../../docs/reviews/SPEC-REVIEW-592-r2.md
legacy/specs/262-readd-child-entity-after-device-delete.md -> ../../docs/reviews/SPEC-REVIEW-262-r1.md
docs/reviews/CODE-REVIEW-635-r1.md -> CODE-REVIEW-594-r1.md
Это прямо противоречит явному заявлению в теле коммита 7feb6177:
«Относительные ссылки перенесённых файлов переписаны … все 26 резолвятся» —
утверждение было верным только на промежуточном дереве между вторым и третьим
коммитом, а не на финальном SHA материала (97d19268), который и есть предмет
ревью (§2.7: числа и факты сверяются с git rev-parse HEAD). Ни один гейт
этого не ловит: check-docs.mjs проверяет фиксированный список из 7
канонических файлов, docs/reviews/, docs/specs/ и legacy/** в него не
входят, а автоматика процесса (review-doc-guard.mjs, process-metrics.mjs)
читает документы как текст/имена файлов, не как markdown-ссылки, поэтому
поломка не мешает конвейеру — только человеку, идущему по ссылке из архива.
Функционального или продуктового ущерба нет (SHA-якоря «Материала раунда» —
это литеральный текст, не ссылки, и не пострадали; ни один тест или скрипт
не разыменовывает эти пути), поэтому не High. Но это прямая находка в
скоупе задачи — весь смысл переноса «одним коммитом класса C» и стиля
"история не переписывается" в том, чтобы архив оставался читаемым и
навигируемым, а не только грузом в git-истории; 53 битые ссылки в 46 файлах —
не мелочь, и без исправления она войдёт в legacy/ уже сломанной. Без
второго High это жёлтый вердикт, возврат автору (§3 п.8).
Похожий класс проблемы будет повторяться на каждой следующей волне архива
(v1.78.0 и далее), если reviews-archive.mjs не научится либо переписывать
относительные ссылки при git mv (как это уже сделано для legacy/specs/*
вручную), либо хотя бы фейлить --apply предупреждением о ссылках,
которые он не может проверить.
Low (в скоупе, самостоятельно снимаю с записью) — два архивных ТЗ без документированной причины остаться
Собственный критерий задачи: в docs/specs/ остаются только ТЗ, на которые
ссылаются живые код/тесты/документы, и те, на которые ссылаются они сами.
Я проверил все 20 файлов (без README) на прямую и транзитивную ссылку и
подтвердил обоснование для 18 из них (например: 067 ← LIGHT.md, 084/050
← транзитивно через 067, 043 ← scripts/support-relay/README.md, 505 ←
demo/helpers/README-ha-dialog.md, 485* ← RADAR.md/ARCHITECTURE.md,
089/122/160/471 ← ISOMETRIC.md/ADR, 141/229 ← wall-merge.ts, 220 ←
space-order.ts, 146 ← SUN.md, 506 ← ARCHITECTURE.md, 039 ←
DECOR-EDITOR.md). Для двух — 329-junction-limits.md и
403-area-relocation-safety.md — живой ссылки не нашёл: обе задачи закрыты
без последующих коммитов, единственные совпадения по имени — упоминание
номера issue в заголовке раздела CONFIG-COMPATIBILITY.md (не ссылка на файл)
и строковый литерал-пример в test/review-doc-guard.test.mjs/
review-doc-guard.mjs (синтетическая фикстура с реальными историческими
числами, не зависящая от существования файла). По собственному критерию
задачи эти два файла — кандидаты в legacy/specs/, но не мешают ничему:
scripts/task-packet.mjs продолжает работать с любым именем в docs/specs/,
и живучесть двух лишних файлов не ломает ни один тест. Снимаю как Low: не
блокирует, but стоит доразобрать в следующей волне (или этой же правкой,
на усмотрение автора).
Что проверено и корректно
archivePlan— чистая функция, покрыта тестами по каждому правилу: задача уходит в последнюю свою линию (не в первую — проверил мутантомreviews-archive-first-line-wins, ловится); задача с трейлером в открытой линии остаётся целиком (мутантreviews-archive-moves-open-line-issue, ловится); закрытая без выпуска — по линии документа;RELEASE-REVIEW-*уходит в каталог своего тега; чужое имя не трогается. Прогнал оба мутанта сам (node scripts/mutation-gate.mjs --id=…) — 1/1 у каждого, не только поверил заявлению автора.- Числа автора воспроизведены независимо, не только прочитаны в
комментарии: 965/332 (
legacy/reviews, 14 каталогов), 154+INDEX.md (docs/reviews), 219 (legacy/specs), 21 (docs/specs), 694 парыreviewRoundsдо и после (посчитал поorigin/devи поHEADотдельно — совпало). git grepпо перенесённымdocs/specs/*внеlegacy//docs/reviews— пусто, подтверждено самостоятельно; под-перенос (файл должен был уехать, но остался нужным где-то в живом дереве) не найден.process-gate.mjs:legacy/— класс C, не пересекается с классом D (списокCLASS_Dне содержитlegacy/); коммиты проходят гейт классификации и трейлеров (0 нарушений на диапазоне).- Трейлеры: все три коммита несут
Issue: #682,User-Visible: no— верно для инфраструктурной/документной правки без видимого поведения; порядок коммитов (инструмент → ТЗ → ревью) совпадает с порядком в issue. legacy/действительно только Markdown — проверилfind legacy -type f ! -name '*.md'— пусто, соответствует комментарию вprocess-gate.mjs.- Идемпотентность инструмента: повторный
node scripts/reviews-archive.mjs --through=v1.77.0на уже применённом дереве возвращает «переносится 0», остальные 154 — «задача есть в открытой линии»; корректное поведение при повторном запуске. reviewDocNames/reviewRounds: перенос части раундов задачи в архив не меняет счёт пар «этап:задача» — проверено и юнит-тестом автора, и отдельным прогоном на полном дереве репозитория.- PROCESS.md §2.10 и DEVELOPMENT.md: правки текста соответствуют фактической реализации (команда, условие пустой очереди S7, порядок теги → архив после ревью линии); не нашёл противоречия между текстом канона и кодом.
Чего не проверял
- Полные
npx tsc --noEmit,npm test,npm run buildсо сверкой бандла — не гонял повторно, засчитан зелёный Validate на этом SHA (диф не трогаетsrc/**,dist/**). golden:verify,smoke-select, инварианты модели,pytest tests_backend, performance-профили — не применимы: диф не содержит рендера, геометрии, Python-кода, frontend-исполняемого кода.process-metrics.mjsживойfetchSnapshotчерезgh api— как и автор, не выполнял (недоступно из песочницы); опирался на статическую сверкуreviewRoundsпо дереву, что и есть предмет изменения.- Полную посимвольную ручную вычитку всех 965+219 перенесённых файлов — не
делал; проверял структурно (числа, схема имён, случайные и целевые выборки)
и полнотекстовым скриптом на битые markdown-ссылки (раздел «Как
проверялось»), которым и нашёл находку. Не исключаю битых ссылок вне
синтаксиса
[text](path)(голые URL,*-текст без скобок) — синтаксис проверен только в стандартной markdown-форме. - Ссылки из перенесённых документов на GitHub (issue/PR URL) — не резолвил
каждую сетевым запросом; синтаксически они не зависят от структуры
репозитория и не могли пострадать от
git mv.
Материал раунда
- Ветка:
issue/682-hygiene-wave5, коммит97d192687f19— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
790ebed0bed2718b1da899e2b4c58e3de3f48a6agit log --all --format='%H %T' | grep 790ebed0bed2 - Тело issue:
2e9a988e3aad9028017feba6b8bde3e8f5b31db5aee0749e15eb6b5d205ab1b4 - Вердикт конвейера:
yellow· High 0