Фаза 3 harness-серии: «машина важнее текста». Превращаем архитектурные правила из текста в docs/architecture.md в детерминированные проверки — границы импортов, мёртвый код, циклы, строгие типы. Кросс-стековая таблица «роль → инструмент» (dependency-cruiser / import-linter, knip / ts-prune / vulture, madge, tsc / mypy), test-integrity tripwire вместо мандата на покрытие (Goodhart), pre-commit + CI как точки форса и самописные скрипты для непокрытого. С копируемыми промптами и конфигами.
ПродвинутыйDevOps с AI25 минpre-commit, CI, dependency-cruiser, import-linter, eslint
1
Барьер ≠ конвенция: форсится только барьер
Базовый принцип harness-инженерии — «машина важнее текста». Правило, выраженное проверкой (линтер, тест, скрипт), действует. То же правило абзацем в README — нет. Phase 3 — это место, где архитектурные правила из docs/architecture.md перестают быть текстом и становятся детерминированными проверками.
Различай три состояния правила. Барьер (barrier) форсится механически: pre-commit и CI не дадут зашипить код, который его нарушает — у агента просто нет пути в обход. Конвенция (convention) держится только на дисциплине агента: записана в AGENTS.md, но никто не проверяет — на дистанции дрейфует. Условный шаг (conditional) срабатывает по триггеру (worktree, ExecPlan, эскалация) и часто правильно пропускается. Вывод жёсткий: на что нельзя поставить барьер — на то нельзя положиться надолго. Phase 3 максимизирует долю правил, переведённых в барьеры.
🟥 Барьер
Форсится механически: pre-commit / CI
Нет пути в обход — агент упирается
Границы, типы, циклы, мёртвый код
🟦 Конвенция
Держится на дисциплине агента
Записана в AGENTS.md, но не проверяется
На дистанции дрейфует
🟨 Условный
Срабатывает по триггеру
worktree, ExecPlan, эскалация, /wrong
Часто и правильно пропускается
Прежде чем писать конфиг, перечитай Layer model в docs/architecture.md. Барьер ловит ровно то, что в нём закодировано: размытое правило в тексте даст размытую (или дырявую) проверку.
2
Кросс-стек таблица: роль → инструмент
Главное правило этой фазы — инструмент-агностичность. Не «eslint — закон вселенной», а «есть роль проверки, и под стек проекта подбирается инструмент в этой роли». Сначала определяешь роли (что должно проверяться), потом фиксируешь конкретный выбор в ADR docs/decisions/0001-tool-selection.md — дальше все фазы берут его как вход.
Ниже — роли и примеры инструментов по стекам. Например, в JS/TS границы модулей и публичный API часто держат через eslint-plugin-boundaries или архитектуру FSD, но это лишь один стек — роль та же и в Python (import-linter), и в Go (depguard). Что не покрыто готовым инструментом — закрываешь самописным скриптом-проверкой (последняя строка). Не ищи «один инструмент на всё»: каждая роль — отдельный барьер.
Роль (что проверяем)
JS / TS
Python
Другие стеки
Границы импортов / слоёв
dependency-cruiser, eslint-plugin-boundaries
import-linter
depguard (Go), ArchUnit (JVM)
Мёртвый код (dead code)
knip, ts-prune
vulture
deadcode (Go), dead_code lint (Rust)
Циклы зависимостей
madge
import-linter (contracts)
компилятор (Go), ArchUnit (JVM)
Вложенность и размер файлов
eslint (max-depth, max-lines)
flake8, ruff (C901, PLR)
golangci-lint, clippy
Строгие типы (strict types)
tsc --strict
mypy --strict, pyright
sorbet (Ruby), компилятор (Go/Rust)
Непокрытое выше
Свой скрипт-проверка
Свой скрипт-проверка
Свой скрипт-проверка
Каждую строку этой таблицы зафиксируй в ADR с обоснованием выбора. Иначе следующий агент через месяц поставит второй инструмент в ту же роль — и появятся два расходящихся барьера.
3
Хребет: строгие типы + границы импортов
Два барьера несут основную нагрузку — это хребет Phase 3. Первый: строгие типы. Для статически типизированных языков — включить strict-режим и самые строгие опции компилятора (tsc --strict, mypy --strict). Стратегия миграции из промпта 3.2: если включение строгости даёт < 50 ошибок — включи и исправь сейчас; если больше — заведи ADR с поэтапным планом и TODO с датой, не блокируй работу.
Второй: границы импортов из Layer model. Конфиг должен отражать ВСЕ правила слоёв — разрешённые направления зависимостей, запрет циклов, запрет orphan-модулей (мёртвый код, не импортируемый ниоткуда), запрет импортов в обход публичного API. Что из этого применимо — зависит от стека. Важный нюанс из промпта 3.1: НЕ молча правь найденные нарушения. Записывай их в docs/quality.md как техдолг с приоритетами; чинь сразу только то, что < 30 минут и не блокирует.
Хребет Phase 3 — что должно проверяться
Strict-режим типов включён на максимум, разумный для проекта
Разрешённые направления зависимостей между слоями
Запрет циклов между модулями
Запрет orphan-модулей (мёртвый код)
Запрет импортов в обход публичного API
Нарушения занесены в docs/quality.md, а не «починены молча»
Массовый авто-фикс нарушений без записи в реестр
Строгость типов и границы дают агенту легибельность среды (agent-legibility): чем строже сигнатуры и чётче слои, тем меньше агент додумывает контекст — и тем реже ломается на ровном месте.
4
Test-integrity tripwire, а не «100% покрытия»
Самая частая ошибка на этой фазе — ввести покрытие (coverage) как мандат. Это Goodhart: как только процент становится целью, агент пишет фиктивные тесты под число, и сигнал умирает. Поэтому не ставь процент как цель.
Что ставить вместо мандата — test-integrity барьер (tripwire). Это проверка по диффу в CI: агенту запрещено ослаблять, удалять, скипать или «смягчать» существующие тесты, чтобы они прошли. Удалил assert, добавил .skip, ослабил матчер, выкинул кейс — CI падает. Тесты здесь — сигнал здоровья, а не цель; покрытие — побочный эффект честной работы, а не таргет. Если очень хочешь гейт на процент — только на новый/изменённый код (≈80% — разумный дефолт, но это твой выбор, не мандат), плюс mutation testing для качества сигнала.
✅ Test-integrity tripwire
Нельзя удалять/скипать существующие тесты
Нельзя ослаблять assert и матчеры
Проверяется по диффу в CI
Тесты = сигнал здоровья
❌ Мандат на покрытие (Goodhart)
«100% везде» как глобальное правило
Процент становится целью
Агент пишет фиктивные тесты под число
Сигнал умирает, дашборд зелёный
Покрытие — это side effect честной работы, а не таргет. Один tripwire «не ослабляй тесты» защищает сигнал лучше, чем любой процент: число можно накрутить, integrity-диффом — нет.
5
Точки форса: pre-commit + CI (копируемый конфиг)
Барьер действует только там, где его нельзя обойти. Две точки форса: pre-commit (быстрый, по диффу, ловит до коммита) и CI (полный, ничего не пропустит на merge). На pre-commit вешаем форматтер, основной линтер, линтер границ и тайп-чек — но только на затронутые файлы, не на всю кодовую базу, иначе хук станет невыносимо медленным и его начнут обходить через --no-verify. CI гоняет всё целиком и делает обход невозможным.
Definition of Done для Phase 3: pre-commit падает на нарушении границ, CI падает на нарушении границ, docs/quality.md содержит реестр известных нарушений с приоритетами. Ниже — копируемый промпт 3.1 для агента и генерик-конфиг pre-commit. Конкретный оркестратор (pre-commit framework, husky, lefthook) выбирается под стек на Phase 0 и фиксируется в ADR.
# ── Промпт 3.1 для агента — границы модулей / Prompt 3.1 — module boundaries ──
Phase 3. Прочитай docs/architecture.md (Layer model) и
docs/decisions/0001-tool-selection.md (роль "Линтер границ модулей").
Задача: превратить layer model в машинно-проверяемые правила,
используя инструмент, выбранный на Phase 0.
1. Сконфигурируй инструмент. Конфиг отражает ВСЕ правила Layer model:
- разрешённые направления зависимостей между слоями
- запрет циклов между модулями
- запрет orphan-модулей (мёртвый код, не импортируемый ниоткуда)
- запрет импортов в обход публичного API
(что применимо — зависит от стека).
2. Запусти на текущей кодовой базе. Покажи отчёт.
3. НЕ правь нарушения молча. Запиши их в docs/quality.md как
техдолг с приоритетами. Чини только то, что < 30 минут и не блокирует.
4. Подключи запуск как обязательный шаг на коммит/пуш (pre-commit
hook / husky / lefthook — уместный под стек) и в CI.
Обнови Progress, Surprises & Discoveries, Decision Log.
# ── Генерик-конфиг pre-commit (только diff!) / generic pre-commit (diff only!) ──
repos:
- repo: local
hooks:
- id: format # форматтер / formatter
entry: <project-formatter>
- id: lint # основной линтер / main linter
entry: <project-linter>
- id: boundaries # границы импортов / import boundaries
entry: <boundary-linter> # dep-cruiser / import-linter
- id: typecheck # строгие типы по затронутым файлам
entry: <type-checker --changed> # tsc / mypy
# CI повторяет всё это на ПОЛНОЙ базе — обход через --no-verify не спасёт.
pre-commit — по диффу, CI — целиком. Если повесить всё на pre-commit полным прогоном, агент (и человек) начнут жать --no-verify, и барьер тихо превратится в конвенцию.
6
Самописные скрипты для непокрытого + что дальше
Готовые инструменты закрывают типовые роли, но у каждого проекта есть свои правила, на которые нет линтера: «в domain-слое нет импортов из infra», «в этой папке нет console.log», «у каждого публичного хендлера есть контракт-схема». Это последняя строка таблицы: что не покрыто — пишешь своим скриптом-проверкой и вешаешь в ту же связку pre-commit + CI. Скрипт — такой же барьер, если он падает с ненулевым кодом.
Граница harness-инженерии честная: эти барьеры дают архитектурную целостность и поддерживаемость, но НЕ валидируют функциональную корректность — что код делает то, что нужно пользователю, всё ещё проверяет человек и продуктовые тесты. Phase 3 закрыта, когда выполнен DoD. Дальше — Phase 4 (quality.md как живой документ, где покрытие фиксируется как сигнал, а не цель) и Phase 5 — сборка мусора харнеса: автоматические проверки на дрейф по расписанию, чтобы барьеры не ржавели.
#!/usr/bin/env bash
# check-boundaries-custom.sh — барьер для правила, на которое нет готового линтера
# Пример: в domain-слое нет импортов из infra / no infra imports in the domain layer.
set -euo pipefail
# Адаптируй grep/AST-инструмент под свой стек — суть в exit code.
VIOLATIONS=$(grep -rEn "from ['\"].*infra" src/domain/ || true)
if [ -n "$VIOLATIONS" ]; then
echo "❌ domain-слой импортирует infra (нарушение Layer model):"
echo "$VIOLATIONS"
exit 1 # ненулевой код → pre-commit и CI падают / pre-commit & CI fail
fi
echo "✅ boundaries: domain чист от infra-импортов"
# Что НЕ ловит ни один из этих барьеров:
# функциональная корректность — «делает ли код то, что нужно пользователю».
# Это остаётся на человеке и продуктовых тестах. Открытая граница harness.
Самописный скрипт — кандидат №1 на промоушн из lessons-ledger: повторяющаяся коррекция «снова импорт в обход слоя» затвердевает в проверку и больше не повторяется. Так агент чинит свою среду сам.
Результат
Архитектурные правила перестали быть текстом и стали барьерами: строгие типы и границы импортов — хребет, test-integrity tripwire защищает тесты вместо мандата на покрытие (без Goodhart), pre-commit + CI делают обход невозможным, а самописные скрипты закрывают непокрытое. Инструменты выбраны по ролям под стек и зафиксированы в ADR. DoD Phase 3 выполнен; дальше — quality.md (Phase 4) и сборка мусора харнеса (Phase 5).