# Zodchiy

> Архитектурный аудит по трём осям: намерение, структура, поведение git-истории. Находка допускается, только когда измерена её цена. Вызывать при «разбери архитектуру», «где техдолг», «что рефакторить», «оцени кодовую базу», «изучи чужой репо», «стоит ли переписывать», перед крупным рефакторингом и при планировании миграции. Сюда же — жалобы на цену изменения, в которых слова «архитектура» нет: боимся трогать модуль, страшно менять, почему тут больно менять, одна правка задевает соседние файлы, каждый релиз ломается смежное. НЕ для: ревью диффа перед коммитом (code-review), поиска уязвимостей (fynd-dyrka) — даже когда речь о платежах и деньгах, отладки конкретного отказа (systematic-debugging), внешнего ресёрча (deepdive), UI (design-modern).

- Skill: `socialpranker/zodchiy` (Agent Skill, multi-file: 9 files)
- Install (CLI): `npx skillmds@latest add socialpranker/zodchiy`
- Raw SKILL.md: https://api.skillmd.com/api/skills/socialpranker/zodchiy/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Socialpranker (https://skillmd.com/u/socialpranker)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/socialpranker/zodchiy

---


# Зодчий — архитектурный аудит с измеримой материальностью

**Тезис: материальность измеряется, а не утверждается.** Остальные инструменты
объявляют «цикл — находка, только если он чего-то стоит», но стоимость взять
неоткуда: граф импортов её не содержит. Здесь она берётся из git-истории и
служит условием допуска находки в отчёт.

Полный дизайн — `SPEC.md`. Ниже — как исполнять.

## Железное правило

```
Число — из скрипта. Суждение — из модели. Не наоборот.
Ни одна метрика не оценивается «на глаз»: ни fan-in, ни сложность, ни цикл.
Ни одна находка не выносится до того, как построена полная карта.
```

Нарушение — брак прогона, а не мелочь: вердикт, вынесенный до карты, тянет за
собой весь дальнейший разбор.

**Число сопровождается ссылкой.** Поле `source` находки — путь в `measure.json`
(`behavior.hotspots[file=src/x.py].fix_share`), а не проза «по данным замера».
Прозу нельзя отличить от числа, названного по памяти; путь проверяется
механически — `zodchiy.py selfcheck`. Полный перечень путей —
`references/measure_schema.md`.

**Рядом с абсолютным числом — перцентиль.** Пороги подобраны на двух
репозиториях и на третьем поплывут. `fix_share 0.43` не переносится между
проектами, `верхние 7% этого репозитория` переносится. Поля `*_pct` есть у
всех ранжируемых метрик.

**Бюджет чтения.** Файл читается целиком только после того, как попал в топ по
метрике (`hotspots`, `hubs`, `complex_files`, `slowest_files`, `unstable_files`).
На пятисотфайловом репозитории обратный порядок топит контекст, и разбор
скатывается к суждению по именам каталогов — ровно то, что шаг 2 запрещает.

**Модель по шагам.** Шаг 1 — модель не нужна вовсе, это скрипт. Шаг 2 —
механическое чтение, хватает средней. Шаг 4 — сильная: на слабой опровержение
превращается в вежливое согласие.

## Три оси

| Ось | Откуда | Чего НЕ видит |
|---|---|---|
| **Намерение** | ADR, CLAUDE.md, README, import-linter/ArchUnit/eslint-boundaries | врёт, когда документы отстали от кода |
| **Структура** | `scripts/structure.py` | связи через DI, реестры, рефлексию, строковые ключи |
| **Поведение** | `scripts/behavior.py` | код, который ещё не менялся |

**Допуск находки — по сходимости:**

| Осей сошлось | Статус | Судьба |
|---|---|---|
| 1 | `hypothesis` | в отчёт не идёт, ждёт следующего прогона |
| 2 | `finding` | в отчёт, с указанием недостающей оси |
| 3 | `verdict` | первым, годится в основание ADR |

Ось намерения **никогда** не перебивает исполняемое поведение. Наличие ADR не
доказывает, что так и сделано.

## Режимы

| Режим | Когда | Справочник |
|---|---|---|
| `audit` | свой проект: что болит и что чинить | ниже + `references/materiality.md` |
| `recon` | чужой/незнакомый репо: как устроен и почему так | `references/recon.md` |
| `gate` | CI: не стало ли хуже | `references/materiality.md` |
| `plan` | из находок в решения и миграцию | `references/remedy.md` |

