name: code-quality description: Жёсткая оценка качества Unity-кода (пакета/системы/подсистемы) по канонным формулам с архитектурным множителем. Триггеры — «оцени качество кода», «прогони по критериям оценки», «проверь пакет на качество», «оцени систему X». Размер кода определяет режим: детальный анализ (≤3000 LOC), детальный по подгруппам (3000–12000 LOC при естественном делении на 2–4 равные группы), частичный (≤12000) или субъективный обзор (>12000). Баллы не смягчаются — объём фикса считается отдельно.

Оценка качества кода (Unity)

Платформа: Claude Code (CLI от Anthropic). Размещать в ~/.claude/skills/code-quality/SKILL.md (user-level) или .claude/skills/code-quality/SKILL.md (project-level). Использует инструменты Agent (subagent), Bash, Grep, Read, Edit. На других LLM-платформах (Cursor, Continue, OpenAI Assistants) требует адаптации формата делегирования.

Скилл самодостаточен: канон, формулы, маркеры, чек-листы и итоговая формула — всё прописано ниже. Внешние ссылки не нужны.

На вход — целевой пакет/система/подсистема. На выходе — оценка по формулам с архитектурным множителем + чек-лист маркеров и red-flag'ов, либо явное предупреждение о невозможности применить канон.


0. Триггер и проверка контекста

Запускается, когда пользователь просит оценить качество кода конкретного пакета/системы.

Если цель не указана явно — уточнить:

«Какой пакет/система оценивается? Канон применим к одному пакету ≤3000 LOC; для большего объёма буду предупреждать о деградации точности.»

Без указания цели скилл НЕ выполняется.


1. Шаг 0 — измерение размера (всегда)

Перед любым анализом измерить LOC всех релевантных .cs файлов.

