Garbage collection харнеса: проверки на дрейф (Phase 5)
Фаза 5 серии Harness Engineering: маленькие непрерывные платежи против энтропии. Запланированные проверки на дрейф (drift) — мёртвый код, устаревшие зависимости, рассинхрон документации — которые не просто алертят, а ПРОИЗВОДЯТ артефакт: PR, issue, обновлённый quality.md. Полу-автоматический quality.md (скрипт собирает цифры, человек пишет интерпретацию ~15 мин/нед), doc-freshness gate, расписание в CI и честность про метрики (Goodhart). Стек-агностично, с копируемыми промптами.
ПродвинутыйDevOps с AI20 минCI, quality.md, knip, dependency-cruiser
1
Зачем GC: дрейф копится тихо
Один из принципов harness-инженерии — «маленькие непрерывные платежи»: энтропию убираешь фоновыми проверками по расписанию, а не «спринтом техдолга раз в квартал». Garbage collection (GC) здесь — не уборка памяти, а фоновая чистка дрейфа (drift): расхождения между тем, что записано, и тем, что есть в коде.
Дрейф опасен тем, что копится тихо. Удалили модуль — а в docs/architecture.md он всё ещё описан. Зависимость устарела на три минорные версии — никто не заметил, пока не прилетела CVE. Функция стала мёртвым кодом (dead code) после рефакторинга — линтер про неё молчит, потому что её никто не проверяет. По отдельности каждое расхождение мелочь; вместе они превращают «источник правды» в художественную литературу, а агент читает её как факт и галлюцинирует на ровном месте.
Главное отличие фазы 5 от обычного линтинга: проверка не просто падает с красным крестиком. Она ПРОИЗВОДИТ артефакт, который читает человек или подхватывает следующий agentic-проход — PR с фиксом, issue со списком, обновлённый markdown в репо.
🟥 Проверка, которая только алертит
Падает красным крестиком — и всё
Шум в логах, который привыкаешь игнорить
Никто не подхватывает результат
🟩 Проверка, которая ПРОИЗВОДИТ артефакт
PR с фиксом / issue / обновлённый MD
Артефакт подхватывает человек или агент
Harness, который чинит сам себя
Тест на полезность GC-задачи: что станет с её результатом через неделю? Если «попадёт в PR/issue, который кто-то закроет» — хорошо. Если «добавит красную галочку в дашборд, на которую все забьют» — это не GC, это шум.
2
Что мерить → какой артефакт (реальный класс проблем, не дашборд)
Каждая GC-задача описывается как РОЛЬ: «что должно проверяться» — а конкретный инструмент выбираешь под стек и фиксируешь в ADR. Где написано «поиск мёртвого кода» — это knip / ts-prune для JS/TS, vulture для Python, deadcode для Go. Где «гигиена зависимостей» — npm audit + npm outdated, pip-audit, bundler-audit, cargo audit.
Ключевое правило выбора: каждая проверка ловит РЕАЛЬНЫЙ класс проблем, а не красит метрику в дашборде. Мёртвый код — это будущий источник путаницы для агента (он начнёт «чинить» то, что никто не вызывает). Дрейф зависимостей — это уязвимости и несовместимости. Doc-freshness — это рассинхрон источника правды. Циклы в графе модулей — это размывание границ, на которых стоит вся архитектурная дисциплина. Каждый пункт связан с конкретным артефактом-выходом.
Избегай «vanity»-метрик: число строк, абстрактный «технический долг в баллах», процент-ради-процента — они легко гонятся и ничего не говорят о реальных багах.
Роль проверки
Инструменты по стекам (примеры)
Артефакт на выходе
Мёртвый код (dead code)
knip / ts-prune · vulture · deadcode
PR с удалением / issue со списком
Дрейф зависимостей
npm audit/outdated · pip-audit · cargo audit
minor/patch → PR · major → issue
Циклы и границы модулей
madge · dependency-cruiser · import-linter
issue с найденными циклами
Свежесть документации (doc-freshness)
git log last-modified · скрипт сверки путей
issue со списком устаревших docs
Снимок качества (quality.md)
агрегатор всех проверок выше + diff
PR с обновлённым quality.md
Покрытие (coverage) в этой таблице сознательно НЕ строка-цель. Если фиксируешь его — только как описательный сигнал внутри quality.md, и в паре с барьером test-integrity: агент не имеет права ослаблять или удалять существующие тесты ради зелёной цифры (проверяется в CI по диффу).
3
quality.md полу-авто: скрипт собирает, человек интерпретирует
Центральный артефакт фазы 5 — живой quality.md, отражающий ТЕКУЩЕЕ состояние качества по доменам. Делается он полу-автоматически, и это сознательный выбор, а не временный костыль.
Скрипт берёт на себя механику: прогоняет coverage, линтер границ, поиск мёртвого кода, npm outdated, сверяет last-modified в docs/ — собирает цифры и считает diff к прошлому отчёту (что улучшилось / ухудшилось). Человек берёт на себя то, чего скрипт не может: интерпретацию. Почему домен X просел до C — это намеренный техдолг под дедлайн (есть ADR) или незамеченная деградация? Это ~15 минут в неделю на чтение готового черновика.
Почему не полная LLM-автоматизация прямо сейчас: без глубокого контекста проекта LLM пишет общие места — «покрытие можно улучшить», «есть устаревшие зависимости». Цифры собирает скрипт детерминированно и дёшево; суждение о том, что эти цифры ЗНАЧАТ для этого проекта, пока остаётся за человеком. Полу-автоматизация здесь — золотая середина: ~80% выгоды за ~5% усилий.
Скрипт: прогон всех проверок
Цифры + diff к прошлой неделе
Черновик quality.md
почему так?
Человек: интерпретация ~15 мин/нед
PR с обновлённым quality.md
# Quality Report (last updated: YYYY-MM-DD)
## Domains
| Domain | Test coverage | Architectural compliance | Documentation freshness | Overall grade |
|--------|---------------|--------------------------|-------------------------|---------------|
| ... | (signal, %) | (cycles / boundary viol.)| (stale docs count) | A / B / C / D |
Grades: A (clean), B (minor), C (significant gaps), D (broken).
Coverage is a descriptive SIGNAL, not a target. No global % mandate.
## Known violations
- <issue/PR link> — what it is, what's planned (ref docs/decisions/ or docs/plans/)
## Trends
What improved / regressed since the last report (auto-diff by the script).
Grades A/B/C/D ставит человек, не скрипт. Скрипт даёт сырьё (coverage 62%, 3 цикла, 5 outdated deps); перевод «62% — это A или C для этого домена?» требует знания, какой код критичен. Автоматизировать перевод сигнала в оценку — преждевременно.
4
Doc-freshness gate: документация не отстаёт от кода
Documentation staleness check (проверка свежести документации) ловит самый коварный класс дрейфа — рассинхрон между docs/ и кодом. Агент читает docs/architecture.md, AGENTS.md, docs/decisions/ как источник правды; если там описан удалённый модуль или переименованный путь, агент уверенно строит на несуществующем фундаменте.
Две реализации, от мягкой к жёсткой. Мягкая (фоновая задача): скрипт сравнивает last-modified документа с временем изменения релевантных частей кода; если код менялся, а связанный doc — нет, открывается issue со списком потенциально устаревших документов. Жёсткая (gate в CI): отдельный скрипт парсит docs/ и проверяет, что все упомянутые там пути / модули реально существуют в кодовой базе. Удалили или переименовали путь без обновления документации — CI краснеет на этом PR. Это превращает «доку надо бы обновить» из конвенции (на дисциплине) в барьер (форсится механически), а в харнесе полагаться надолго можно только на то, на что поставлен барьер.
Жёсткий gate ставь там, где это реально автоматизируемо: проверка существования путей/символов детерминирована. Семантическую актуальность («текст всё ещё описывает то, что делает код») барьером не закроешь — она остаётся в мягкой issue-задаче и на человеке.
Doc-freshness gate: от мягкого к жёсткому
Мягко: docs старше связанного кода → issue со списком (фоновая задача)
Жёстко: упомянутые в docs пути/модули реально существуют → иначе CI краснеет
Существование путей/символов детерминировано — годится для барьера
Семантическую актуальность барьером не закрыть — остаётся issue + человек
Жёсткий gate на семантику текста («звучит актуально») — ложная уверенность
«Машина важнее текста»: правило «обновляй доку при изменении модуля» в AGENTS.md живёт ровно до первого дедлайна. Тот же инвариант как CI-gate на существование путей соблюдается всегда — потому что без него не мерджится.
5
Расписание в CI + честность (Goodhart)
GC-задачи живут в планировщике фоновых задач, выбранном на старте внедрения: GitHub Actions, GitLab CI, Jenkins, n8n, cron на сервере. Роль одна — «регулярно запустить скрипт и положить артефакт куда-то, доступное команде». Разумный дефолт расписания — раз в неделю: достаточно часто, чтобы дрейф не успел накопиться, достаточно редко, чтобы PR-ы и issue не превратились в шум. Если планировщика в проекте нет — фаза 5 требует сначала развернуть минимальный (этого плана достаточно для регулярного запуска скрипта) и зафиксировать ADR.
Честность напоследок. Этот rollout НЕ валидирует функциональную корректность — что код делает то, что нужно пользователю, всё ещё проверяет человек и продуктовые тесты. GC обеспечивает архитектурную целостность и поддерживаемость, не «правильность фичи».
И главное про метрики — Goodhart: как только метрика становится целью, она перестаёт быть хорошей метрикой. Не вводи глобальный мандат «покрытие 100% везде»: агент тут же напишет мусорные тесты под процент. Хочешь гейт — только на новый/изменённый код, и не как мандат, а как разумный дефолт твоего выбора. По-настоящему важна не цифра, а политика test-integrity: агент не правит и не ослабляет существующие тесты (проверяется в CI по диффу). Каждая GC-проверка должна ловить реальный класс проблем, а не радовать дашборд.
# Weekly GC schedule (role: background scheduler — GH Actions / GitLab CI / cron)
# Each job PRODUCES an artifact (PR / issue / updated MD), never just alerts.
on:
schedule:
- cron: '0 6 * * 1' # weekly — Monday 06:00; a default, not a law
jobs:
quality-refresh: # → PR updating docs/quality.md (numbers + diff)
dead-code: # knip / vulture / deadcode → PR/issue
dep-hygiene: # audit + outdated → minor/patch PR, major issue
doc-freshness: # stale docs + path-existence → issue (+ CI gate on PRs)
# Honesty:
# - GC ensures architectural integrity, NOT functional correctness.
# - No global coverage mandate (Goodhart). Coverage = signal, not target.
# - Test-integrity barrier: agent must not weaken/delete existing tests.
Если артефакты GC-задачи неделями никто не закрывает — это не повод усилить алерты, а сигнал, что задача ловит несуществующую проблему или расписание слишком частое. Мёртвая GC-проверка сама становится дрейфом.
6
Что дальше: карта серии
Garbage collection — фаза 5 rollout-плана: фоновые проверки на дрейф, которые превращают энтропию из «спринта техдолга раз в квартал» в маленькие непрерывные платежи. Вы собрали четыре регулярные задачи (quality refresh, dead code, dependency hygiene, doc-freshness), каждая ПРОИЗВОДИТ артефакт, а quality.md живёт полу-автоматически.
Следующая фаза в логике серии — 5.5, lessons-ledger: коррекции как код. Она садится на УЖЕ выбранный здесь планировщик (maintain-цикл уроков — фоновая задача того же класса, что фаза 5) и закрывает то, чего GC не ловит: повторяющиеся НЕ-детерминируемые ошибки агента (вкус, контекстный выбор). Её отдельный рецепт — фаза 5.5, ссылка в карте серии ниже. А полную карту всех фаз rollout и связи между ними смотрите в хабе серии.
GC и lessons-ledger — фоновые задачи одного класса на одном планировщике, но ловят разное: GC — детерминируемый дрейф (мёртвый код, deps, docs), ledger — недетерминируемые ошибки вкуса. Не путайте: computational-урок надо отдавать в барьер (фаза 3), а не копить текстом.
Результат
Фаза 5 собрана: дрейф убирается маленькими непрерывными платежами, а не квартальным спринтом техдолга. Вы понимаете, как описывать GC-задачи ролями со стек-агностичными инструментами (knip/vulture, npm audit/pip-audit, madge), почему каждая проверка должна ПРОИЗВОДИТЬ артефакт (PR/issue/quality.md), а не просто алертить, как держать quality.md полу-автоматически (скрипт собирает цифры, человек пишет интерпретацию ~15 мин/нед), и как поставить doc-freshness gate от мягкой issue до жёсткого CI-барьера на существование путей. И главное — честность: GC обеспечивает архитектурную целостность, а не функциональную корректность, а test-integrity (агент не ослабляет тесты) важнее любого процента покрытия. Дальше — фаза 5.5, lessons-ledger.