Files
portal/lefthook.yml
T
Дмитрий 821f966071 fix статанализ: сторож встал ПЕРЕД анализатором и объясняет молчание
Мина из промта подтвердилась, но диагноз вчерашней смены оказался неполным,
а способ её доказательства - неверным. Разбор по замерам.

Что на самом деле убивает статанализ молча - код возврата 1 и ни строки
ни в обычном выводе, ни в канале ошибок:

1. нет файла, перечисленного в scanFiles. Подсказчик _ide_helper_models.php
   собирается на месте, в git его нет, на чистой копии проекта его не будет.
2. метка BOM в начале phpstan.neon. Её ставит Set-Content -Encoding utf8
   в Windows PowerShell 5.1 - то есть любой, кто правит настройку скриптом,
   а не редактором, убивает анализатор. Вчерашнее "доказательство" мины было
   именно этим: временную настройку записали с меткой, молчание списали на
   отсутствующий файл. Причина была другая, вывод совпал случайно.

Третий случай молча не падает, а врёт убедительнее: подсказчик, собранный
без ключа -M, объявляет ПУСТЫЕ копии поверх настоящих моделей. И четвёртый:
подсказчик просто устарел - не знает моделей, появившихся после сборки.
Замер: старый файл знал 52 модели, свежий - 79. Ровно отсюда расхождение
"1865 замечаний в одной рабочей папке против 42 в другой" на одном коде.

Сделано:
- tools/storozh-statanaliza.mjs - сторож на все четыре случая. Называет
  причину человеческим языком и команду починки. Код возврата 2.
- tools/storozh-statanaliza.test.mjs - 13 тестов, писались до сторожа.
- сторож встроен в composer stan и в хук перед larastan через && , так что
  при его срабатывании phpstan не запускается вовсе и не путает человека.
- fail_text у larastan переписан: сначала прочитай, не сказал ли сторож,
  что ошибок в твоём коде нет.
- composer setup и composer ide-helper теперь собирают подсказчик правильным
  ключом. Раньше его нельзя было получить НИ ОДНОЙ описанной командой.
- из baseline убрана устаревшая запись про saas_transactions.credit_target:
  столбец настоящий, миграция от 25.07, свежий подсказчик его знает.
- reportUnmatchedIgnoredErrors выключен с объяснением: записи baseline не
  должны краснеть оттого, что на этой машине подсказчик полнее или беднее.

Датчик свежести намеренно по СОДЕРЖИМОМУ, а не по времени файла. Сначала
сделал по времени - и он тут же закричал "устарел" на неизменившемся коде,
потому что время правки обновляет любая операция git. Заменено на сверку
списка моделей со списком классов-спутников.

Приёмка вырезанием, каждая половина проверена:
- убрал подсказчик -> сторож назвал причину, код 2, phpstan не запускался;
- поставил метку BOM -> назвал её и способ записи без метки;
- подложил подсказчик без -M -> назвал подмену моделей;
- положил старый файл на 52 модели -> перечислил 27 недостающих поимённо;
- вернул всё -> код 0, статанализ ноль замечаний;
- подложил настоящие поломки в tests -> обе пойманы, значит выключение
  reportUnmatchedIgnoredErrors анализатор не ослепило.

Побочно найдено, НЕ закрыто: столбца credit_target нет в db/schema.sql,
хотя миграции на него есть и в CHANGELOG_schema он упомянут. Канон схемы
разошёлся с миграциями - отдельная работа.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 17:02:44 +03:00

