Репозиторий для автономных агентов

Harness начинается с репозитория.

Агенту предстоит ещё тысяча итераций. Репозиторий должен оставаться понятным и пригодным для изменений.

Хорошего agent loop для этого мало. Нужны правила, которые переживут смену агента, контекста и проекта.

$ harness check --verbose
FAIL  ~/shop
  harness 3.9.0 · repository pins 3.9.0
✅ complexity.csharp       0
❌ docs.policy             1
    violation  NOTES.md: unexpected
    tracked Markdown
❌ dependencies.csharp     1
    violation  module dependency cycle
    Shop.Billing -> Shop.Orders
    -> Shop.Billing
✅ duplication.csharp      0
exit 1
Сокращённый вывод: нарушение даёт код 1. Полная находка называет строки, которые замыкают цикл, а harness explain объясняет правило и исправление.

Я собрал в Harness CLI проверки из собственной практики: неочевидные вещи, которые приходится заново объяснять в каждом репозитории. Теперь эти правила можно одинаково применять после каждого изменения.

Подход уже прошёл много итераций; текущую версию я считаю готовой к релизу. Можно взять CLI или перенести сами правила в свой harness.

Строгость — часть идеи

Нормальное состояние — все применимые проверки включены и проходят.

Я добавляю проверку, потому что считаю её нужной. Если правило постоянно приходится отключать, для меня это повод пересмотреть правило.

Все проверки уже входят в CLI. Выбирать набор плагинов не нужно. Правила и настройки явно записаны в .harness.json и меняются вместе с кодом на ревью.

  • required нарушение блокирует прогон
  • advisory нарушение видно, но оно не блокирует
  • off проверка выключена целиком
  • не названа в policy — вне рамки: не запускается, в отчёте одна строка outside the frame

Любую проверку можно смягчить или отключить. Спрятать отдельную находку исключением для файла нельзя. Названная проверка несёт свои настройки полностью: скрытых дефолтов нет.

Ослабления на весь репозиторий тоже живут в рамке: generated-пути объявляются в generated с причиной, глобально выключенные правила линтеров и анализаторов — в settings.<check>.repositoryWide с id и причиной. Адресные //nolint и # noqa блокируют даже с причиной. ADR-0067 →

adrs.shape удерживает ADR в форме записи решения: непрерывные номера, дата, статус и разделы; дефолты — 1000 слов, 10 строк кода и 12 строк таблицы. Почему эта форма →

Несколько проектов в одном Git-репозитории

Корневой .harness.json регистрирует непересекающиеся каталоги: "projects": ["apps/api", "apps/web"]. Каждый содержит tracked .harness.json с собственными ответами, applicability, policy и settings. Наследования нет; версия и commit-интеграция остаются только в корне.

Корень покрывает файлы вне проектов. Проверку можно выключить или оставить advisory целиком для одного проекта; исключений для файлов внутри проекта нет. Общие tracked настройки предков доступны при чтении, но соседний код не входит в измерение.

harness check проверяет весь workspace; harness check --project apps/api — корневая область и выбранный проект после валидации всех конфигов, явно частичный прогон. --verbose показывает область. Незарегистрированный вложенный конфиг — ошибка.

Дублирование и графы измеряются локально: межпроектные повторы и зависимости не проверены. Границы проектов меняют область метрик и требуют ревью. ADR-0060.

Что включено сразу после init

init читает git-индекс и записывает только языки и стеки, у которых есть tracked-исходники (--languages заменяет детекцию для CI). Все проверки стартуют как required, включая комментарии; для языков, которых в проекте нет, в файле ничего нет. Вопрос application/library задаётся только при C#; для standalone-библиотеки стандарт архитектуры неприменим.

harness.coverage защищает от забытого стека: язык с tracked-исходниками без записи в applicability — блокирующая находка с готовым JSON-фрагментом для вставки. Отказ выражается явным applicable: false с причиной; сам файл harness не правит.

Ответы о тестах, сборке и других инструментах init оставляет пустыми; все frame.* стоят в required. Заполните ответы — это завершение настройки. Переход на advisory или off агент обсуждает с владельцем для конкретной проверки, включая комментарии.

У архитектуры восемь самостоятельных проверок с явной policy. Каждая находка блокирует при required; эвристические рекомендации удалены. Селекторы architecture и architecture.sliced-dotnet выбирают всю группу.

44 проверки · две части

Общее для репозитория. Конкретное для его стека.

Сначала — одинаковые вопросы и правила независимо от языка. Затем — проверки исходников и устройства проекта. Самый подробный набор — для C# и .NET; Go получает комментарии, повторы, длину функций, DSM по пакетам и учёт подавлений линтера; TypeScript и JavaScript — циклы импортов, повторы и DSM по файлам. Ansible — подавления линтера и циклы ролей.

