feat(no-plan-stage-refs): запрет ссылок на номера этапов плана в коде #20

Merged
claude-secretary merged 2 commits from feature/no-plan-stage-refs into main 2026-08-27 12:28:27 +07:00

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

Что ловит

Ключевое слово + номер (Этап 6, Этапа 3, Фаза 2.5, Milestone 2, Sprint 4, Этап №7) либо ключевое слово + закавыченное имя (Этап «Preview Tool»).

Чего не ловит — это и есть суть правила

Признак нарушения — номер, а не слово. «Фаза» легитимна, когда описывает фазы алгоритма:

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

Слово то же, смысл другой, вреда нет — такие строки обязаны проходить, иначе хук начнёт требовать переписывания корректной прозы. Разделитель между словом и номером обязателен, поэтому Stage2Runner и stage_2 тоже мимо.

Конфигурация — у потребителя, не в скрипте

По замечанию @volody в issue: и словарь, и исключения зависят от репозитория, и «Фаза A» где-то легитимна, а где-то нет. Поэтому в скрипте зашита только форма нарушения, а списка фраз нет вовсе:

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

Дефолтный набор — Этап, Фаза, Milestone, Sprint, Phase, Stage; падежи ловятся хвостом словоформы, регистр не важен.

Почему отдельный id, а не расширение no-mvp-phase-language

