# 9. Линтеры скриптов (2026-08-03) ## Что было Три скрипта на python, 3600 строк, ни одной проверки. `tasks.py` — 2450 строк, которые ходят по файловой системе, переименовывают и удаляют файлы задач. Требование к самим скриптам прежнее и не обсуждается: **голый `python3` 3.12, ноль внешних зависимостей** — они лежат рядом со скиллами и запускаются в чужом проекте, где ничего ставить нельзя. ## Решено **Р30. `pyproject.toml` в корне `dev-skills`, зависимости через `uv`.** Файл живёт только здесь и не уезжает никуда: он держит **линтеры**, а не зависимости скриптов. Скрипты остаются запускаемыми любым `python3` — это проверено прогоном всех операций через `/usr/bin/python3`, а не через `.venv`. **Р31. Ноль зависимостей охраняется двумя способами, и главный — второй.** `banned-api` у ruff ловит частые соблазны по имени (`requests`, `yaml`, `pydantic`, `click`, `rich`) — список заведомо неполный. Настоящий страж — pyrefly: в окружении нет ничего, кроме линтеров, поэтому **любой** сторонний импорт у него не разрешается. Первый способ даёт понятное сообщение, второй — полноту. **Р32. Версии линтеров прибиты точно** (`ruff==0.16.1`, `pyrefly==1.2.0`) плюс `uv.lock` в git. Обновление линтера меняет набор находок, а находки правятся руками в скриптах, которые уезжают в чужие проекты. Обновление обязано быть отдельной осознанной правкой, а не побочным эффектом `uv sync`. **Р33. `RUF001`–`RUF003` выключены.** Весь текст скриптов русский: сообщения, докстроки, комментарии. «Похожая на латиницу кириллица» здесь норма, а не опечатка, и три этих правила давали **311 срабатываний из 338** — шум, в котором тонут остальные 27. **Р34. `av-dev-backlog` исключён из проверки.** *(исчерпано [темой 30](30-av-dev-backlog-removed.md): плагин удалён, исключение снято из `pyproject.toml` и `copies.py`.)* Плагин помечен устаревшим и живёт до перевода последнего проекта, после чего удаляется целиком. Шесть его находок косметические (`os.replace`, `l` как имя), а правка замороженного кода без тестов — риск без выгоды. Исключение уходит вместе с плагином. **Р35. Голый `except Exception` разрешён только помеченный.** Правило `BLE` включено, а два места последнего рубежа (`main` обоих скриптов, код выхода 4 по словарю) несут `# noqa: BLE001` с причиной. Так третий такой except не появляется молча. ## Что из этого следует **С40. Найдено и починено 27 находок ruff и 14 pyrefly.** Содержательных две: мёртвая переменная `ques` в `check` (вычислялась и не использовалась — вопросы проверяет `questions_open`) и два места в `check --fix`, где `find_entry_index` может вернуть `None`, а результат идёт прямо в `list.pop` и в `range`. Оба сегодня недостижимы, и недостижимость держалась на рассуждении о вызывающем коде, а не на проверке. *Поправлено по ревью:* там стоит `raise`, а не `continue`. Тихий пропуск превратил бы сломанный инвариант в отчёт «индексы согласованы» — то есть в враньё; громкий отказ кодом 4 честнее. **С41. `os` из `tasks.py` ушёл целиком.** `os.replace` → `Path.replace`, `os.path.basename` → `Path.name`; импорт стал не нужен. **С42. `fail()` в `docs.py` объявлен `NoReturn`.** Без этого `read_config` выглядел как возвращающий неинициализированное значение — и это ровно то, что читатель кода тоже не мог знать наверняка. **С43. Проверка не входит ни в один гейт.** CI у репозитория нет, хука нет; запускается руками командой из README. Заводить хук ради двух скриптов, которые правятся раз в месяц, — плата ритуалом без выгоды.