Новый хук: запрет ссылок на номера этапов плана в коде #14

Closed
opened 2026-08-26 21:06:53 +07:00 by claude-secretary · 3 comments

Заведено по замечанию на ревью jamzap/backend!462: «(Этап 6) — и подобные слова не должны быть у нас в коде».

Зачем отдельный хук

no-mvp-phase-language запрещает ровно эту фазовую рамку, но по одному слову — MVP. «Этап 6» — та же рамка, привязанная к номеру в плане разработки, и вред от неё прямее: из «Этап 8» не следует ничего о поведении кода. Через полгода читатель не знает, что это был за этап, а план давно закрыт.

Особенно плохо в файлах, которые грузятся в стартовый контекст агента. Реальный пример из jamzap/backend, app/admin/core/model_admin.py:9: «На Этапе 3 реально используется только list_display + search_fields». Агент читает это как описание текущего состояния и проверить не может.

Расширять no-mvp-phase-language нельзя: его скоуп у потребителей включает docs/**, а в плановом документе перечень этапов — предмет документа. Нужен отдельный id, который потребитель наводит только на код.

Что ловить

Слово + номер этапа: Этап 6, Этапа 3, Этапе 2, Фаза 2.5, Milestone 2, Sprint 4, плюс форма с кавычками — Этап «Preview Tool».

Чего НЕ ловить — важнее

Слово «фаза» само по себе легитимно, когда описывает фазы алгоритма, а не этапы плана. Живой пример, app/modules/content/services/catalog_audit.py:

"""Фаза A: конкурентные сетевые пробы, ни одной записи в БД."""
...
"""Фаза B: последовательное применение решений."""

Слово то же, смысл другой, вреда нет. Отсюда требование: признаком должен быть номер (или закавыченное имя этапа), а не слово. Буквенные Фаза A / Фаза B обязаны проходить.

Замеры на реальном дереве

jamzap/backend: 64 места в 36 файлах, основная масса app/admin/** (26) и tests/admin/** (9). Формы: (Этап N), Этап N добавляет ..., наследие Этапа 3, live-баг Этапа 2, Регрессия Этапа 5, Definition of Done Этапа 5, (Фаза 2.5).

Приёмка

  • Этап N / Этапа N / Этапе N, Фаза N, Milestone N, Sprint N ловятся
  • Фаза A / Фаза B (буква вместо номера) проходят — фикстура обязательна
  • Этап «Имя» ловится
  • Скоуп задаёт потребитель через files:, дефолтного нет — как у no-broken-repo-paths
  • README описывает границу «этап плана против фазы алгоритма» явно
  • Мутации: снятие требования номера (тогда Фаза A покраснеет), выпадение падежных форм

Потребитель: jamzap/backend#478.

Заведено по замечанию на ревью jamzap/backend!462: «(Этап 6) — и подобные слова не должны быть у нас в коде». ## Зачем отдельный хук `no-mvp-phase-language` запрещает ровно эту фазовую рамку, но по одному слову — `MVP`. «Этап 6» — та же рамка, привязанная к номеру в плане разработки, и вред от неё прямее: из «Этап 8» не следует **ничего** о поведении кода. Через полгода читатель не знает, что это был за этап, а план давно закрыт. Особенно плохо в файлах, которые грузятся в стартовый контекст агента. Реальный пример из jamzap/backend, `app/admin/core/model_admin.py:9`: «На Этапе 3 реально используется только `list_display` + `search_fields`». Агент читает это как описание текущего состояния и проверить не может. Расширять `no-mvp-phase-language` нельзя: его скоуп у потребителей включает `docs/**`, а в плановом документе перечень этапов — предмет документа. Нужен отдельный id, который потребитель наводит **только на код**. ## Что ловить Слово + номер этапа: `Этап 6`, `Этапа 3`, `Этапе 2`, `Фаза 2.5`, `Milestone 2`, `Sprint 4`, плюс форма с кавычками — `Этап «Preview Tool»`. ## Чего НЕ ловить — важнее Слово «фаза» само по себе легитимно, когда описывает **фазы алгоритма**, а не этапы плана. Живой пример, `app/modules/content/services/catalog_audit.py`: ```python """Фаза A: конкурентные сетевые пробы, ни одной записи в БД.""" ... """Фаза B: последовательное применение решений.""" ``` Слово то же, смысл другой, вреда нет. Отсюда требование: признаком должен быть **номер** (или закавыченное имя этапа), а не слово. Буквенные `Фаза A` / `Фаза B` обязаны проходить. ## Замеры на реальном дереве jamzap/backend: **64 места в 36 файлах**, основная масса `app/admin/**` (26) и `tests/admin/**` (9). Формы: `(Этап N)`, `Этап N добавляет ...`, `наследие Этапа 3`, `live-баг Этапа 2`, `Регрессия Этапа 5`, `Definition of Done Этапа 5`, `(Фаза 2.5)`. ## Приёмка - [ ] `Этап N` / `Этапа N` / `Этапе N`, `Фаза N`, `Milestone N`, `Sprint N` ловятся - [ ] `Фаза A` / `Фаза B` (буква вместо номера) проходят — фикстура обязательна - [ ] `Этап «Имя»` ловится - [ ] Скоуп задаёт потребитель через `files:`, дефолтного нет — как у `no-broken-repo-paths` - [ ] README описывает границу «этап плана против фазы алгоритма» явно - [ ] Мутации: снятие требования номера (тогда `Фаза A` покраснеет), выпадение падежных форм Потребитель: jamzap/backend#478.
Owner

Исключения фраз прописываем не скрипте. Так как для каждой репы они могут быть свои. Так же как и добавлять ключевое слово, что мы должны поймать. Так же "Фаза A" может быть как легитимна, так и нет.

Исключения фраз прописываем не скрипте. Так как для каждой репы они могут быть свои. Так же как и добавлять ключевое слово, что мы должны поймать. Так же "Фаза A" может быть как легитимна, так и нет.
Author
Owner

Беру в работу. План с учётом замечания про конфигурируемость — ветка feature/no-plan-stage-refs.

Что настраивает потребитель, а что зашито

Зашита в скрипт только форма нарушения: ключевое слово + номер (Этап 6, Фаза 2.5, Milestone 2) либо ключевое слово + закавыченное имя (Этап «Preview Tool»). Всё остальное — аргументы в args: consumer-конфига, потому что и словарь, и легитимность зависят от репозитория:

Аргумент Что делает
--keyword СЛОВО добавляет ключевое слово к дефолтному набору (повторяемый)
--keywords a,b,c заменяет дефолтный набор целиком
--pattern REGEX добавляет произвольную запрещённую форму (повторяемый) — этим репозиторий, где Фаза A не легитимна, ловит и её: --pattern 'Фаза\s+[A-Z]\b'
--allow REGEX исключение: фрагмент, матчащий regex, нарушением не считается (повторяемый)

Дефолтный набор ключевых слов: Этап, Фаза, Milestone, Sprint, Phase, Stage. Падежи ловятся хвостом \w* (Этапа, Этапе, Этапов), регистр не важен. Списка исключений-фраз в скрипте нет вовсе — только --allow у потребителя.

Дефолт «нужен номер» остаётся дефолтом, а не законом: Фаза A проходит, пока репозиторий не попросит обратного через --pattern. Это ровно тот случай, о котором сказано в комментарии — легитимность буквенной фазы решается по месту.

Скоуп

types в манифесте не задаётся, дефолтного files нет — как у no-broken-repo-paths. Потребитель наводит хук только на код; docs/** не скоупит, поэтому расширять no-mvp-phase-language и не понадобилось.

Проверка

Фикстуры fixtures/plan_stages/: падежные формы, Фаза 2.5, Milestone/Sprint, закавыченное имя, буквенная Фаза A / Фаза B из catalog_audit.py (обязана проходить), фазы алгоритма в docstring'е, работа --keyword / --pattern / --allow. Мутации, каждая обязана уронить smoke: снятие требования номера (краснеет Фаза A), выпадение падежного хвоста, потеря --allow.

Версию проставлю при мерже — сейчас в очереди два PR (#7 и #13), и номер зависит от порядка.

Беру в работу. План с учётом замечания про конфигурируемость — ветка `feature/no-plan-stage-refs`. ## Что настраивает потребитель, а что зашито Зашита в скрипт только **форма** нарушения: ключевое слово + номер (`Этап 6`, `Фаза 2.5`, `Milestone 2`) либо ключевое слово + закавыченное имя (`Этап «Preview Tool»`). Всё остальное — аргументы в `args:` consumer-конфига, потому что и словарь, и легитимность зависят от репозитория: | Аргумент | Что делает | |---|---| | `--keyword СЛОВО` | добавляет ключевое слово к дефолтному набору (повторяемый) | | `--keywords a,b,c` | заменяет дефолтный набор целиком | | `--pattern REGEX` | добавляет произвольную запрещённую форму (повторяемый) — этим репозиторий, где `Фаза A` **не** легитимна, ловит и её: `--pattern 'Фаза\s+[A-Z]\b'` | | `--allow REGEX` | исключение: фрагмент, матчащий regex, нарушением не считается (повторяемый) | Дефолтный набор ключевых слов: `Этап`, `Фаза`, `Milestone`, `Sprint`, `Phase`, `Stage`. Падежи ловятся хвостом `\w*` (`Этапа`, `Этапе`, `Этапов`), регистр не важен. Списка исключений-фраз в скрипте нет вовсе — только `--allow` у потребителя. Дефолт «нужен номер» остаётся дефолтом, а не законом: `Фаза A` проходит, пока репозиторий не попросит обратного через `--pattern`. Это ровно тот случай, о котором сказано в комментарии — легитимность буквенной фазы решается по месту. ## Скоуп `types` в манифесте не задаётся, дефолтного `files` нет — как у `no-broken-repo-paths`. Потребитель наводит хук только на код; `docs/**` не скоупит, поэтому расширять `no-mvp-phase-language` и не понадобилось. ## Проверка Фикстуры `fixtures/plan_stages/`: падежные формы, `Фаза 2.5`, `Milestone`/`Sprint`, закавыченное имя, буквенная `Фаза A` / `Фаза B` из `catalog_audit.py` (обязана проходить), фазы алгоритма в docstring'е, работа `--keyword` / `--pattern` / `--allow`. Мутации, каждая обязана уронить smoke: снятие требования номера (краснеет `Фаза A`), выпадение падежного хвоста, потеря `--allow`. Версию проставлю при мерже — сейчас в очереди два PR ([#7](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/7) и [#13](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/13)), и номер зависит от порядка.
Author
Owner

Закрываю: хук no-plan-stage-refs в main, версия 0.16.0 (#20).

Замечание про конфигурируемость учтено буквально: списка фраз в скрипте нет вовсе, есть только форма нарушения. Всё остальное — args: у потребителя: --keyword добавляет слово, --keywords заменяет набор, --pattern добавляет свою форму (ею же ловится Фаза A там, где она нелегитимна), --allow снимает фразу, законную в этом репозитории. Дефолтный набор — Этап, Фаз, Milestone, Sprint, Phase, Stage.

Одна оговорка, которую стоит знать заранее: легитимна по умолчанию именно буквенная нумерация фаз. Фаза 1 и Stage 1 покраснеют — отличить «первая фаза алгоритма» от «первый этап плана» по тексту нельзя, и это лечится у потребителя через --allow. В README записано.

Приёмка закрыта целиком, включая мутации: снятие требования номера и необязательный разделитель красят Фаза A, выпадение падежного хвоста и снос любого слова из дефолтного набора роняют свой кейс — каждая форма проверяется отдельной фикстурой с пином фрагмента в выводе. Мисконфиг (--pattern '(', --keywords '') даёт вердикт, а не трейсбек и не молчаливый зелёный.

Потребитель — jamzap/backend#478 (64 места в 36 файлах), отдельным MR после релиза. Пин rev: v0.16.0 заработает после #16 — тегов на remote пока нет.

Закрываю: хук `no-plan-stage-refs` в `main`, версия `0.16.0` ([#20](https://git.homedevlab.ru/senokosov/pre-commit-hooks/pulls/20)). Замечание про конфигурируемость учтено буквально: списка фраз в скрипте нет вовсе, есть только форма нарушения. Всё остальное — `args:` у потребителя: `--keyword` добавляет слово, `--keywords` заменяет набор, `--pattern` добавляет свою форму (ею же ловится `Фаза A` там, где она нелегитимна), `--allow` снимает фразу, законную в этом репозитории. Дефолтный набор — `Этап`, `Фаз`, `Milestone`, `Sprint`, `Phase`, `Stage`. Одна оговорка, которую стоит знать заранее: легитимна по умолчанию именно **буквенная** нумерация фаз. `Фаза 1` и `Stage 1` покраснеют — отличить «первая фаза алгоритма» от «первый этап плана» по тексту нельзя, и это лечится у потребителя через `--allow`. В README записано. Приёмка закрыта целиком, включая мутации: снятие требования номера и необязательный разделитель красят `Фаза A`, выпадение падежного хвоста и снос любого слова из дефолтного набора роняют свой кейс — каждая форма проверяется отдельной фикстурой с пином фрагмента в выводе. Мисконфиг (`--pattern '('`, `--keywords ''`) даёт вердикт, а не трейсбек и не молчаливый зелёный. Потребитель — jamzap/backend#478 (64 места в 36 файлах), отдельным MR после релиза. Пин `rev: v0.16.0` заработает после [#16](https://git.homedevlab.ru/senokosov/pre-commit-hooks/issues/16) — тегов на remote пока нет.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
senokosov/pre-commit-hooks#14
No description provided.