From 5067bc204837d5ee64c959f9b3339d80e5831fa9 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Fri, 7 Aug 2026 09:24:37 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B3=D0=B5=D0=B9=D1=82=20=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BC=D0=B8=D1=82=D0=B0:=20=D0=BF=D1=8F=D1=82=D1=8C=20=D0=BC?= =?UTF-8?q?=D0=B0=D1=88=D0=B8=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BE=D0=BA=20=D0=B2=D1=81=D1=82=D0=B0=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=B2=20pre-commit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проверки существовали и запускались руками — то есть тогда, когда о них вспоминали. Каждая из них ловит ровно тот класс поломок, который не виден при чтении: фронтматтер разбирает загрузчик скиллов, а не человек; копия правила расходится с домом молча; диаграмма mermaid выглядит правдоподобно и падает на рендере; ruff и pyrefly стерегут ноль внешних зависимостей, без которого tasks.py и docs.py перестают работать в чужом проекте. Полагаться на память в таком наборе — значит узнавать о поломке от того, кто скачал плагин. Ставится lefthook (конфиг в lefthook.yml, `lefthook install` один раз на клон), пять задач в parallel. Glob разводит две половины: правка одних скриптов не платит за рендер диаграмм (~15 секунд), правка документов не гоняет линтеры. Внутри своей половины проверяется весь репозиторий, а не изменённые файлы — и расхождение копии, и находка ruff в соседнем файле это ровно тот случай, когда правка сломала не себя. Два свойства названы в README и в шапке конфига, чтобы не выяснялись отладкой. Первое: судится рабочее дерево, а не индекс — скрипты написаны как обход репозитория и про git add не знают, поэтому частичный коммит при грязном дереве проверяется по тому, что на диске. Второе: обход разовый — LEFTHOOK=0, и он законен ровно для случая, когда найденное нечем чинить прямо сейчас. Заодно поправлена строка README про диаграммы: «поэтому он не в гейте, а в руках того, кто правит диаграмму» — с этой правкой она перестала быть верной. Co-Authored-By: Claude Opus 5 (1M context) --- README.md | 39 +++++++++++++++++++++++++++++++++++++-- lefthook.yml | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 lefthook.yml diff --git a/README.md b/README.md index 6c012cd..6af5df7 100644 --- a/README.md +++ b/README.md @@ -228,6 +228,7 @@ claude plugin uninstall <плагин>@av-dev-skills --scope project /agents/ charter'ы сабагентов scripts/ проверки репозитория: копии, диаграммы, фронтматтеры pyproject.toml линтеры скриптов, только для этого репозитория +lefthook.yml гейт коммита: проверки документов ``` ## Проверка скриптов @@ -315,11 +316,45 @@ uv run python scripts/diagrams.py # 0 рендерятся, 1 нет, 3 нет ``` Рендерит `mmdc` с PATH или `npx --yes @mermaid-js/mermaid-cli`; ни того ни -другого нет — код 3, а не молчаливый успех. Прогон занимает секунды на блок, -поэтому он не в гейте, а в руках того, кто правит диаграмму. +другого нет — код 3, а не молчаливый успех. Прогон занимает секунды на блок — +это самая дорогая из трёх проверок, и в гейте коммита она стоит **под glob по +markdown**: правка одних скриптов проходит мгновенно. Чего проверка **не** ловит — расхождение диаграммы с прозой вокруг неё. Дословного соответствия между текстом и графом нет, сличать нечего, и держится это правилом старшинства, записанным рядом с каждой диаграммой: **в конвейере ревью старший граф** (он и есть алгоритм планировщика, проза его объясняет), **в остальных местах старшая проза** (диаграмма там сводка). + +## Гейт коммита + +Все пять проверок стоят в `pre-commit` через [lefthook](https://lefthook.dev) — +конфиг в [lefthook.yml](lefthook.yml), ставится один раз на клон: + +``` +lefthook install # пишет .git/hooks/pre-commit +lefthook run pre-commit # прогнать руками, не коммитя +``` + +| Проверка | Когда идёт | Сколько | +| --- | --- | --- | +| фронтматтеры | правка `*.md` | миллисекунды | +| копии правил | правка `*.md` | миллисекунды | +| диаграммы | правка `*.md` | ~15 с на весь репозиторий | +| `ruff check .` | правка `*.py` | доли секунды | +| `pyrefly check` | правка `*.py` | доли секунды | + +Glob разводит две половины: коммит, трогающий одни скрипты, не платит за рендер +диаграмм, а коммит в документы не гоняет линтеры. Внутри своей половины +проверяется **весь репозиторий**, а не изменённые файлы: и расхождение копии, и +находка ruff в соседнем файле — это ровно тот случай, когда правка сломала не +себя. + +Два свойства, о которых стоит знать заранее: + +- **судится рабочее дерево, а не индекс.** Скрипты обходят репозиторий целиком + и про `git add` не знают: частичный коммит при грязном дереве проверяется по + тому, что на диске. Это цена того, что проверки — обход, а не фильтр файлов, и + она принята: расхождение копий и битая диаграмма ловятся именно обходом; +- **обход разовый — `LEFTHOOK=0 git commit …`.** Он законен ровно для случая, + когда найденное нечем чинить прямо сейчас; молча пропущенная проверка — нет. diff --git a/lefthook.yml b/lefthook.yml new file mode 100644 index 0000000..07d63f7 --- /dev/null +++ b/lefthook.yml @@ -0,0 +1,48 @@ +# Гейт коммита: всё, что в этом репозитории проверяется машиной. +# +# Документы — то, что читает не человек, а машина, и чья поломка **не видна при +# чтении**: фронтматтер (его разбирает загрузчик скиллов), помеченная копия +# правила (расходится молча) и диаграмма mermaid (текст правдоподобен, рендер +# падает). Скрипты — ruff и pyrefly: они же стерегут ноль внешних зависимостей, +# без которого tasks.py и docs.py перестают работать в чужом проекте. +# Всё остальное (проза, устройство, язык) — предмет ревью, а не хука. +# +# Скрипты читают **рабочее дерево целиком**, а не индекс: частичный коммит при +# грязном дереве судится по тому, что на диске. Это осознанно — они и написаны +# как обход репозитория, а не как фильтр по файлам. +# +# Ставится `lefthook install` (см. README, «Гейт коммита»). Обойти разово — +# `LEFTHOOK=0 git commit …`; обход законен ровно для того случая, когда чинить +# найденное нечем прямо сейчас. + +pre-commit: + parallel: true + jobs: + - name: фронтматтеры + glob: "*.md" + run: python3 scripts/frontmatter.py + + - name: копии правил + glob: "*.md" + run: python3 scripts/copies.py + + # Секунды, а не миллисекунды: рендер идёт настоящим mermaid-cli. Поэтому + # glob — правка одних только скриптов проходит гейт мгновенно. Нет ни + # `mmdc`, ни `npx` — код 3, коммит отказан: «не проверено» здесь не то же + # самое, что «проверено и сошлось». + - name: диаграммы + glob: "*.md" + run: python3 scripts/diagrams.py + + # Скрипты — под своим glob и через uv: версии линтеров прибиты точно, и + # `uv run` берёт именно их, а не то, что оказалось в PATH. Обе проверки + # укладываются в доли секунды на весь репозиторий, поэтому проверяется он + # целиком, а не изменённые файлы: находка в чужом файле здесь означает, что + # правка сломала соседа. + - name: ruff + glob: "*.py" + run: uv run ruff check . + + - name: pyrefly + glob: "*.py" + run: uv run pyrefly check