Режим не объявлен — выведи из просьбы и назови одной строкой.

## Команда

Один вход, а не четыре скрипта. Дальше по тексту команды пишутся коротко
(`zodchiy.py gate`), полный путь — `python3 ~/.claude/skills/zodchiy/zodchiy.py`.

| Команда | Что делает |
|---|---|
| `measure <repo> --out .zodchiy/measure.json` | обе оси + калибровка; в stdout сводка, JSON в файле |
| `snapshot <measure> --out .zodchiy/baseline.json` | снимок метрик под сравнение |
| `diff <measure> --baseline <base>` | что изменилось между прогонами |
| `gate <measure> --baseline <base>` | то же + `exit 1` при регрессии — для CI |
| `add --findings <csv> --json '{...}'` | дописать находку |
| `refute --json '{...}'` | вердикт линзы опровержения (шаг 4) |
| `selfcheck --findings <csv> --measure <json>` | проверка находок перед сдачей |
| `verify --findings <csv> --measure <json>` | сверить прогноз `gain` с новым замером |
| `export --findings <csv> --measure <json> [--format sarif]` | находки машиночитаемо: JSON по схеме или SARIF |

`behavior <repo>` и `structure <repo>` гоняют одну ось — для отладки, не для
отчёта: без калибровки числа не годятся в находку.

## Деградация

Скилл переносим между харнессами, а фичи харнессов — нет. Каждая деградация
**объявляется в отчёте**. Молчаливая запрещена: она превращает «проверка была»
в неправду, и заметить это по отчёту нельзя.

| Чего нет | Что делаем | Чем платим и где это видно |
|---|---|---|
| субагентов | линзы шага 4 прогоняются последовательно, вердикт пишется с `mode: sequential` | потолок находки — `finding`; `selfcheck` вернёт `refutation.ceiling_cap` и строку `disclosure` для «слепых зон» |
| прогрессивной загрузки `references/` | справочник читается файлом по пути из таблицы ниже перед шагом, которому он нужен | ничем, если прочитан; по памяти — доктрина расходится с файлом молча |
| `tree-sitter` | разбор регулярками | `parser.backends.regex > 0`; такие файлы выпадают из метрик сложности, потолок по ним — `finding` |
| git-истории | две оси вместо трёх | `behavior.available: false`, потолок `finding` (см. `references/materiality.md` §6) |

Что каждый харнесс читает, куда класть адаптеры и чего у него нет —
`references/harnesses.md`. Факты там проверены 01.09.2026 по первоисточникам
и протухают: перед сборкой адаптеров сверяются заново, а не по памяти.

## Порядок работ — `audit`

Каждый шаг — пункт в todo. Пропуск шага объявляется вслух с причиной.

### 1. Считать
```bash
python3 ~/.claude/skills/zodchiy/zodchiy.py measure <repo> --out .zodchiy/measure.json
```
В stdout — сводка, полный JSON в файле. Читай файл прицельно (`adjacency`,
`temporal_coupling`, `hotspots`), а не целиком.

Смотри `calibration.passed` и `confidence.ceiling` **до** всего остального.
Калибровка не прошла — заблокированные метрики не дают находок, и это
проговаривается в отчёте. Потолок `finding` означает, что `verdict` в этом
прогоне недостижим в принципе.

Скрипт молчит про то, чего не может: короткая история, regex вместо дерева,
несвязный граф — всё выходит явными полями, а не тишиной.

**Чекпоинт.** `calibration.passed == false` или потолок ниже ожидаемого —
остановись и спроси, продолжать ли на оставшихся осях. Одна строка в конце
отчёта на сорок находок этого не заменяет: к тому моменту решение уже принято.

### 2. Понять — карта без вердиктов

Прочитай намерение: `CLAUDE.md`, `README`, `docs/architecture/*`, контракты
слоёв в `pyproject.toml` / `.eslintrc` / `archunit`. Прочитай ключевой код.

Построй карту: слои, модули, потоки, контракты, где чем владеют. Шаблон с
обязательными секциями — `references/map_template.md`.

**Здесь запрещено:** называть проблемы, ставить severity, предлагать лечение.
Тянет назвать — запиши в черновик находок и вернись к карте.

