Git log как датасет для архитектурного ретроанализа
Гибрид: детерминированные метрики (маленький bash-скрипт) плюс LLM-интерпретация
поверх. Каждое утверждение в отчёте опирается либо на SHA коммита, либо на число из
метрик — никаких выдуманных паттернов. Read-only by design. Proof — реальный прогон
на .claude/skills/
этого репо с конкретными архитектурными выводами, пришедшими из чисел.
Проблема
При долгой разработке проекта из десятков скиллов, сервисов или модулей теряется контекст принятия прошлых архитектурных решений. Подходы менялись, что-то рефакторилось, что-то умерло без явного решения, что-то слиплось без явного намерения. Чтобы увидеть это объективно, нужен датасет, который не зависит от человеческой памяти.
Такой датасет уже есть — это git-история. Проблема не в его отсутствии, а в том, что им редко пользуются как датасетом. Обычно его читают как ленту коммитов («что было в этом месяце»), реже — как метрику (churn, co-change). И почти никогда — как материал для интерпретации.
Сразу оговорка про что это не. Это не «скормить git log в LLM и получить инсайт». Так не работает: на месячной истории среднего репо дифф — это десятки мегабайт текста, в один промпт не лезет; и даже если бы лез, LLM на сыром логе склонна к нарративной гладкости — найдёт «эволюцию» и «переломы» там, где их нет.
Это и не code-maat-подобный подход (одни количественные метрики). Метрики дают факт, но не объяснение. «Компонент X переписывали 16 раз» — факт. Был ли это поиск формы, реакция на изменение требований или признак неудачного дизайна — числа не скажут.
Запрос: получить объяснение, основанное на числах, а не нарратив без основания и не цифру без интерпретации.
Методика
Один принцип: разделить метрический слой и интерпретационный. Метрики
считаются детерминированно, поверх них LLM пишет нарративный отчёт. Каждое утверждение
в отчёте обязано ссылаться либо на конкретный SHA коммита, либо на число из метрик.
Это снимает риск выдуманных паттернов: если LLM сказала «компонент А связан с Б»,
должно быть co_commits ≥ 2
и SHA коммитов, где они вместе. Иначе утверждение не появляется в отчёте.
Слой 1 — метрики (скрипт, bash + awk)
Один скрипт принимает --root <path>,
--since <date>,
--until <date>,
--bulk-threshold K и
выдаёт JSON из трёх блоков:
- •
components[]— per-component churn, first/last коммит, biggest коммит. «Компонент» = подкаталог первого уровня внутри--root. - •
co_change_pairs[]— для каждой пары компонентов число коммитов, тронувших обоих. - •
commits[]— индекс по SHA: дата, автор, размер диффа, тронутые компоненты, is_bulk флаг, subject.
Bulk-фильтр: коммит, тронувший больше K компонентов (дефолт 5),
помечается is_bulk=true
и исключается из co-change матрицы. Иначе один-два cross-cutting рефактора
(«обновил формат во всех скиллах») перекосят матрицу настолько, что любая пара из
задействованных компонентов будет выглядеть связанной. Per-component churn такие
коммиты всё же набирают — это реальная работа, просто не семантический сигнал
сцепленности.
Никаких зависимостей: git + bash + awk. Read-only.
Слой 2 — детерминированный отбор коммитов
LLM не выбирает, что смотреть. Правила фиксированы:
- • per-component top-N (default 10) по размеру диффа;
- • multi-component non-bulk: коммиты, тронувшие 2-4 компонента — кандидаты в переломы (cross-cutting, но не «обновил всё»);
- • top co-change drivers: для топ-5 пар — найти коммиты, в которых эта пара встречается.
Объединение трёх множеств идёт в полный дифф через
git show -p. Если
суммарный объём диффов превышает бюджет (~200K токенов), скилл обрезает с конца
отсортированного по дате списка и в отчёте пишет об обрезке.
Слой 3 — интерпретация (LLM)
Имея метрики JSON + полные диффы отобранных коммитов, LLM пишет отчёт по жёсткой структуре: scope → метрики → co-change → переломы → парадоксы → эволюция подхода → что напрашивается → ограничения.
Правила интерпретации:
- • Утверждение про компонент → ссылка на число из метрик («21 commit, +716/-166»).
- • Утверждение про конкретное изменение → ссылка на SHA.
- • Утверждение про co-change → ссылка на пару с числом co_commits.
- • Пара с
co_commits = 1— это шум, не паттерн. В отчёт берём толькоco_commits ≥ 2. - • Если что-то «кажется», но числами не подтверждается — это место остаётся пустым. Дыра в отчёте лучше нарратива без основания.
Что это даёт
- 1. Воспроизводимость. На одном и том же git state скрипт даёт идентичный JSON. Метрики — факт. Интерпретация поверх — мнение LLM, и оно явно подписано как мнение (со ссылками на источник).
- 2. Локализация ошибки. Числа кривые — баг в скрипте, чинится тестом. Нарратив кривой — LLM нагалюцинировала, фиксится правкой инструкции.
- 3. Bulk-фильтр прозрачен. Исключённые коммиты не пропадают — они названы в отчёте отдельной секцией. Видно, что было решено выкинуть и почему.
- 4. Метрики переиспользуемы. Скрипт работает без LLM-слоя — для CI, дашборда, сравнения двух запусков. Бонус, не цель.
Артефакт
Скилл git-evolution-audit —
публичный репозиторий:
github.com/dobryakov/git-evolution-audit.
- •
SKILL.md— оркестрация, правила отбора, структура отчёта, failure modes. - •
scripts/collect_metrics.sh— bash-скрипт метрического слоя (~150 строк, без зависимостей).
Один реальный прогон сделан на
.claude/skills/ этого
репо за период 2026-04-27..2026-05-22 (62 коммита, 19 компонентов, 2 bulk-исключения).
Из конкретных выводов прогона:
-
У humanizer самый высокий
delete-to-insert ratio в каталоге:
824/1625 ≈ 51%против ~3% у соседей. Половина того, что было написано, переписана — поиск формы, а не наращивание. 22 мая (коммит27fa2bc) монолитныйSKILL.mdраспался на skill +references/{calques,lexicon,rhetoric,typography}.md. -
action-planner — следующий кандидат на ту же
декомпозицию: 21 коммит, 716+ строк, но
delete:insert = 23%(итерации, не переписывания). Если темп удержится, монолит станет узким местом. -
Co-change paradox: пара
evidence-finder ↔ humanizer(co_commits=2, 50%) не из структуры каталогов — а из явного пайплайна публикации, зафиксированного вCLAUDE.md. Маркер, что в репо есть имплицитные пайплайны, проявленные только через co-change. Кандидат вынести их в first-class объект.
Все эти выводы пришли из чисел, не из «общих соображений». Это и есть проверка методики на собственном репо.
Где ломается
-
Переименования каталогов искажают историю.
Если компонент был переименован/перемещён, метрики покажут «короткую жизнь» нового
и «смерть» старого.
git log --followработает только для одиночных файлов, не для каталогов. Rename detection не реализован by design (MVP). На репо без переименований — не проблема. На рефакторящемся коде — disclaimer обязателен. - Rebase/squash/force-push в истории делают числа неточными там, где команда чистила историю. На репо с linear-merge политикой обычно ОК.
-
Бинарные файлы в
--numstatпоказываются как-/-. Скрипт считает их как 0/0 в churn, но они попадают в счётчик коммитов компонента. На каталогах с изображениями метрика обманчива. -
Контент vs код.
На
outputs/,market-state/signals/и т.п. высокий churn = нормальная работа, не нестабильность. Скилл с этой семантикой не разбирается — интерпретация должна знать, что за каталог анализирует. На код-репо разница между «source» и «log-style» каталогами проще; на content-репо границы размыты. -
Generator/generated пары не детектируются.
Запуск на
website/покажет «эволюцию HTML», что бессмысленно: исходник —content.md. Эвристика автоопределения (1:1 co-change → пометить как generated) оставлена за рамками MVP — на однородном каталоге типа.claude/skills/не нужна, на разнородном репо — обязательна. - Размер контекста. На крупных код-репо выборка диффов не лезет в LLM даже после top-N фильтра. Шаг 3 обрезает с честным предупреждением, но реалистичный масштаб MVP — каталоги в десятки-сотни коммитов с осмысленными диффами. Скейл «весь монорепо за 5 лет» потребует budgeting (квартальные срезы → meta-summary) и ре-инжиниринга, не реализованных здесь.
-
Co-change на малых N недостоверен. Пара с
co_commits = 1— шум. Скилл это фильтрует на уровне отчёта (≥ 2), но на короткой истории (1-2 недели) даже 2 — недостоверно. Минимум 4 недели или 50+ коммитов на каталог для осмысленной матрицы. - Bulk-threshold — эвристика. Дефолт K=5 хорошо ловит cross-cutting «обновил формат везде», но может ошибочно выкинуть легитимный крупный рефактор по 6 компонентам. Выбор K — конфигурируемый параметр, и при сомнениях стоит запустить дважды с разными значениями и сравнить co-change матрицы.
- LLM-склонность к нарративной гладкости не исчезает полностью. Снижается за счёт правила «каждое утверждение со ссылкой на SHA или число», но не до нуля. Лекарство — читать отчёт критически: проверять любое подозрительное обобщение через grep по упомянутым SHA.
-
Single-author bias.
На репо одного автора
actor_breakdown— не сигнал. На командном — критически важная секция, в MVP не отдельно выделена.
Для кого и почему
Architect, tech lead, head of AI — те, кто строит долгоживущие системы и хочет периодически получать объективное зеркало того, что в них происходит.
Особенно полезно — для систем со скиллами/агентами/LLM-пайплайнами, где «как развивался компонент» сложнее ответить, чем для классического кода: скиллы часто переписываются итеративно, разделение между «методология» и «реализация» размыто, и без специального инструмента состояние через квартал восстановить тяжело.
Главное, что закрывает паттерн: subjective drift — когда мнение о собственной архитектуре расходится с её реальным состоянием. Метрики не дают забыть, что было переписано пять раз; интерпретация поверх не даёт утонуть в одних числах. Гибрид работает лучше каждого слоя по отдельности.
Это не замена ревью или ретроспективы; это дешёвый промежуточный артефакт, который можно прогонять ежеквартально на любом критичном каталоге и хранить как baseline. Через квартал — следующий прогон, видна дельта без специальных инструментов.
Нужно такое зеркало для вашего кода?
Архитектурные аудиты, опирающиеся на числа, а не на ощущения. Governance скиллов, AI-driven engineering practices, выдерживающие квартальный пересмотр.
Написать письмоЕщё разборы
Серия инженерных разборов: реальная задача → методика → работающий артефакт → честный анализ того, где он ломается.
К серии →