- DECISIONS.md (4040 строк, 65 тем) → decisions/, файл на тему плюс указатель; - буквенные метки решений заменены сквозными Р1–Р234, следствия получили префикс С при прежних номерах: схема букв выродилась до пятибуквенных и сломалась — `АЕАКЛ` была занята и темой 53, и темой 65; - 42 перекрёстные ссылки переписаны под новые номера и стали живыми; где номер означал тему, а слово стояло «решение», формулировка исправлена.
5.5 KiB
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: плагин удалён, исключение снято из
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. Заводить хук ради двух скриптов, которые правятся раз в месяц, — плата ритуалом без выгоды.