**Не верь именам.** Каталог зовётся `domain` — проверь, что в нём домен.
Функция зовётся `validate_*` — прочитай тело. Имя не доказывает ничего.

**Говори про ненайденное.** Границы слоёв ничем не защищены, кроме соглашения —
это утверждение, а не молчание. Помечай: `OBSERVED` / `INFERRED` / `UNKNOWN`.

### 3. Судить

Только теперь — находки. Числа берутся из `measure.json`; перечитывать код ради
смысла можно, ради измерения — нет.

Каталог рисков R1–R6 и пороги — `references/risks.md`.
Гейт материальности и Pain × Spread — `references/materiality.md`.
Правила расхождения осей — `references/axes.md`.

**Различай R2 и R3 через граф.** Пара меняется вместе И связана импортом
(`adjacency_through_barrels`) — честная зависимость, R2. Меняется вместе БЕЗ
ребра — одно решение разложено в двух местах, R3, другое лечение. Проверять
надо по графу *через barrel*: `from pkg import X` даёт ребро в `__init__.py`,
и наивная проверка объявит связь скрытой, когда она прямая.

**Цикл считай только рантаймовый.** `cycles` — настоящие. `cycles_type_only` —
`if TYPE_CHECKING:` и `import type`, они ровно для разрыва цикла и заведены.
Смешать — выдать выдуманный дефект первой строкой.

**Сложность сравнивай по функции**, не по файлу: `cyclomatic_per_function_max`,
не `cyclomatic_total`. Порог McCabe задан на функцию.

**Цена считается и во времени.** `pain × spread` — цена в пространстве.
`velocity.touch_cost` говорит, сколько мест надо тронуть на одно изменение;
`velocity.episodes.multi_commit_share` — какая доля изменений потребовала
доделок. Это и есть «больно менять», выраженное числом.

**`rework_rate` — не то же, что `fix_share`.** `fix_share` говорит «файл часто
чинят» (сложное место). `stability.rework_rate` — «правки этого файла не
держатся» (обратной связи нет: ловит не тест, а пользователь). Диагнозы разные,
лечение разное. Ранжировать по `rework_rate_lb`, не по сырой доле: «5 из 5»
иначе обгоняет «55 из 67» и первой строкой отчёта идёт шум.

### 4. Опровергнуть

Каждая находка со статусом `verdict` и каждая с приоритетом ≥6 идёт на
опровержение — **разными линзами**, а не копиями одного промпта: N одинаковых
агентов дают одно мнение по цене N. Четыре линзы, что каждая читает и чем
убивает находку — `references/refutation.md`.

Как физически шли линзы — независимо или одним проходом — объявляется полем
`mode`, а не подразумевается: см. «Деградация» выше и `references/refutation.md`.

Вердикт линзы кладётся файлом, а не пересказывается:

```bash
zodchiy.py refute --json '{"finding_id":"F1","lens":"L4","verdict":"dropped",
                          "reason":"...","mode":"parallel"}'
```

Не устояла — вниз по статусу или прочь.

### 5. Отчёт

Открывается строкой `Чеклист: X/Y`. Незакрытые пункты перечисляются с причиной
и следствием — тихий пропуск запрещён.

Порядок: снимок (remote, ветка, HEAD, чистота дерева, дата) → калибровка и
потолок → карта → находки по приоритету → **слепые зоны**.

Секция «слепые зоны» обязательна и собирается механически, не пересказом:
`calibration.blocked_metrics` + файлы, разобранные регулярками
(`parser.backends.regex`) + окно истории (`snapshot.since`) + связи, которых
граф импортов не видит (DI, реестры, рефлексия, строковые ключи) + режим шага 4
(`selfcheck` → `refutation.disclosure`, если линзы шли последовательно).

**Тихое усечение запрещено.** Обрезал список топ-N — скажи, сколько отброшено.
Молчание читается как «покрыто всё», и это единственная ошибка отчёта, которую
читатель не может заметить.

Перед сдачей: `zodchiy.py selfcheck --findings ... --measure ...` — поля на
месте, `source` разрешается, находки высокого приоритета прошли опровержение.

