130 lines
12 KiB
Bash
Executable file
130 lines
12 KiB
Bash
Executable file
#!/bin/sh
|
||
# Зонный pre-commit фрагмент docs/. Ставится тем же диспетчером .git/hooks/pre-commit, что и
|
||
# фрагмент фронта (диспетчер перебирает */scripts/githooks/pre-commit).
|
||
#
|
||
# ┌─ ЧТО ЭТО ДЕЛАЕТ И ЧЕГО НЕ ДЕЛАЕТ — читать до того, как ругаться на него ───────────────────┐
|
||
# │ ДЕЛАЕТ: при коммите, задевающем ЛЮБОЙ док, проверяет file:line-якоря живых доков; при │
|
||
# │ коммите с D-логом или PROGRESS дополнительно сверяет три носителя номера головы и │
|
||
# │ производные числа доков (счёт очереди, зон, вес открытых строк регистра платформы). │
|
||
# │ НЕ ДЕЛАЕТ: не блокирует коммит НИКОГДА — только печатает предупреждение и выходит 0. │
|
||
# │ Не трогает содержимое, не правит файлы, не лезет в сеть, не запускает тесты. │
|
||
# │ ЕСЛИ МЕШАЕТ: `git commit --no-verify` обходит всю цепочку; выключить только этот фрагмент — │
|
||
# │ `chmod -x docs/scripts/githooks/pre-commit`. Ни то, ни другое ничего не ломает. │
|
||
# │ ⚠ НО: обход — это ДЕЙСТВИЕ, а не мнение. Считаешь, что хук неправ, шумит, мешает или ловит │
|
||
# │ не то — НАПИШИ ВЛАДЕЛЬЦУ (он передаст оркестратору), а не обходи молча и не подпирай │
|
||
# │ хаком/воркэраундом. Тихая деградация гейта не оставляет следа: следующая сессия получит │
|
||
# │ его же в прежнем виде и потратит то же время, а мы не узнаем, что он врал. Жалоба на │
|
||
# │ гейт — полезный сигнал и повод его починить; молчаливый обход — потеря сигнала. │
|
||
# │ ПОЧЕМУ ВООБЩЕ: голова CURRENT-STATE отставала ТРИЖДЫ у трёх РАЗНЫХ оркестраторов │
|
||
# │ (D39.81 · D39.83 · D39.112 п.5б). Одна ошибка у независимых сессий на одном поле — │
|
||
# │ свойство поля, а не сессий, поэтому проверка механическая, а не нормой прозой. │
|
||
# │ ПОЧЕМУ ПРЕДУПРЕЖДАЕТ, А НЕ БЛОКИРУЕТ: фрагмент общий, он сработает у сессии, которой я не │
|
||
# │ писал промт. Останавливать чужой коммит — решение владельца, не моё (оркестратор №15). │
|
||
# └────────────────────────────────────────────────────────────────────────────────────────────┘
|
||
#
|
||
# Ложные тревоги, которых здесь СОЗНАТЕЛЬНО избегают, — это важнее, чем полнота: хук, который
|
||
# врёт, учат игнорировать, и тогда он хуже отсутствующего.
|
||
# · числа считаются по СОДЕРЖИМОМУ КОММИТА (`--from-index`), а не по рабочему дереву, иначе
|
||
# частичный стейдж (своя правка в общем файле) давал бы ругань на то, что в коммит не поедет;
|
||
# · голова сверяется ТОЛЬКО когда в коммите есть D-лог, то есть когда идёт ратификация — норма
|
||
# требует бампать голову в том же коммите, что аппенд ноты, и вот этот случай и ловится;
|
||
# коммит, который правит PROGRESS по другому поводу, про голову не спрашивают;
|
||
# · линт якорей (D39.148) читает СКАНИРУЕМЫЕ доки по содержимому коммита — иначе незакоммиченное
|
||
# дерево живого полигона в docs/experiments/ давало бы ругань на чужой WIP; ЦЕЛИ якорей берутся
|
||
# из дерева осознанно: код не входит в docs-коммит, и его истина — дерево.
|
||
|
||
staged=$(git diff --cached --name-only)
|
||
[ -z "$staged" ] && exit 0
|
||
|
||
dlog='docs/architecture/05-decisions-log.md'
|
||
prog='docs/PROGRESS.md'
|
||
|
||
# ⚠ БЫСТРОГО ВЫХОДА ПО «коммит не задевает docs/» БОЛЬШЕ НЕТ (21.08, разбор с внешним ревьюером).
|
||
# Он ампутировал ЦЕЛЕВУЮ сторону дельта-сверки якорей: якорь протухает, когда двигается ЦЕЛЬ, а цели
|
||
# у нас в основном КОД — то есть главный двигатель протухания приезжал ровно тем коммитом, на котором
|
||
# хук молча выходил. Плюс расширение области линта на platform/docs и frontend/docs в старый триггер
|
||
# не попадало вовсе. Цена отмены нулевая: линт ~0.2 с и warn-only; блок производных ЧИСЕЛ по-прежнему
|
||
# гейчен составом коммита ниже (он дорогой и осмыслен только при ратификации).
|
||
|
||
warn=0
|
||
|
||
# 1) Голова. Только при ратификации (D-лог в коммите).
|
||
if printf '%s\n' "$staged" | grep -qx "$dlog"; then
|
||
after() {
|
||
if printf '%s\n' "$staged" | grep -qx "$1"; then git show ":$1" 2>/dev/null; else git show "HEAD:$1" 2>/dev/null; fi
|
||
}
|
||
note=$(after "$dlog" | grep -oE '^## D39\.[0-9]+' | tail -1 | sed 's/^## //')
|
||
banner=$(after "$dlog" | head -1 | grep -oE 'D1–D39\.[0-9]+' | sed 's/^D1–//')
|
||
claimed=$(after "$prog" | head -8 | grep -oE 'голова D39\.[0-9]+' | head -1 | sed 's/^голова //')
|
||
|
||
if [ -n "$note" ] && [ -n "$claimed" ] && [ "$note" != "$claimed" ]; then
|
||
echo "pre-commit ⚠ docs: голова разошлась — последняя нота D-лога $note, CURRENT-STATE говорит $claimed." >&2
|
||
echo " Норма: голова бампается в ТОМ ЖЕ коммите, что аппенд ноты (промт оркестратора)." >&2
|
||
warn=1
|
||
fi
|
||
if [ -n "$note" ] && [ -n "$banner" ] && [ "$note" != "$banner" ]; then
|
||
echo "pre-commit ⚠ docs: шапка-диапазон D-лога $banner при последней ноте $note." >&2
|
||
warn=1
|
||
fi
|
||
fi
|
||
|
||
# 2) Производные числа — по содержимому коммита, и ТОЛЬКО когда в коммите носители этих чисел
|
||
# (гейтовка прежняя, D39.112). Ненайденный литерал скрипт тоже считает расхождением: проверка,
|
||
# молча перестающая проверять, хуже отсутствующей.
|
||
if printf '%s\n' "$staged" | grep -qE "^($dlog|$prog)$"; then
|
||
if command -v python3 >/dev/null 2>&1 && [ -f docs/scripts/counts.py ]; then
|
||
if ! out=$(python3 docs/scripts/counts.py --check --from-index 2>&1); then
|
||
echo "pre-commit ⚠ docs: производные числа разошлись с пере-счётом:" >&2
|
||
# Печатаем строки-находки, а если их нет (скрипт УПАЛ) — весь вывод: предупреждение с пустым
|
||
# телом нечитаемо и учит игнорировать хук.
|
||
printf '%s\n' "$out" | grep '✗' >&2 || printf '%s\n' "$out" | tail -5 >&2
|
||
warn=1
|
||
fi
|
||
fi
|
||
fi
|
||
|
||
# 3) Якоря file:line живых доков (D39.148 — ревизия D39.126 «--lint только руками»). Сканируемые
|
||
# доки читаются по содержимому коммита, поэтому чужой незакоммиченный WIP невидим; ЦЕЛИ якорей
|
||
# читаются из дерева — код не часть docs-коммита. ~0.2 с, warn-only.
|
||
if command -v python3 >/dev/null 2>&1 && [ -f docs/scripts/counts.py ]; then
|
||
if ! out=$(python3 docs/scripts/counts.py --lint --from-index 2>&1); then
|
||
echo "pre-commit ⚠ docs: мёртвые или переросшие file:line-якоря:" >&2
|
||
# Печатаем строки-находки, а если их нет (скрипт УПАЛ) — весь вывод: предупреждение с пустым
|
||
# телом нечитаемо и учит игнорировать хук.
|
||
printf '%s\n' "$out" | grep '✗' >&2 || printf '%s\n' "$out" | tail -5 >&2
|
||
warn=1
|
||
fi
|
||
fi
|
||
|
||
# Untracked-под-коммитом: pathspec-форма коммита МОЛЧА роняет неотслеживаемые файлы.
|
||
# `git commit -- <пути>` обходит индекс и берёт рабочее дерево только для ОТСЛЕЖИВАЕМЫХ путей,
|
||
# поэтому новый файл, даже застейдженный, в коммит не попадает. Ровно так 31.08 уехал коммит, чей
|
||
# `migrations.sha256` уже ссылался на три миграции, оставшиеся снаружи; на свежем клоне схема
|
||
# оказалась бы короче на три шага, а симптом — «почему схема не та» у следующего человека.
|
||
# ⚠ Шум низок ПО ПОСТРОЕНИЮ: gitignored файлы (books/, .env, песочницы) в `--porcelain` не видны,
|
||
# поэтому сюда попадает только по-настоящему новое и неигнорируемое — то есть ровно тот случай,
|
||
# ради которого предупреждение и стоит.
|
||
untracked=$(git status --porcelain --untracked-files=normal | sed -n 's/^?? //p')
|
||
if [ -n "$untracked" ]; then
|
||
dirs=$(printf '%s\n' "$staged" | sed 's#/[^/]*$##' | sort -u)
|
||
hits=''
|
||
for d in $dirs; do
|
||
h=$(printf '%s\n' "$untracked" | grep -E "^${d}/[^/]*$" || true)
|
||
[ -n "$h" ] && hits="${hits}${h}
|
||
"
|
||
done
|
||
if [ -n "$hits" ]; then
|
||
echo "pre-commit ⚠ неотслеживаемые файлы в каталогах ЭТОГО коммита — pathspec-форма их РОНЯЕТ:" >&2
|
||
printf '%s' "$hits" | sed 's/^/ ?? /' >&2
|
||
echo ' Нужны в коммите? git add -- <путь>, затем проверь состав: git show --name-only' >&2
|
||
warn=1
|
||
fi
|
||
fi
|
||
|
||
if [ "$warn" -eq 1 ]; then
|
||
echo "pre-commit: это ПРЕДУПРЕЖДЕНИЕ — коммит не остановлен (обход: --no-verify)." >&2
|
||
echo " Считаешь, что хук неправ или мешает? Напиши ВЛАДЕЛЬЦУ — не обходи молча и не подпирай хаком." >&2
|
||
echo " Предупреждение про ЧУЖОЙ док (не твоя зона)? Чинить его НЕ надо и нельзя — сообщи владельцу" >&2
|
||
echo " и коммить дальше: якорь в чужом доке уехал из-за твоей правки цели, чинит его владелец зоны." >&2
|
||
fi
|
||
exit 0
|