Скоуп того хука у потребителей включает docs/**, а в плановом документе перечень этапов — предмет документа. Этот хук наводят только на код: дефолтного files: у него нет, как у no-broken-repo-paths.

Скан построчный по всему файлу

Не только по комментариям: «(Этап 6)» одинаково вредна в комментарии, в docstring'е и в тексте, который код отдаёт наружу (verbose_name, description). Форма правила достаточно специфична, чтобы код не флагался — идентификаторы отсекает обязательный разделитель.

Файлы

  • scripts/check_no_plan_stage_refs.py + console-script в pyproject.toml
  • .pre-commit-hooks.yaml: id: no-plan-stage-refs
  • fixtures/plan_stages/ (9 фикстур) + 14 кейсов в fixtures/run_smoke.sh
  • README.md + examples/.pre-commit-config.example.yaml

Проверка

  • bash fixtures/run_smoke.sh — зелёный; кейсы покрывают падежи, Фаза 2.5, английский словарь, закавыченное имя, буквенную фазу (проходит по дефолту и падает под --pattern), работу каждого из четырёх аргументов и номер строки в выводе.
  • Мутации, каждая роняет smoke: снятие требования номера и необязательный разделитель (обе красят Фаза A в good.py), выпадение падежного хвоста, игнор --allow.
  • Инвокация через настоящий pre-commit с args: проверена на временном репозитории: --keyword=Волна ловит «Волна 3», --allow=воронк снимает «Этап 2 воронки продаж», «Этап 6» падает дефолтным набором.

Приёмка из issue

  • Этап N / Этапа N / Этапе N, Фаза N, Milestone N, Sprint N ловятся
  • Фаза A / Фаза B проходят — фикстура letter_phase.py плюс good.py
  • Этап «Имя» ловится
  • Скоуп задаёт потребитель через files:, дефолтного нет
  • README описывает границу «этап плана против фазы алгоритма» явно
  • Мутации: снятие требования номера, выпадение падежных форм

Версия

0.15.00.16.0: новый хук, minor. Напоминание: тегов после v0.9.1 на remote нет — #16.

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

Закрывает [#14](https://git.homedevlab.ru/senokosov/pre-commit-hooks/issues/14). Заведено по замечанию на ревью jamzap/backend!462: «(Этап 6) — и подобные слова не должны быть у нас в коде». ## Что ловит Ключевое слово + номер (`Этап 6`, `Этапа 3`, `Фаза 2.5`, `Milestone 2`, `Sprint 4`, `Этап №7`) либо ключевое слово + закавыченное имя (`Этап «Preview Tool»`). ## Чего не ловит — это и есть суть правила **Признак нарушения — номер, а не слово.** «Фаза» легитимна, когда описывает фазы алгоритма: ```python """Фаза A: конкурентные сетевые пробы, ни одной записи в БД.""" """Фаза B: последовательное применение решений.""" ``` Слово то же, смысл другой, вреда нет — такие строки обязаны проходить, иначе хук начнёт требовать переписывания корректной прозы. Разделитель между словом и номером обязателен, поэтому `Stage2Runner` и `stage_2` тоже мимо. ## Конфигурация — у потребителя, не в скрипте По замечанию @volody в issue: и словарь, и исключения зависят от репозитория, и «Фаза A» где-то легитимна, а где-то нет. Поэтому в скрипте зашита только **форма** нарушения, а списка фраз нет вовсе: | Аргумент | Что делает | |---|---| | `--keyword СЛОВО` | добавляет ключевое слово к дефолтному набору (повторяемый) | | `--keywords a,b,c` | заменяет дефолтный набор целиком | | `--pattern REGEX` | добавляет свою запрещённую форму (повторяемый) — ею репозиторий, где буквенная фаза **не** легитимна, ловит и её: `--pattern 'Фаза\s+[A-Z]\b'` | | `--allow REGEX` | строка, матчащая regex, нарушением не считается (повторяемый) | Дефолтный набор — `Этап`, `Фаза`, `Milestone`, `Sprint`, `Phase`, `Stage`; падежи ловятся хвостом словоформы, регистр не важен. ## Почему отдельный id, а не расширение `no-mvp-phase-language` Скоуп того хука у потребителей включает `docs/**`, а в плановом документе перечень этапов — предмет документа. Этот хук наводят только на код: дефолтного `files:` у него нет, как у `no-broken-repo-paths`. ## Скан построчный по всему файлу Не только по комментариям: «(Этап 6)» одинаково вредна в комментарии, в docstring'е и в тексте, который код отдаёт наружу (`verbose_name`, `description`). Форма правила достаточно специфична, чтобы код не флагался — идентификаторы отсекает обязательный разделитель. ## Файлы - `scripts/check_no_plan_stage_refs.py` + console-script в `pyproject.toml` - `.pre-commit-hooks.yaml`: `id: no-plan-stage-refs` - `fixtures/plan_stages/` (9 фикстур) + 14 кейсов в `fixtures/run_smoke.sh` - `README.md` + `examples/.pre-commit-config.example.yaml` ## Проверка - `bash fixtures/run_smoke.sh` — зелёный; кейсы покрывают падежи, `Фаза 2.5`, английский словарь, закавыченное имя, буквенную фазу (проходит по дефолту и падает под `--pattern`), работу каждого из четырёх аргументов и номер строки в выводе. - Мутации, каждая роняет smoke: снятие требования номера и необязательный разделитель (обе красят `Фаза A` в `good.py`), выпадение падежного хвоста, игнор `--allow`. - Инвокация через **настоящий** `pre-commit` с `args:` проверена на временном репозитории: `--keyword=Волна` ловит «Волна 3», `--allow=воронк` снимает «Этап 2 воронки продаж», «Этап 6» падает дефолтным набором. ## Приёмка из issue - [x] `Этап N` / `Этапа N` / `Этапе N`, `Фаза N`, `Milestone N`, `Sprint N` ловятся - [x] `Фаза A` / `Фаза B` проходят — фикстура `letter_phase.py` плюс `good.py` - [x] `Этап «Имя»` ловится - [x] Скоуп задаёт потребитель через `files:`, дефолтного нет - [x] README описывает границу «этап плана против фазы алгоритма» явно - [x] Мутации: снятие требования номера, выпадение падежных форм ## Версия `0.15.0` → **`0.16.0`**: новый хук, minor. Напоминание: тегов после `v0.9.1` на remote нет — [#16](https://git.homedevlab.ru/senokosov/pre-commit-hooks/issues/16). Потребитель — jamzap/backend#478 (64 места в 36 файлах), отдельным MR после релиза.
feat(no-plan-stage-refs): запрет ссылок на номера этапов плана в коде
All checks were successful
ci / smoke (push) Successful in 15s
ci / smoke (pull_request) Successful in 15s
ad662c11a6
Заведено по замечанию на ревью jamzap/backend!462 (hooks#14). «(Этап 6)»
— back-reference на план разработки: из номера этапа не следует ничего о
поведении кода, а через полгода план закрыт и проверить фразу нельзя.
Особенно вредно в файлах стартового контекста агента: «На Этапе 3 реально
используется только list_display» читается как описание текущего
состояния.

Признак нарушения — номер (или закавыченное имя этапа) при ключевом
слове, а не само слово: «Фаза A» / «Фаза B» описывают фазы алгоритма и
обязаны проходить. Разделитель между словом и номером обязателен —
Stage2Runner и stage_2 под правило не попадают.

Словарь и исключения задаёт консьюмер через args (--keyword/--keywords,
--pattern, --allow), а не скрипт: они у каждого репозитория свои, и
легитимность буквенной фазы тоже решается по месту. Списка фраз в коде
нет вовсе.

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

Проверка: 14 smoke-кейсов, включая работу каждого из четырёх аргументов;
мутации — снятие требования номера, необязательный разделитель (обе
красят «Фаза A»), выпадение падежного хвоста, игнор --allow. Инвокация
через настоящий pre-commit с args проверена на временном репозитории.

Версия 0.15.0 → 0.16.0: новый хук.

Closes #14

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fix(no-plan-stage-refs): ниты ревью — смоук держит словарь, мисконфиг даёт вердикт
All checks were successful
ci / smoke (pull_request) Successful in 15s
ci / smoke (push) Successful in 16s
28ef0dfaa0
Блокеры:

- Смоук был слеп: фикстуры паковали по нескольку форм в файл с одним
  assert'ом по exit-коду, и 11 мутаций из 19 выживали — включая снос
  любого слова из дефолтного набора. Теперь одна форма — одна фикстура —
  один кейс с пином фрагмента через run_case_out. Проверено: удаление
  Sprint / Milestone / Фаз / Phase+Stage, разделитель без № и без тире,
  номер без подпункта, потеря IGNORECASE, снятие границы слова и
  расширение хвоста до \w* — каждая мутация роняет свой кейс.
- Кривой regex в --pattern/--allow ронял re.error стектрейсом внутри
  pre-commit: консьюмер видел «сломан хук», а не «опечатка в конфиге».
- `--keywords ''` (и опечатка `--keywords=`) обнуляла словарь, и хук
  молча пропускал всё — вечнозелёный гейт, который в конфиге выглядит
  работающим. Теперь оба случая — FAIL с объяснением.

Ниты:

- Дефолтное слово «Фаз» вместо «Фаза»: у него основа короче словарной
  формы, и падежи («после Фазы 2», «в Фазе 3») хвостом не ловились.
- Типографские кавычки “ ” ‘ ’ в закавыченной форме.
- --help включён (был add_help=False без причины), докстринг стал
  текстом справки.
- README: нумерованная фаза алгоритма («Фаза 1», «Stage 1») дефолтным
  набором краснеет — легитимна только буквенная; --allow снимает строку
  целиком; поведение при мисконфиге. Счётчик хуков поправлен на 14.
- Пример конфига больше не везёт живые фикстурные значения — они
  закомментированы как примерные.
- Комментарий про ограничение хвоста объяснял его неверной причиной:
  `Stage2Runner` отсекает разделитель, а не длина хвоста.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
claude-secretary deleted branch feature/no-plan-stage-refs 2026-08-27 12:28:28 +07:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
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!20
No description provided.