**Отчёт — не единственный выход.** `zodchiy.py export` отдаёт те же находки
машиночитаемо: JSON по `schema/findings.schema.json` либо SARIF для CI и code
scanning. Markdown пишет модель, и его форма зависит от харнесса — сравнивать
прогоны и гейтить сборку можно только по машинному выходу. Экспорт гоняет тот
же `selfcheck` и отказывается собирать документ из брака.

### 6. Артефакты

`references/artifacts.md`: `docs/architecture/current-state.md`, ADR,
диаграммы, план миграции, `.zodchiy/findings.csv`, `baseline.json`,
`.zodchiy/verify/refutation.json`.

## Справочники

| Файл | Когда открывать |
|---|---|
| `references/measure_schema.md` | ищешь путь к числу или пишешь `source` находки |
| `references/risks.md` | шаг 3: каталог R1–R6 и разделы «что НЕ флагать» |
| `references/materiality.md` | шаг 3: гейт материальности, Pain × Spread |
| `references/axes.md` | оси разошлись — что перевешивает |
| `references/map_template.md` | шаг 2: секции карты и пометки OBSERVED/INFERRED/UNKNOWN |
| `references/refutation.md` | шаг 4: четыре линзы и запись вердикта |
| `references/remedy.md` | режим `plan`: из находок в решения |
| `references/recon.md` | режим `recon`: чужой репозиторий |
| `references/artifacts.md` | шаг 6: формы артефактов |

## Форма находки

```
id · title · risk · symptom · axes · source · cost_pain · cost_spread ·
remedy · remedy_cost · gain · gain_metric · gain_target · gain_direction ·
alternatives · refutation · confidence · priority · status
```

Четыре поля, без которых находка — брак:

- **`cost_pain`** — чем обходится сейчас, числом и ссылками на коммиты. Нет
  числа — находки нет.
- **`remedy_cost`** — во что обойдётся лечение, той же валютой. Без него линза
  «лечение дороже болезни» работает на глаз: сравнивать не с чем. Дороже
  болезни на горизонте — находка не выносится, идёт в «знаем, не чиним» с датой
  пересмотра.
- **`gain` + `gain_metric` + `gain_target`** — проверяемый прогноз в машинной
  форме. «Станет чище» — брак. `gain_metric: containment_ratio`,
  `gain_target: 0.70` — годится: `zodchiy.py verify` сверит это со следующим
  прогоном и проставит `gain_actual` и `gain_verdict`. Прогноз, который не
  ложится на метрику снимка, помечается `gain_metric: manual` — честно и
  видно, а не молча.
- **`refutation`** — какой факт снял бы находку. «Ничего не снимет, это
  очевидно» означает, что находку не проверяли.

Скилл требует измеримости от чужого кода. `zodchiy.py verify` — то же требование
к собственным советам: обещал `containment 51% → 70%`, через прогон видно, что
вышло. Без этой петли рекомендация ничем не отличается от мнения.

## Что режется гейтом

Придирки · вкусовщина в именовании · теоретическая чистота · «так принято» ·
спекулятивный масштаб («а если 10 млн пользователей») · новизна ради новизны.

Модернизация оправдана текущим дефектом или измеримым упрощением. Точечный
ремонт границы предпочтительнее переписывания, когда переписывание добавит
больше переходной сложности, чем уберёт.

## Что НЕ флагать

Разделы «что НЕ флагать» в `references/risks.md` — несущие, а не вежливые.
Проверено на живом коде: без них скилл объявил бы дефектами DI-корень с самым
высоким churn (74 правки, но 11% багфиксов — точка расширения по замыслу),
barrel с fan-in 109 и четыре «цикла», ни один из которых не существует в
рантайме.

Высокий churn при низкой доле багфиксов — точка расширения, не долг.
Смотри `hotspots[].fix_share`, а не только `edits`.

Эти четыре ложняка закреплены регрессией: `evals/` держит по паре
«нарушение / похожий, но невиновный» на каждый из них. Правишь `scripts/*.py` —
гоняй `python3 evals/run_evals.py` (юнит + сквозной прогон на мини-репозиториях,
без модели, секунды). Подробности — `evals/README.md`.

## Границы

Безопасность → `fynd-dyrka` · ревью диффа → `code-review` · внешний ресёрч →
`deepdive` · UI → `design-modern`.

Репо без git-истории или короче порога: поведенческая ось недоступна, скилл
говорит это прямо, работает на двух осях, потолок — `finding`.