01 / COMMON

Порядок в любом репозитории

Где агент читает правила, как оформляет изменения и чем проверяет результат.

Документация, ADR, коммиты, конфигурация Короткая навигация, записи решений, единая история и явные правила · 5 проверок
    Тесты и инструменты проекта Unit, integration, architecture, format, lint, build, typecheck, verify · 8 проверок

    Репозиторий сообщает, где находятся инструменты или почему их нет. Harness проверяет полноту ответа; тесты, сборку и линтеры запускает ваш verify-скрипт в CI.

      02 / LANGUAGES & STACK

      Структура кода и границы

      Языковые проверки используют ту же рамку. Если языка нет, его группы просто нет в файле; если язык появился, harness.coverage скажет, что дописать.

      C# Циклы, связанность, повторы, длина функций, один тип на файл, комментарии · 6 проверок
        .NET Сборка, пакеты, .slnx, стиль, подавления warnings · 5 проверок
          Архитектура .NET-приложения Слои и слайсы sliced-dotnet/1 · 8 проверок

            Посмотреть раскладку проекта ↓

            Go Комментарии, повторы, длина функций, DSM по пакетам, подавления линтера · 5 проверок

            Единица DSM — пакет: файлы одного пакета видят друг друга без импортов, а циклы импортов запрещает компилятор. go vet и gofmt не дублируются: рамка спрашивает, где они запускаются.

              Ansible Подавления линтера и циклы ролей · 2 проверки

              Ось обнаруживается по ansible.cfg, ролям или плейбукам. YAML-комментарии остаются отдельной проверкой; ansible-lint, syntax-check и molecule запускает verify-скрипт проекта.

                YAML, TypeScript и JavaScript Комментарии, импорты, дубли и DSM · 5 проверок

                TS/JS-ось читает tracked импорты без tsc, считает циклы каталогов и DSM по файлам с прозрачными barrels. Неразрешённые импорты видны в деталях; стартовый потолок средней достижимости — 8 файлов.

                  Подробности любой проверки: harness explain <check-id>. Идентификаторы и версия на сайте сверяются с бинарём тестом.

                  Три понятия, которые стоит разобрать

                  Где лежит код. Кто от кого зависит. Как далеко тянутся связи.

                  01. Слайсы: у каждой задачи своё место

                  Заказы — в Orders, оплата — в Billing. Слой отвечает за роль кода, слайс — за задачу продукта. Агент находит нужную часть и меняет её в заданных границах.

                  Это адаптация идей Feature-Sliced Design к .NET: разделение по смыслу, изоляция слайсов и явный публичный API. Здесь свои слои Clean Architecture и правило «слой = сборка».

                  Фрагмент src/Shop/: читаем строку как слой, колонку как слайс
                  Слой / рольOrders / заказыBilling / оплата
                  Api · HTTP-входApi/Orders/Endpoints/Api/Billing/Endpoints/
                  Application · сценарииApplication/Orders/Placement/Application/Billing/Invoicing/
                  Domain · бизнес-правилаDomain/Orders/Domain/Billing/
                  Infrastructure · адаптерыInfrastructure/Orders/Postgres/Infrastructure/Billing/Payments/
                  У каждой строки свой .csproj. Host собирает приложение; для входа через очередь есть Consumers. Слайсы лежат прямо в слое, без промежуточного Features/.

                  Обычный вход в слайс

                  Api/Orders→Application/Orders/Contracts

                  Внешний код использует объявленный API сценария. Внутреннее устройство Placement/ можно менять без обхода всех потребителей.

                  Связь с соседним слайсом

                  Application/Orders→Application/Billing/Contracts/X/Orders

                  Billing явно открывает API для Orders. Обычный Contracts/ не разрешает импорт соседа на том же слое; нужен X/Orders, аналог @x в FSD.

                  Направления слоёв и ещё несколько правил
                  Разрешённые зависимости между слоями
                  ОткудаКакие слои можно использовать
                  HostВсе остальные — собирает приложение
                  Api, ConsumersApplication, Domain
                  InfrastructureApplication, Domain
                  ApplicationDomain
                  DomainНи один другой слой
                  • Каждому Application-слайсу нужен вход в Api или Consumers. Доменные слайсы доступны верхним слоям независимо от имени слайса-потребителя.
                  • Папки внутри слайса называются по назначению: Placement, Pricing. Helpers и Utils не объясняют назначение.
                  • Общие зарезервированные места — Domain/Shared и Infrastructure/Persistence. Обвязка приложения живёт в Host.
                  • Слой с кодом имеет одну сборку. Подключать чужие слои через Compile Include нельзя; ProjectReference подчиняется той же таблице.
                  • Standalone-библиотека отмечает архитектуру как неприменимую с причиной.

                  Стандарт sliced-dotnet/1 и его обоснование →

                  02. Цикл: зависимость вернулась к началу

                  Orders использует Billing. Если Billing тоже использует Orders, модули зависят друг от друга. Их труднее менять и переносить независимо: исправление одной стороны может потребовать правок другой.

                  Цикл из двух модулей

                  Orders→Billing→Orders

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

                  Стрелка всегда означает «использует». Проверка показывает кратчайшее кольцо и строки исходников, которые его замыкают.

                  Один из способов разорвать круг

                  Orders→Billing→Domain/Orders

                  Если Billing нужен только тип заказа, вынесите само бизнес-понятие в Domain. Обратная ссылка на сценарии Orders исчезнет: Domain не зависит от верхних слоёв.

                  Если модули всегда меняются вместе, стоит проверить, не одна ли это задача. Добавление Contracts само по себе цикл не разрывает.

                  В dependencies.csharp модуль определяется по namespace. Блокируются только связи, доказанные ридером исходников; предположение по имени не становится нарушением. Как определяется доказанная связь →

                  03. Средняя достижимость файлов: сколько файлов видно по цепочке зависимостей

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

                  Весь пример — три файла

                  A.cs→B.cs→C.cs
                  Идём по стрелкам из каждого файла
                  СтартДостижимыСколько
                  AA, B, C3
                  BB, C2
                  CC1

                  Средняя достижимость файлов = (3 + 2 + 1) / 3 = 2 файла

                  Без связей каждый видит только себя: средняя достижимость файлов = 1. Если добавить C → A, каждый увидит все три файла: средняя достижимость файлов = 3, размер циклической группы = 3.

                  Как читать число

                  Чем оно выше, тем дальше в среднем тянутся зависимости файла. Это сигнал о связанности кода, а не обещание, что при следующей задаче придётся изменить ровно столько файлов.

                  Размер циклической группы — размер крупнейшей группы файлов, из каждого из которых можно добраться до всех остальных. Это размер всей группы, а не длина отдельного простого цикла. В цепочке слева такой группы нет: размер циклической группы = 0.

                  По умолчанию complexity.csharp требует средняя достижимость файлов ≤ 8 и размер циклической группы = 0. При превышении отчёт называет файлы с наибольшим числом достижимых зависимостей или участников циклической группы.

                  Что входит в расчёт и откуда предел 8

                  Измеряются файлы продукта и доказанные связи между ними. Для sliced-dotnet это файлы внутри архитектурных зон; без зон — файлы вне тестовых проектов. Generated-код исключён только при объявленном пути и маркере; исключения видны в scope:. Host входит в число, но не в список кандидатов на сокращение связей.

                  Предел 8 взят из практических замеров и устройства sliced-dotnet. Это выбранное автором ограничение, которое можно пересмотреть, накопив опыт. Оба предела явно записаны в settings."complexity.csharp".

                  Те же ключи и стартовые пределы 8 / 0 у complexity.typescript (файлы, barrels прозрачны) и complexity.go (пакеты по tracked go.mod; циклы импортов запрещает компилятор).

                  Формула и область измерения · Обоснование пределов

                  Применить у себя

                  Заберите правила. Или готовый CLI.

                  Суть подхода — договорённости в репозитории, проверяемые после каждого изменения. Реализация открыта; причины решений записаны в ADR.

                  Установить Harness CLI и включить в работу

                  Standalone-бинарь; для работы нужен Git. .NET runtime не требуется.

                  1. Установите CLI:
                    curl -fsSL https://raw.githubusercontent.com/gently-whitesnow/harness-cli/master/install.sh | sh
                  2. В корне репозитория создайте конфигурацию:
                    harness init --kind application

                    Для standalone-библиотеки: --kind library.

                  3. Заполните ответы в .harness.json, отметьте неприменимые языки и просмотрите policy. Добавьте конфигурацию и файлы проекта в индекс Git.
                  4. Подготовьте клон и проверьте репозиторий:
                    harness setup
                    harness check

                  После каждой правки

                  Включите harness check в свой verify-скрипт вместе с тестами, сборкой и линтерами. Запускайте этот скрипт в CI и возвращайте отчёт агенту для исправления.

                  Harness видит только файлы в индексе Git. Сам тесты и сборку не запускает.

                  0 — блокирующие проверки прошли; замечания и пробелы в готовности ещё возможны.
                  1 — доказано нарушение.
                  2 — проверка не завершилась достоверно.

                  Объяснение правила: harness explain <check-id>.
                  Обновление контракта репозитория: harness upgrade.
                  Руководство для агента одной командой: harness guide.