Что считается в LOC

  • Включаем: .cs файлы в целевой папке.
  • Исключаем: *.Designer.cs, любые сгенерированные файлы (содержат маркер // <auto-generated>), тестовые сборки (*Tests*, *.Tests.*, Editor/Tests/), .meta-файлы.
  • LOC = wc -l (с пустыми строками и комментариями). Для гейтинга порога этого достаточно — точная NCLOC не нужна.

Команды (по платформе)

bash / git-bash / WSL:

find <target-path> -name "*.cs" \
  -not -name "*.Designer.cs" \
  -not -path "*/Tests/*" -not -path "*Tests*" \
  -exec wc -l {} + | tail -1

PowerShell (Windows):

Get-ChildItem -Path <target-path> -Filter *.cs -Recurse |
  Where-Object {
    $_.Name -notmatch '\.Designer\.cs$' -and
    $_.FullName -notmatch '[\\/](Tests|.*Tests.*)[\\/]'
  } |
  Get-Content | Measure-Object -Line

Делегирование подсчёта

Подсчёт LOC выполнять через дешёвого агента, не в основном контексте:

  • Запустить Agent с subagent_type=Explore (или general-purpose с model=haiku).
  • Задача: «Посчитай суммарный LOC всех .cs файлов в <target-path> с исключениями выше. Верни одно число.»

Это экономит контекст основной сессии — инвентаризация не должна занимать его место.


2. Развилка по размеру

Развилка идёт только по LOC. Количество asmdef/папок верхнего уровня не влияет: один пакет может состоять из нескольких сборок, и при малом объёме его всё равно можно проанализировать детально.

Ветка A — детальный анализ

Условие: ≤3000 LOC.

→ Раздел 3 (полный канон + протокол).

Ветка A+ — разбиение на подгруппы под порог детального анализа

Условие: 3000–12000 LOC, и подсистема естественно делится на 2–4 смысловые группы, каждая ≤3000 LOC.

Жёсткие ограничения деления:

  • Не более 4 групп (выше — дилюция сигнала через усреднение делает оценку недостоверной).
  • Размеры групп сопоставимы: max_LOC / min_LOC ≤ 3. Иначе крупная группа доминирует, мелкие тонут в среднем — переходить к ветке B.
  • Границы естественные (типы акторов, бэкенды/реализации, ядро/контракты/реализации). Если деление искусственное (раздробит классы одной зоны ответственности) — ветка B.

Протокол:

  1. Предложить разбиение с LOC по группам, проверкой соотношения size_ratio и обоснованием границ. Дождаться подтверждения пользователя.
  2. По каждой группе провести полный детальный анализ ветки A (раздел 3) — параллельно через Agent с model=sonnet.
  3. Свести результаты:
    • Категории 1–4 — среднее арифметическое баллов по группам.
    • Архитектурные маркеры — объединение с дедупом по типу маркера (одна и та же «Ложная точка расширения» в G1/G2/G3 = 1 единица в arch, но в отчёте перечислить все проявления). Повторение одного типа в нескольких группах не умножает счётчик — каждый тип засчитывается один раз.
    • Red-флаги — аналогично с дедупом по типу, без мультипликации за совпадение в подсистемах.
    • Yellow-флаги — объединение, информационно.
  4. Применить итоговую формулу к усреднённым категориям с объединёнными arch/red (раздел 3.7).

→ Раздел 4.

Ветка B — частичный анализ (с предупреждением)

Условие: 3000–12000 LOC, разбиение невозможно или нецелесообразно (искусственные границы, неравные размеры, >4 групп).

→ Раздел 5.

Ветка C — субъективный обзор (с предупреждением)

Условие: >12000 LOC.

→ Раздел 6.


3. Ветка A — детальный анализ (полный канон)

3.1 Порядок чтения (критичен)

  1. Сначала интерфейсы и базовые классы — здесь живут архитектурные проблемы.
  2. Затем реализации — на соответствие контрактам.
  3. Затем endpoint-классы — поля (с суммированием по иерархии), цикломатика.
  4. В конце — количественные метрики по формулам.

Не начинать с подсчёта файлов и LOC как самоцели — это создаёт иллюзию прогресса при нулевом анализе. Инвентаризация (Шаг 0 + сбор счётчиков) — параллельно с чтением, как сбор данных для формул.

Делегирование инвентаря (Haiku): перед чтением запустить Agent с subagent_type=Explore или general-purpose с model=haiku со сводной задачей:

  • сосчитать число .cs файлов, классов, интерфейсов, структур, enum'ов;
  • для каждого public interface вернуть имя и число членов ({ get, void , Task , UniTask );
  • вернуть строки документации (///-комментарии) с суммой;
  • вернуть в виде таблицы.

Не делегировать: классификацию классов на endpoint / нейтральный / с логикой и подсчёт цикломатики по методам — это семантические задачи, выполняются основным контекстом по чтению кода.

3.2 Глубокое чтение (обязательное)

  • Каждый публичный интерфейс — на ISP (число членов, смешение ответственностей).
  • Каждый as ConcreteType — на ложную расширяемость.
  • Каждый async void — красный флаг (исключения: Unity event-handlers, input-handlers).
  • Каждый endpoint-класс — поля с суммированием базовых классов.
  • Каждый override — не пустая ли заглушка.

3.3 Pre-check реальной расширяемости (перед скорингом 2.x)

Наличие интерфейса и virtual-методов само по себе не означает расширяемости. Перед скорингом 2.1–2.3 прогнать grep'ы.

Плейсхолдеры: <ConcreteServiceName> — подставить реальное имя класса оцениваемого сервиса (например, UIManager, ScriptPlayer). Прогон с буквальной строкой ConcreteServiceName бессмысленен.

bash:

grep -rE "class \w+ : <ConcreteServiceName>\b" <target-path>   # есть ли наследники?
grep -rE "GetService<<ConcreteServiceName>>" <target-path>     # ходят через concrete?
grep -rE "GetService<I[A-Z]\w+>" <target-path>                 # vs через интерфейс?
grep -rE "InitializeAtRuntime\([^)]*typeof\(" <target-path>    # механизм подмены: typeof(Concrete)?
grep -rE "OverrideService\b" <target-path>

PowerShell:

Select-String -Path <target-path>\*.cs -Pattern "class \w+ : <ConcreteServiceName>\b" -Recurse
Select-String -Path <target-path>\*.cs -Pattern "GetService<<ConcreteServiceName>>" -Recurse
Select-String -Path <target-path>\*.cs -Pattern "GetService<I[A-Z]\w+>" -Recurse
Select-String -Path <target-path>\*.cs -Pattern "InitializeAtRuntime\([^)]*typeof\(" -Recurse
Select-String -Path <target-path>\*.cs -Pattern "OverrideService\b" -Recurse

Делегирование: все grep-подсчёты — через дешёвого агента (Haiku) с задачей «прогони шаблоны и верни числа совпадений по каждому». В основном контексте остаются только цифры, не сырые матчи.

Интерпретация:

  • 0 наследников + 0 ходов через concrete → расширяемость декларирована, не валидирована (фасадная). Балл по 2.1, 2.3 не выше минимума.
  • ходы через concrete > 0 одновременно с интерфейсом → концертная связность утечкой через интерфейсный фасад.
  • механизм подмены работает по typeof(ConcreteClass) → единица замены — класс, не интерфейс (см. маркер «Кустовая обязательность контрактов»).

3.4 Применение формул (жёстко)

Каждое снижение балла привязано к посчитанному счётчику нарушений с ссылкой на файл/строку. Если данных нет — n/a, нужны данные, не аппроксимировать.

Категория 1. Документируемость (0–10)

Критерий Формула Баллы
1.1 Строки документации / системы 4 × log₁₀(docs_lines / systems + 1) / log₁₀(1000) 0–4
1.2 Комментарии / объекты Норма: 1/8 для полей/методов, 1/3 для классов 0–3
1.3 Контракты и примеры 3 × min(1, detailed_docs × completeness / systems) 0–3

Делегирование (Haiku): подсчёт docs_lines (строки ///), числа классов, методов, полей — через Agent с subagent_type=Explore или general-purpose с model=haiku. Возвращать только числа.

Категория 2. Масштабируемость и гибкость (0–10)

Pre-check (раздел 3.3) обязателен ДО выставления баллов 2.1–2.3.

Критерий Формула Баллы
2.1 Нейтральные классы / всего (без endpoint) 1.5 × log₁₀(neutral / (total - endpoint) × 100 + 1) / 2 0–2.5
2.2 Атомарность (среднее полей на endpoint) 2 × log₁₀(30 / avg_fields), норма = 3–5 полей 0–2
2.3 Чистота паттернов 1.5 × clean_patterns / detected_patterns 0–1.5
2.4 Однонаправленность ссылок 2 × (1 - violations / total_dependencies) 0–2
2.5 Глубина наследования (DIT) 1 × max(0, 1 - max(0,(avg_depth - 1) / 4)) 0–1
2.6 Цикломатическая сложность 1 × max(0, 1 - max(0,(avg_complexity - 5) / 15)) 0–1

Определения:

  • endpoint-класс: класс без ссылок на другие классы с логикой (чистые модели допустимы), кроме компонентов движка (Button, Text, Image и т.п.). Render-классы, работающие с GUIStyle/EditorGUI, в endpoint не входят.
  • нейтральный класс: класс без ссылок на другие классы с логикой, кроме ссылок через интерфейсы или на чистые модели данных. Шины данных-синглтоны считаются как 0.5.
  • DIT (Depth of Inheritance Tree): количество уровней наследования от базового класса.
  • Цикломатическая сложность: количество независимых путей в методе (if/else/switch/for/while/catch/?:).

Важно:

  • При проверке чистоты паттернов обязательно проверять сброс Lazy-кешированных параметров.
  • Кол-во полей в классе считать с учётом предков (суммирование по иерархии).

Делегирование (по уровню):

  • Haiku — число public interface с количеством членов (Fat Interface ≥15), DIT по цепочкам class X : Y (без MonoBehaviour/ScriptableObject).
  • Sonnet — подсчёт полей в endpoint-классах с суммированием по иерархии: задача требует разворачивания цепочки : BaseClass и сложения. Запускать через Agent с model=sonnet.
  • Не делегировать — классификацию классов (endpoint / нейтральный / с логикой) и подсчёт цикломатики по методам. Это семантические задачи, выполняются основным контекстом.

Категория 3. Поддерживаемость (0–10)

Критерий Формула Баллы
3.1 Средний размер класса (LOC) 2 × min(1, max(0, 1 - log₂(avg_lines / 100) / 3)) 0–2
3.2 Системы / классы (без интерфейсов и моделей) Чем больше — тем лучше 0–2
3.3 Системы / константы min(1.5, 1.5 × max(0,(1.3 - (constants / system)^(1/3)))) 0–1.5
3.4 Документируемость % 2 × (doc_score / 10) 0–1.5
3.5 Дублирование кода (DRY) 2 × max(0, 1 - duplicate_blocks / total_methods) 0–2
3.6 Магические значения 1 × max(0, 1 - magic_numbers / (systems × 10)) 0–1

Определения:

  • duplicate_blocks: блоки ≥5 строк с совпадением ≥80%.
  • magic_numbers: числовые литералы кроме 0, 1, -1, 2.

Делегирование (Haiku): подсчёт magic_numbers (regex [^0-9][3-9][0-9]*[^0-9f]|[^0-9][0-9]{2,}), числа const-объявлений на систему, средний LOC класса. Также — запуск npx jscpd --min-lines 5 --threshold 80 и парсинг отчёта в число duplicate_blocks / total_methods.

Категория 4. Производительность архитектуры (0–10)

Критерий Формула Баллы
4.1 Аллокации в hot path 4 × max(0, 1 - new_in_update / update_methods) 0–4
4.2 Кеширование GetComponent 3 × (cached_getcomponent / total_getcomponent) 0–3
4.3 String операции в циклах 3 × max(0, 1 - string_concat_in_loops / total_loops) 0–3

Определения:

  • new_in_update: использование new внутри Update/FixedUpdate/LateUpdate/OnGUI.
  • cached_getcomponent: GetComponent вызванный в Awake/Start/OnEnable и сохранённый в поле.
  • total_getcomponent: все вызовы GetComponent.
  • string_concat_in_loops: конкатенация строк (+, +=) внутри for/while/foreach.

Делегирование (Haiku): new_in_update, total_getcomponent, cached_getcomponent (с проверкой контекста — Awake/Start/OnEnable vs Update/FixedUpdate/LateUpdate), string_concat_in_loops. Все четыре — детерминированный grep с -A/-B контекстом + классификация по методу-контейнеру. Возвращать только числа.

3.5 Прогон чек-листов (Шаги 2 и 3 канона)

Маркеры архитектурных проблем (подсчёт arch)

По каждому маркеру — явный grep или ссылка на код, подтверждающие наличие.

Ложная точка расширения

  • Условие: есть публичный интерфейс IA, реализация A работает с конкретным классом B без интерфейса IB.
  • Признак: as ConcreteClass после загрузки «расширяемого» компонента, при этом ConcreteClass обязательно реализует интерфейс IB.
  • Проблема: подмена верхнего уровня бесполезна без переписывания внутренностей.

Ложная точка расширения (детализация)

  • Класс A (с интерфейсом IA) использует класс B напрямую, хотя B имеет интерфейс IB в той же системе.
  • Ключевой вопрос: «Есть ли у B интерфейс в этой системе?»
    • Да, есть IB → использование B вместо IB = ПРОБЛЕМА.
    • Нет интерфейса → прямая зависимость = честная связка (ОК).
    • B это endpoint движка (Image, Button) → всегда ОК.

Кустовая обязательность контрактов

  • Условие: класс A декларирован как реализация IA, но фактически реализует ещё несколько публичных контрактов IB, IC, ID, потребляемых другими сервисами напрямую. Их обязательность не зафиксирована в IA.
  • Признак (механический):
    • Концерт-класс A реализует более одного публичного интерфейса.
    • grep -rE "GetService<I[A-Z]\w+>" находит обращения через IB, IC, ID помимо IA.
    • Механизм подмены движка работает по typeof(A) (концерт), не по typeof(IA).
    • Документация IA не упоминает обязательность IB/IC/ID.
    • A экспонирует public-state в формате конкретного типа (например Stack<T>, Dictionary<K,V>) с неявным сериализационным контрактом.
  • Проблема: «один интерфейс снаружи» — фактически куст из N контрактов внутри. Заместитель обязан реализовать ВСЕ, и список нигде не зафиксирован.
  • Лечение: либо вынести IB/IC/ID в IA (расширить декларацию), либо ввести явный композитный контракт IService = IA + IB + IC.

Смешение Model/View

  • Признак: интерфейс содержит и Position/Rotation/Scale, и бизнес-данные.
  • Проблема: нельзя реализовать логику без реализации визуала.

Fat Interface

  • Признак: 15+ членов в интерфейсе.
  • Проблема: нельзя реализовать часть — только всё или ничего.

Перегруженность настроек

  • Признак: 20+ полей в Configuration/Metadata классе.
  • Проблема: невозможно понять взаимосвязи без документации.

Низкая Discoverability

  • Признак: базовая операция требует неочевидного пути.
  • Пример: «Перейти к следующей реплике» → InputManager.GetContinue().AddObjectTrigger(gameObject).
  • Проблема: решение нельзя найти через автодополнение или интерфейсы.

Критические red-flag'и (подсчёт red)

  • [ ] Цикломатическая сложность >20 в любом методе.
  • [ ] DIT >5 уровней.
  • [ ] Fat interface >15 членов.
  • [ ] new/Instantiate в Update без пула.
  • [ ] GetComponent в каждом кадре.
  • [ ] >30 полей в endpoint-классе.
  • [ ] async void на публичных методах (исключения: Unity event-handlers и обработчики input — там это вынужденная сигнатура).

Делегирование (Haiku) — детектирование маркеров с регулярной сигнатурой:

  • async void на публичных методах: public\s+async\s+void\s+\w+.
  • Fat Interface: для каждого public interface посчитать число { get/void /Task /UniTask между { и }, вернуть имена с count ≥15.
  • Пустые override-заглушки: override\s+\w+\s+\w+\([^)]*\)\s*\{\s*\} и =>\s*base\.\w+\([^)]*\);.
  • DIT-цепочки: class \w+ : \w+ с фильтром MonoBehaviour/ScriptableObject, построение цепочек до >5 уровней.
  • new/Instantiate в Update-семействе и GetComponent в Update — через grep с -A 30 контекстом.

Не делегировать: цикломатику по методам и идентификацию endpoint-классов с >30 полей (нужно знание классификации классов из 3.4).

Жёлтые флаги (для информации, в множитель НЕ идут — учтены в базовом балле через 3.5/3.6/4.x)

  • [ ] Пустые XML-теги документации.
  • [ ] Магические числа без констант.
  • [ ] Дублирование >10%.
  • [ ] String конкатенация в циклах.
  • [ ] Статические контроллеры с состоянием.
  • [ ] Пустые override-заглушки (override void Foo() { } или => base.Foo() без изменений) — маркер раздутого базового класса.

3.6 Быстрые grep'ы (использовать через дешёвого агента)

Делегирование: прогон этих grep'ов — через subagent_type=Explore или general-purpose с model=haiku. Возвращать только числа совпадений, не сырые матчи.

bash:

# Ложные точки расширения
grep -rE "as [A-Z][a-z]+[A-Z]" <target-path>

# Перегруженность
grep -rc "\[SerializeField\]" <target-path>
grep -rc "\[Tooltip\]" <target-path>

# Fat interfaces
grep -rA 50 "public interface I" <target-path> | grep -c "{ get\|void \|UniTask "

# Магические строки
grep -rc "const string" <target-path>

# Скрытые зависимости
grep -rc "Engine.GetService\|GetServiceOrErr" <target-path>

# Глубина наследования
grep -rE "class \w+ : \w+ " <target-path> | grep -v "MonoBehaviour\|ScriptableObject"

# Цикломатическая сложность (приблизительно)
grep -rc "if\|else\|switch\|case\|for\|while\|foreach\|catch\|\?.*:" <target-path>

# Аллокации в Update
grep -rn "void Update\|void FixedUpdate" -A 30 <target-path> | grep "new \|Instantiate"

# GetComponent без кеширования
grep -rn "void Update\|void FixedUpdate" -A 30 <target-path> | grep "GetComponent"

# String concat в циклах
grep -rB5 -A5 "for\|while\|foreach" <target-path> | grep '\".*+\|+=.*\"'

# Дублирование (внешний инструмент)
npx jscpd --min-lines 5 --threshold 80 <target-path>

PowerShell:

# Замена grep -rc:
(Select-String -Path <target-path>\*.cs -Pattern "\[SerializeField\]" -Recurse).Count
(Select-String -Path <target-path>\*.cs -Pattern "const string" -Recurse).Count
(Select-String -Path <target-path>\*.cs -Pattern "Engine\.GetService|GetServiceOrErr" -Recurse).Count

# Глубина наследования (без MonoBehaviour/ScriptableObject):
Select-String -Path <target-path>\*.cs -Pattern "class \w+ : \w+ " -Recurse |
  Where-Object { $_.Line -notmatch "MonoBehaviour|ScriptableObject" }

# async void поиск (red-flag):
Select-String -Path <target-path>\*.cs -Pattern "public\s+async\s+void\s+\w+" -Recurse

3.7 Итоговая формула

Базовый = (D/10 + S/10 + M/10 + P/20) / 3.5 × 10
Итог    = Базовый × 0.5^arch × 0.9^red

Правило дизамбигуации: проблема, упомянутая и в архитектурных маркерах, и в критических red-flag'ах (например Fat Interface), считается один раз — в более тяжёлой категории (архитектурной).

Yellow-флаги отдельным множителем не учитываются — они уже заложены в базовый балл через формулы 3.5 (DRY), 3.6 (магические числа), 4.x (perf-аллокации).

Делегирование (Haiku): агрегацию arch и red после того, как маркеры выписаны в отчёт, можно поручить дешёвому агенту с задачей «пройди по списку выписанных маркеров, верни два числа: сколько архитектурных, сколько red-flag, с учётом правила дизамбигуации». Применение самой формулы — основной контекст.

Чувствительность множителя

arch red Множитель
0 0 1.000
0 1 0.900
0 2 0.810
0 3 0.729
1 0 0.500
1 1 0.450
2 0 0.250
2 1 0.225
3 0 0.125

Каждая архитектурная проблема — пополам, каждый red — минус 10%. Архитектурный штраф резкий намеренно: эти проблемы не лечатся коммитом.

3.8 Шкалы оценки (для интерпретации формул)

Атомарность (полей на endpoint):

Полей Восприятие Балл
3–5 Понятно с первого взгляда 1.5–2.0
10 Нужно вчитаться 1.0
20 Нужна документация 0.4
30+ Архитектурный провал 0

DIT:

Уровней Оценка Балл
1–2 Хорошо 0.75–1.0
3–4 Допустимо 0.25–0.5
5+ Проблема 0

Цикломатическая сложность:

Сложность Оценка Балл
1–5 Простой метод 1.0
6–10 Умеренный 0.5–0.75
11–20 Сложный 0.1–0.4
20+ Требует рефакторинга 0

3.9 Эталонные значения

Метрика Плохо Норма Хорошо
Полей на endpoint 30+ 10–15 3–5
Членов в интерфейсе 15+ 7–10 3–5
Классов на систему 50+ 15–25 5–10
LOC на класс 500+ или <10 150–250 или 20–40 50–100
Констант на систему 20+ 5–10 1–3
DIT 5+ 3–4 1–2
Цикломатика 20+ 6–10 1–5
Дублирование >20% 5–10% <5%
new в Update >50% 10–30% 0%

3.10 Sanity check

  • Сравнить Базовый и Итог.
  • Перечитать выписанные нарушения. Каждое нарушение должно быть привязано к конкретному файлу/строке — иначе оценка невалидна.
  • Большой разрыв Базовый ↔ Итог = архитектурные дефекты, локально невидимые. Это обязательно вывести в финальном блоке отдельным комментарием «Диагностический сигнал», см. формат вывода (раздел 6).

3.11 Что нельзя оценить по коду (вынести в финальный блок)

Метрика Как получить
Time to feature Засечь на реальной задаче
Time to change Попросить изменить неожиданное
Onboarding time Дать новому разработчику задачу
Bug density Статистика за месяцы
Реальная расширяемость Попытаться расширить

4. Ветка A+ — разбиение на подгруппы

Условие: 3000–9000 LOC, естественное деление на 2–3 группы ≤3000 LOC каждая.

4.1 Предложение разбиения

Перед запуском анализа вывести структуру разбиения с обоснованием:

Предлагаю разбить <target> (X LOC) на N групп:
- Группа 1 — <название> (Y₁ LOC): <состав, обоснование>
- Группа 2 — <название> (Y₂ LOC): <состав, обоснование>
- Группа 3 — <название> (Y₃ LOC): <состав, обоснование>

Сумма: Y₁+Y₂+Y₃ = X ✓ (все < 3000)

Каждая группа = отдельный полный анализ по канону (ветка A).
Итоговые баллы — среднее по 3 группам, флаги — объединение с дедупом.
Подтвердить?

Дождаться подтверждения. Без подтверждения — не запускать.

Критерий «естественности» деления:

  • группы соответствуют разным зонам ответственности (ядро vs контракты vs реализации; разные типы данных; разные бэкенды);
  • классы одной зоны ответственности не разрезаются между группами (если ManagerX в G1, MetadataX и StateX тоже в G1);
  • базовые классы и контракты идут в более раннюю группу, реализации — в более позднюю.

Если группы неровные (одна 2900, другие по 100) — деление искусственное, лучше ветка B.

4.2 Параллельный запуск

Agent (subagent_type=general-purpose, model=sonnet) × N
  prompt: «Полный анализ группы по канону code-quality (раздел 3 SKILL.md):
           файлы [...], шаги 0–5, формат вывода — структурированный markdown
           с разделами Инвентарь / Формулы / Pre-check / Архитектурные маркеры
           / Red / Yellow / Итог»
  run_in_background: true

Каждый агент должен вернуть по канону раздела 3 полный отчёт со всеми категориями и обнаруженными маркерами. Никаких сокращений.

4.3 Сведение результатов

Усреднение баллов по категориям

Документируемость = avg(D₁, D₂, …, Dₙ)
Масштабируемость  = avg(S₁, S₂, …, Sₙ)
Поддерживаемость  = avg(M₁, M₂, …, Mₙ)
Производительность = avg(P₁, P₂, …, Pₙ)

Базовый = (D/10 + S/10 + M/10 + P/20) / 3.5 × 10

Дедуп архитектурных маркеров

Маркер идентифицируется типом (Ложная точка расширения, Кустовая обязательность, Смешение Model/View, Fat Interface, Перегруженность настроек, Discoverability), не конкретным проявлением.

  • Если один и тот же тип маркера найден в G1, G2, G3 — это 1 маркер в arch, но в отчёте перечислить все проявления.
  • Если в одной группе Fat Interface — это IActor, в другой — ITextPrinterActor, это всё равно 1 маркер «Fat Interface», просто с двумя проявлениями.
  • Никакой systemic-поправки нет: повторение типа в нескольких группах счётчик не умножает.

Дедуп red-флагов

Аналогично — по типу (cyclo>20, DIT>5, Fat Interface, new в Update, GetComponent в кадре, >30 полей в endpoint, async void). Без мультипликации за повтор.

Yellow-флаги

Объединение всех проявлений по группам без дедупа (информационно). В множитель не идут.

4.4 Итоговая формула

Итог = Базовый × 0.5^arch_dedup × 0.9^red_dedup

Где arch_dedup, red_dedup — счётчики после дедупа по типам.

4.5 Формат вывода

[Ветка A+] Сводная оценка <target>
- LOC: <total>
- Группы: G1 (Y₁), G2 (Y₂), G3 (Y₃)
- Применимость канона: полная (по подгруппам)

## Усреднённые баллы
| Категория | G1 | G2 | … | Среднее |
| ... |

## Объединённые арх. маркеры (дедуп)
| # | Маркер | Где зафиксирован |
| 1 | Ложная точка расширения | G1: …; G2: …; G3: … |
| ... |

## Объединённые red-флаги (дедуп)
…

## Объединённые yellow-флаги
…

## Итог
- Множитель: 0.5^arch × 0.9^red = …
- Базовый: X
- Итог: X
- Интерпретация: <по шкале>

## Диагностический сигнал
<если разрыв Базовый ↔ Итог большой — комментарий>

5. Ветка B — частичный анализ (с предупреждением)

Условие: 3000–10000 LOC.

Перед началом ОБЯЗАТЕЛЬНО вывести:

⚠️ Предупреждение: объём кода превышает порог детального анализа (>3000 LOC). Количественные метрики (формулы 1.x–4.x) применимы, но архитектурные маркеры могут быть пропущены — async void, ложная расширяемость, Fat Interface требуют построчного чтения, которое на этом объёме неполное. Оценка достоверна по формулам; маркеры — выборочно.

Затем:

  1. Применить формулы количественно (раздел 3.4 — все категории 1.x–4.x).
  2. Маркеры архитектурных проблем — прогнать выборочно через grep'ы (раздел 3.3 + 3.6):
    • async void в публичных методах.
    • Интерфейсы с >15 членами.
    • Цепочки as ConcreteType.
    • Прямые вызовы GetService<Concrete> минуя интерфейс.
  3. Red-flag чек-лист (раздел 3.5) — только grep-проверяемые пункты.
  4. Итоговую формулу (раздел 3.7) применять, в выводе явно маркировать «оценка частичная, маркеры могут быть пропущены».

Делегирование: все grep-прогоны и подсчёты — через дешёвого агента (Explore или general-purpose с model=haiku). Основной контекст агрегирует только числа.


6. Ветка C — субъективный обзор (с предупреждением)

Условие: >10000 LOC.

Перед началом ОБЯЗАТЕЛЬНО вывести:

Предупреждение: объём кода (X LOC) превышает порог применимости канона. На этом объёме LLM скатывается в каталогизацию вместо анализа: пропускает async void, ложную расширяемость, Fat Interface, скрытые контракты. Корректная оценка по формулам невозможна в одну сессию.

Доступно только субъективное мнение об архитектуре — без чисел, без формул, без баллов. Это НЕ оценка качества по канону.

Для корректной оценки — разбить скоуп на отдельные пакеты ≤3000 LOC и оценивать каждый по очереди.

Затем спросить:

«Продолжить с субъективным обзором (с явной маркировкой что это мнение, не оценка), или предпочтёте разбить на пакеты для детального анализа?»

Если пользователь выбрал субъективный обзор:

  1. Не выводить никаких числовых оценок по формулам канона.
  2. Дать качественные наблюдения по архитектуре с явной шапкой «Субъективный обзор, не оценка по формулам».
  3. Опционально перечислить grep-обнаруживаемые красные флаги (с пометкой что это не полная картина) — через дешёвого агента (раздел 3.6).
  4. Финальный вывод — рекомендация какие подсистемы стоит оценить детально по канону отдельно.

7. Формат вывода

Вне зависимости от ветки.

Шапка

[Ветка X] Оценка <target>
- LOC: <total>
- Применимость канона: [полная / частичная / субъективная]

Тело

  • Ветка A: полная оценка по формулам с расчётами и ссылками на файлы.
  • Ветка B: формулы + выборочные маркеры + явное «оценка частичная».
  • Ветка C: только качественные наблюдения с шапкой «Субъективный обзор, не оценка».

Финальный блок

Ветка A:

  • Базовый, arch и red счётчики, Итог, интерпретация по шкале (8–10 / 6–8 / 4–6 / 2–4 / 0–2).
  • Диагностический сигнал — отдельный комментарий о разрыве Базовый ↔ Итог:
    • если разрыв большой (Итог < 0.6 × Базовый): «Высокий Базовый при низком Итоге — код локально опрятен, но имеет фундаментальные архитектурные дефекты, которые осреднённые метрики не ловят. Этот разрыв — повод смотреть подсистему прицельно, а не доверять одному числу.»
    • если разрыв малый: «Базовый и Итог близки — архитектурных штрафов мало, оценка устойчива.»
  • Список «что нельзя оценить по коду» (раздел 3.11) — при необходимости.

Ветка B:

  • Базовый, выборочные маркеры, предупреждение о неполноте.
  • Если посчитан Итог — добавить тот же диагностический комментарий, что в ветке A.

Ветка C:

  • Рекомендация декомпозировать для детального анализа.

Интерпретация шкалы итога

  • 8–10 — Отличная архитектура.
  • 6–8 — Хорошая, с minor проблемами.
  • 4–6 — Приемлемая, технический долг копится.
  • 2–4 — Проблемная, сложно поддерживать.
  • 0–2 — Критическая, требует переписывания.

8. Жёсткие правила

  • Применение формул — жёсткое. Балл не корректируется в сторону смягчения «потому что фикс будет дорогой» или «потому что эффект мал». Объём фикса — отдельная задача после оценки.
  • Субъективное — отдельно. В ветке A — формулы. В ветке C — мнение. В ветке B — формулы плюс явное предупреждение что часть маркеров может быть пропущена. Не смешивать.
  • Не оценивать «расширяемость» по наличию interface + virtual + hooks без проверки реальных подмен через grep (раздел 3.3).
  • Каждое снижение балла — привязано к посчитанному counted violation с ссылкой на код. Иначе n/a.
  • Не подменять анализ инвентаризацией (LOC, число файлов, namespace-таблицы — это данные, не оценка).
  • Если паттерн чистый по чек-листу — балл максимальный, не «0.7 на ощупь».
  • Подсчёт LOC и grep-метрик — через дешёвого агента (Haiku, subagent_type=Explore или general-purpose). Основной контекст занимать инвентаризацией нельзя.
  • Хорошая документация плохой архитектуры ≠ хороший код — документация объясняет как обойти проблемы, а не почему их нет.