307 lines
20 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# lefthook.yml — Лидерра (фаза 0)
# =============================================================================
# Документация: https://lefthook.dev
# Установка хуков: npx lefthook install
# Удаление хуков: npx lefthook uninstall
# Ручной pre-commit: npx lefthook run pre-commit
# Пропуск (один раз): LEFTHOOK=0 git commit -m "..."
#
# Принципы (Прил. Н §1):
# - Pre-commit для быстрых проверок: gitleaks, markdownlint, cspell, Stylelint
# - Pre-push для медленных: полный gitleaks по истории + lychee
# - Pa11y — только в CI (требует Chromium)
# =============================================================================
min_version: 2.0.0
# Pre-commit: проверки на staged-файлах перед каждым git commit
pre-commit:
parallel: false
jobs:
# 1. gitleaks — поиск ПДн / токенов / API-ключей в staged
# Правило §5.2 правил Claude. Блокирует коммит при находке.
- name: gitleaks
run: ./bin/gitleaks.exe protect --staged --config .gitleaks.toml --no-banner
fail_text: |
gitleaks нашёл потенциальные ПДн / токены / API-ключи в diff.
Если это маска / тестовое значение — добавь в allowlist .gitleaks.toml.
Если реальные данные — удали и используй маски (+7XXXXXXXXXX, ***@***).
# 2. markdownlint — стиль Markdown с авто-fix
- name: markdownlint
glob: "*.md"
# Вендоренные сторонние скилы .claude/skills/{mermaid,ccpm,data-scientist,marketingskills}/ — не линтуем (их .md
# не наши; markdownlint-cli2 игнорирует .markdownlintignore при явных путях).
exclude:
- ".claude/skills/mermaid/**"
- ".claude/skills/ccpm/**"
- ".claude/skills/data-scientist/**"
- ".claude/skills/marketingskills/**"
- "docs/superpowers/**"
- "docs/observer/STATUS.md"
run: node node_modules/markdownlint-cli2/markdownlint-cli2-bin.mjs --fix {staged_files}
# stage_fixed убран 23.05.2026: на этой Windows-машине он триггерит
# `git stash create` (прятать unstaged), который конфликтует за .git/index.lock
# с родительским git commit → коммит виснет. Авто-fix всё равно правит файлы
# в рабочей копии, но не авто-restage — git add вручную после правок.
fail_text: |
markdownlint нашёл проблемы, которые не исправляются автоматически.
Запусти `npm run lint:md:fix` или поправь руками.
# 3. cspell — орфография на staged .md
# --no-gitignore: staged-файлы по определению tracked; без флага cspell
# (useGitignore:true) игнорирует worktree-коммиты под gitignored .claude/worktrees/.
- name: cspell
glob: "*.md"
# Вендоренные сторонние скилы .claude/skills/{mermaid,ccpm,data-scientist,marketingskills}/ — не проверяем орфографию.
exclude:
- ".claude/skills/mermaid/**"
- ".claude/skills/ccpm/**"
- ".claude/skills/data-scientist/**"
- ".claude/skills/marketingskills/**"
- "docs/superpowers/**"
- "docs/observer/STATUS.md"
run: node node_modules/cspell/bin.mjs --no-progress --no-summary --no-gitignore {staged_files}
fail_text: |
cspell нашёл слова, отсутствующие в словаре.
Если это валидное слово проекта — добавь в cspell-words.txt.
Если опечатка — поправь.
# 4. Stylelint — стиль CSS в HTML-прототипах
- name: stylelint
glob: "*.html"
run: node node_modules/stylelint/bin/stylelint.mjs {staged_files}
fail_text: |
Stylelint нашёл проблемы в CSS прототипа.
Запусти `npx stylelint --fix <file>` где возможно.
# 5. Pint — code style PHP (фаза 1, Прил. Н #11). Авто-fix на staged .php в app/.
- name: pint
glob: "app/**/*.php"
root: "app/"
run: php vendor/bin/pint {staged_files}
# stage_fixed убран 23.05.2026 — см. комментарий у markdownlint-джоба
# (git stash create ↔ index.lock конфликт на Windows).
fail_text: |
Pint не смог отформатировать какие-то файлы (синтаксическая ошибка PHP?).
Запусти `cd app && composer pint` локально, посмотри вывод.
# 6. Larastan — статанализ PHP (фаза 1, Прил. Н #12). Полный анализ при любой правке
# *.php в app/ (точечный анализ только staged-файлов невозможен — phpstan нужен граф
# классов). Скорость: ~5-10 с на default scaffold + baseline 3.
- name: larastan
glob: "app/**/*.php"
root: "app/"
# cache-dir задан через tmpDir в app/phpstan.neon (Sprint 2 Phase A,
# O-perf-07 — ускорение инкрементальных прогонов).
#
# Перед анализом — сторож (tools/storozh-statanaliza.mjs). Замер 01.08.2026:
# в трёх случаях phpstan падает МОЛЧА (код 1, ни строки ни в обычном выводе,
# ни в канале ошибок) — нет файла из scanFiles, метка BOM в phpstan.neon,
# плюс третий случай, когда он не падает, а врёт. Человек видел fail_text
# ниже и шёл искать несуществующие ошибки в коде. Сторож называет причину
# и команду починки; при его срабатывании phpstan не запускается вовсе.
run: node ../tools/storozh-statanaliza.mjs && php vendor/bin/phpstan analyse --no-progress --memory-limit=512M
fail_text: |
Larastan нашёл новые ошибки выше baseline — ИЛИ сторож статанализа
сказал выше, почему запускать его сейчас бессмысленно. Сначала прочитай
вывод: если там строки «[сторож статанализа]» — чини по ним, ошибок в
твоём коде нет.
Иначе запусти `cd app && composer stan` локально и поправь.
Если ошибка ложная — обнови `app/phpstan-baseline.neon`
(см. feedback_environment.md п.25 — генерация baseline в 2 шага).
# 7. squawk — линтер миграций PostgreSQL (фаза 1, Прил. Н #15). Срабатывает только
# на staged *.sql (db/schema.sql и будущие raw-SQL-миграции). Конфиг .squawk.toml
# в корне отключает bootstrap-неприменимые правила и дизайнные предпочтения проекта.
- name: squawk
glob: "*.sql"
run: ./bin/squawk.exe {staged_files}
fail_text: |
squawk нашёл проблемы в SQL.
Конфиг — .squawk.toml в корне проекта.
Если правило неприменимо к bootstrap — добавь в excluded_rules в .squawk.toml.
Если это будущая миграция Laravel в app/database/migrations/ —
создай локальный .squawk.toml рядом с более строгими правилами.
pgFormatter (Прил. Н #16) — отдельно через `npm run format:sql:check`,
НЕ в pre-commit (стиль schema.sql ручной, авто-fix не применяется).
# 8. ESLint+Prettier+Vue — frontend стиль (фаза 2, Прил. Н #22). Lint-only
# на staged .ts/.vue в app/resources/js/. Полный type-check (vue-tsc) и Vitest —
# отдельно (медленно для pre-commit: ~3 сек на vue-tsc, ~3 сек на vitest).
# Запускать вручную: `cd app && npm run type-check && npm run test:vue`.
- name: eslint-vue
glob: "app/resources/js/**/*.{ts,vue}"
root: "app/"
run: npx eslint {staged_files}
fail_text: |
ESLint нашёл проблемы в Vue/TS-файлах.
Запусти `cd app && npm run lint:vue` локально и поправь.
Авто-форматирование: `cd app && npm run format`.
# 9. adr-judge — декларативная проверка ADR Enforcement-блоков (Прил. Н #36).
# Читает `## Enforcement` блоки docs/adr/ADR-*.md, применяет forbid_*/require_*
# правила к staged-дифу. Вендорен из adr-kit v0.13.1 (MIT) → tools/adr-judge.py;
# пере-вендорить после `/adr-kit:upgrade`. Без --llm → только regex, без вызова
# Claude API, нулевая стоимость (AK6). Без glob — adr-judge нужен весь staged-диф.
# `-X utf8` обязателен: stdin Python на Windows = cp1251; диф с кириллицей иначе
# ломается на surrogate → UnicodeEncodeError (crash, не ADR-violation).
- name: adr-judge
run: git diff --cached --unified=0 | python -X utf8 tools/adr-judge.py --diff - --adr-dir docs/adr/
fail_text: |
adr-judge: staged-изменение нарушает задокументированное архитектурное
решение (ADR). Смотри file:line выше и docs/adr/ADR-*.md.
Если ADR устарел — сначала обнови ADR (и его Enforcement-блок).
# 10. deptrac — архитектурный гейт направления зависимостей / границ слоёв
# (Прил. Н #43, architecture-tooling). Анализирует app/app/** по 13 слоям
# из app/deptrac.yaml. Первый прогон: 0 нарушений — кодовая база уже
# конформна, baseline-файл не нужен; job падает на НОВОМ дрейфе (Model→
# Service, Service→Http и т.п.). Без glob точечного анализа: deptrac строит
# граф классов, нужен весь app/. Чистый PHP, без LLM (AK6).
- name: deptrac
glob: "app/**/*.php"
root: "app/"
run: php vendor/bin/deptrac analyse --no-progress
fail_text: |
deptrac: staged-изменение нарушает направление зависимостей между
слоями (см. вывод выше и app/deptrac.yaml ruleset).
Если это осознанное архитектурное изменение — сначала обнови
app/deptrac.yaml (и при необходимости ADR-005), затем коммить.
# 11. l1-watcher — детектор settings.json ↔ Tooling Прил. Н drift
# (brain governance C1, ADR-011 spec §6.1). STRICT mode: блокирует коммит
# при плагине в settings.json без формализации в Tooling Прил. Н. Группо-
# вые/human-имена (Frontend Design plugin, Trail of Bits Skills) разре-
# шаются через tools/.l1-watcher-aliases.txt.
- name: l1-watcher
glob: "{.claude/settings.json,docs/Tooling_v8_3.md}"
run: node tools/l1-watcher.mjs
fail_text: |
l1-watcher detected settings.json ↔ Tooling Прил. Н drift.
Run /claude-md-management:claude-md-improver to formalize the plugin,
or — if it is documented under a group/human name — add an alias to
tools/.l1-watcher-aliases.txt.
# 12. cross-ref-checker — version drift между нормативными файлами
# (brain governance C2, ADR-011 spec §6.2). STRICT mode: блокирует коммит
# при расхождении версии в cross-ref и в header целевого файла. Link-anchored
# детекция + scope-cut по history-маркерам — исторические «наследие»-цепочки
# в шапках не дают ложных срабатываний.
- name: cross-ref-checker
glob: "{docs/Pravila_raboty_Claude_v1_1.md,docs/Tooling_v8_3.md,docs/Plugin_stack_rules_v1.md,CLAUDE.md,MEMORY.md}"
run: node tools/cross-ref-checker.mjs
fail_text: |
cross-ref-checker detected version drift in §0 cross-refs.
Update the offending file's cross-ref to match the target's header.
# 12b. extract-node-dormancy — REMOVED 2026-05-25 (LLM-first router overhaul
# Task 4). Source of truth for dormancy migrated from tools/.node-dormancy.json
# to docs/registry/nodes.yaml (field `status: active|dormant|deferred|historic`).
# Adapter: tools/registry-to-classification-map.mjs::buildDormancyMap.
# Archive: docs/archive/llm-bootstrap-2026-05/routing-docs/.
# 13. observer-of-observer — счётчик чтений docs/observer/ + 54-week self-prune
# (brain governance C3, ADR-011 spec §6.3). Скрипт всегда exit 0 (warn-only by
# design). При observer infrastructure не используется >=54 недель — warn.
- name: observer-of-observer
run: node tools/observer-of-observer.mjs check
fail_text: |
observer-of-observer reports stale state.
Consider running /brain-retro or archiving docs/observer/.
# 15. observer-coverage-checker — brain governance C5 (observer factor-
# analysis spec §5.2). Warn-only (script always exits 0). Flags observer
# coverage gaps (git activity but 0 episodes) + registration-integrity
# breaks (Stop-hook missing from settings.json, post-commit not installed).
# Findings surface in docs/observer/STATUS.md C5 row — never blocks a commit.
- name: observer-coverage-checker
run: node tools/observer-coverage-checker.mjs
fail_text: |
observer-coverage-checker reports a gap (coverage or registration).
See docs/observer/STATUS.md C5 row for details.
# 16. observer-chain-map-checker — brain governance C6 (chain attribution).
# Сверяет tools/observer-chain-map.json с таблицей L1-L13 в
# docs/routing-off-phase.md по множествам L-номеров (обе стороны). Блокирует
# коммит при дрейфе: несуществующая L в JSON или потерянная цепочка из .md.
- name: observer-chain-map-checker
run: node tools/observer-chain-map-checker.mjs
fail_text: |
observer-chain-map-checker: дрейф chain-map <-> routing-off-phase.md.
Обновите tools/observer-chain-map.json под таблицу L1-LN.
# 17. registry-render-check — drift между docs/registry/nodes.yaml и
# auto-region маркерами в docs/Tooling_v8_3.md / docs/routing-off-phase.md
# (router overhaul этап 1, task 11). Warn-only первую неделю — `|| exit 0`
# печатает WARN, но не блокирует коммит; после стабилизации убрать `|| ...`
# и сделать blocking.
- name: registry-render-check
glob: "{docs/registry/nodes.yaml,docs/Tooling_v8_3.md,docs/routing-off-phase.md}"
run: node tools/registry-render.mjs --check || echo "[registry] WARN rendered diverged from auto-region; run node tools/registry-render.mjs and recommit"
fail_text: |
registry-render-check: rendered output расходится с auto-region маркером.
Запустите `node tools/registry-render.mjs` и закоммитьте Tooling/routing-off-phase.
# Post-commit: regenerate STATUS.md dashboard (informational, not gate)
post-commit:
parallel: false
jobs:
# 14. status-md generator — brain governance C4, ADR-011 spec §6.4. Rebuilds
# docs/observer/STATUS.md after each commit. Pure JS +execFileSync — Security
# Guidance #40 compliant. STATUS.md becomes staged for NEXT commit — owner
# reviews before persisting.
- name: status-md
run: node tools/status-md-generator.mjs && git add docs/observer/STATUS.md || true
fail_text: |
status-md regeneration failed.
# 15. graphify safe auto-update — ADR-017 § "Стратегия обновлений".
# Wrapper читает `git diff HEAD~1 --name-only`, фильтрует к scope
# docs/+.claude/+app/ (НЕ tools/, vendor/, node_modules/, bin/ — иначе
# spike-blow-up 6305→41586 узлов). Code-файлы в scope → explicit-list AST
# extract (no detect_incremental, no manifest-prune risk) + build_merge в
# graph.json. Doc/MD-файлы → флаг needs_update без LLM (cost-control:
# subagent dispatch не подходит для хука). Skip silent если graphify-out/
# нет (другие worktrees) или 0 файлов в scope. Non-blocking — всегда exit 0.
- name: graphify-safe-update
run: node tools/graphify-safe-update.mjs
fail_text: |
graphify-safe-update failed (non-blocking — run /graphify --update manually).
# Pre-push: проверки перед git push (медленнее, но реже запускаются)
pre-push:
parallel: false
jobs:
# 5. Полный gitleaks-скан всей истории (не только staged)
- name: gitleaks-full-history
run: ./bin/gitleaks.exe detect --source . --no-banner --config .gitleaks.toml --redact
fail_text: |
gitleaks нашёл утечки в истории коммитов.
Перед push разберись с каждой находкой.
# 6. lychee — проверка ссылок в narrative-доках (.md). HTML-концепты в web/v8/
# и liderra_v8_handoff/concepts/ намеренно НЕ проверяются — там root-relative
# ссылки на будущие маршруты Vue+Vuetify (/login, /register, /legal/*).
#
# 🔴 30.07.2026 — БЫЛО: `"docs/**/*.md" "db/**/*.md" "*.md"`. Под lefthook `**`
# схлопывался до одного уровня: проверялись только docs/*/*.md, а docs/*.md и
# всё глубже второго уровня (docs/superpowers/specs|plans|audits|findings|
# runbooks, docs/observer/notes) НЕ проверялись вообще. Замер: под хуком
# 183 ссылки = 95 корневых + 3 db + 85 один уровень docs; той же командой
# руками — 2242. Так и накопились 92 битые ссылки, 88 из них глубже второго
# уровня. Теперь каталоги + --extensions md: lychee обходит дерево сам,
# без участия shell-глоба. Проверено вырезанием: подложенная битая ссылка
# в docs/ ловится, старый вариант её не видел.
- name: lychee-links
run: ./bin/lychee.exe --config .lychee.toml --extensions md docs db *.md
fail_text: |
lychee нашёл битые ссылки в .md.
Запусти `npm run links` локально, поправь URL или добавь в `.lychee.toml` exclude.
# Подавить служебный вывод
skip_output:
- meta
- skips