# Решения по устройству процесса Журнал согласований: что решено, почему и что из этого следует. Пишется по ходу разбора тем, одна тема — один раздел. Причина обязательна: через месяц она забывается раньше факта. Незакрытые остатки прошлого захода — [REMAINING.md](REMAINING.md). ## Требования, зафиксированные по ходу Не решения — вход, который обязан быть удовлетворён и разбирается в названной теме. **Т1. Адаптация и проверка проекта под канон — обязательный скилл.** Нужно уметь прийти в **любой** старый проект и перевести его на текущие рельсы. Канон при этом сам будет меняться, поэтому уже приведённые проекты тоже должны повышаться до новых версий. *Разбирается в теме 5 (старт и жизненный цикл проекта).* Следствия, которые из этого уже видны: - **У канона обязана быть версия, а у проекта — отметка, под какую он приведён.** Иначе «соответствует канону» не имеет определённого ответа: сравнение идёт с тем, что модель помнит сейчас, а это и есть дрейф. - **Журнал изменений канона — как миграции.** Каждое повышение версии несёт запись «что добавилось, что переехало, что удалено, что сделать проекту». Без него адаптация переизобретается на каждом проекте. - **Отметка версии машиночитаема.** `.docs.json` отвергнут как *указатель путей* (решение F), но отметка версии — другое: её читает скрипт, и разбирать прозу `CLAUDE.md` для этого не нужно. Прецедент — `.tasks.json`. - **Операций три:** `check` (соответствие текущему канону), `adopt` (перевод чужой раскладки), `upgrade` (повышение с версии N до M по журналу). Первая и третья — одно сравнение с разными исходами. - **Механизируемое и суждение не смешивать.** Скрипт проверяет пути, лишние файлы, битые ссылки, версию. Агент судит о смысловых дублях (`docs/specs/ recognition.md` против capability `recognition`) и об оставшемся поведении в `architecture.md`. Скрипт, отчитавшийся «канон соблюдён» на проекте с тремя лишними файлами, хуже отсутствующего. - **Границы плагинов:** `docs/tasks/` — часть канона документов, но владеет им `av-dev-tasks` со своим `tasks.py adopt`. Два плагина сходятся на одном каталоге. *Тема 7.* ## 1. Статус OpenSpec (2026-08-03) ### Что было OpenSpec несёт оба проекта: healthlog — 5 capability, 3530 строк спек, 9 архивных change за две недели; jellybit — 11 capability, 3895 строк, 43 архивных change. При этом в трёх местах плагина написана ветка «проект без OpenSpec» (`task-pipeline` предпосылки, `review-pipeline` предпосылки, `task-batch` предпосылки) — и **не исполнялась ни разу**. Проектные факты живут в пяти домах: `CLAUDE.md`, `docs/architecture.md`, `openspec/specs/`, `openspec/config.yaml` → `context`, и планируется шестой — `docs/review-brief.md`. Расхождение измерено: у healthlog раздел «Хранилище» в `docs/architecture.md` — 950 строк (377–1328) против `openspec/specs/storage/spec.md` на 1337 строк. Два описания одного поведения, никем не сверяемые. У jellybit того же нет: `docs/specs/architecture.md` — 300 строк обзора, детали в 11 спеках. **Проект с 43 изменениями держит архитектуру втрое короче проекта с 9.** ### Решено **A. OpenSpec — жёсткая предпосылка `av-dev-pipeline`.** Ветки деградации удаляются, вместо них объявленная зависимость и проверка на старте. Зависимость на уровне **плагина, а не процесса**: `av-dev-tasks`, `av-dev-git` и будущий плагин документов от OpenSpec не зависят и работают на python/ansible-проектах. *Причина:* непроверенная ветка деградации хуже честной строки «требуется OpenSpec» — она даёт ложную уверенность, что проект без спек поедет. **B. Нормативный дом поведения — `openspec/specs/`.** `architecture.md` переопределяется как **обзор**: принципы, компоненты со ссылками на capability, внешние форматы данных, раскладка, деплой, открытые вопросы. Поведения он не описывает. *Причина:* `opsx:archive` вливает дельты именно в `openspec/specs/` — любой другой нормативный дом обязан синхронизироваться руками и разойдётся. Форма jellybit это уже подтвердила на 43 изменениях. **C. `openspec/config.yaml` → `context` держит только нужды генерации.** Язык, правила именования capability, придирки валидатора RFC 2119 — и ссылки. Правило ревью, пересказ конвенций и инварианты оттуда вычищаются: у них есть свои дома. *Причина:* блок «Ревью (процесс, не артефакт)» в обоих `config.yaml` дословно повторяет шаги 4 и 7 `task-pipeline`. Это второй дом для правила, которым владеет плагин, и он разойдётся на первой же правке. ### Что из этого следует Из A: 1. Три места с веткой деградации переписываются на объявленную предпосылку плюс проверку на старте (есть `openspec/`, разрешаются `opsx:*`) и внятный отказ: `task-pipeline` предпосылки, `review-pipeline` предпосылки, `task-batch` предпосылки. 2. Описание `av-dev-pipeline` в маркетплейсе получает строку «требует OpenSpec». 3. **Факт для темы «объединять ли tasks и pipeline»:** объединение потянуло бы зависимость от OpenSpec на управление задачами, которой там сейчас нет. Из B: 4. Правило «поведение — в спеку, устройство и границы — в архитектуру» становится контрактом плагина документов и правилом шага «синк документации» в `task-pipeline`. 5. healthlog чистится **не разом**: раздел вычищается той задачей, которая его касается. Нужен способ не потерять остаток — иначе 950 строк «Хранилища» останутся навсегда. 6. **Дыра, которую решение открывает:** «почему» после архивации. Сегодня `CLAUDE.md` healthlog велит писать причину решения в `architecture.md` — а мы её оттуда выселяем. Спеки нормативны и «почему» не держат; `design.md` живёт внутри change и уезжает в архив. Либо ADR (как у jellybit), либо явное правило «почему живёт в архивных change». **Первый вопрос следующей темы.** Из C: 7. `av-dev-pipeline` даёт образец `openspec/config.yaml` отдельным reference — он владеет связью с OpenSpec. Заполняется при старте проекта и при `adopt`. 8. У обоих проектов из `config.yaml` вычищается блок «Ревью (процесс, не артефакт)», пересказ конвенций и инвариантов. ## 2. Канон документов проекта (2026-08-03) ### Что было Измерено по обоим проектам: - **«Почему» не теряется — оно не находится.** `design.md` пишется почти всегда (jellybit 39 из 43 архивных change, healthlog 9 из 9 — ≈285 КБ за две недели) и имеет секции `Context` / `Goals / Non-Goals` / `Decisions` / `Risks / Trade-offs`, то есть является ADR по структуре. Против этого ADR руками: **6 записей у jellybit, четыре из них 13 июня — в день старта**; между 15 июня и 23 июля прошло ~40 изменений и ноль ADR. У healthlog ADR нет вовсе, а настоящее ADR-рассуждение (отказ от DuckDB) лежит в разделе «Открытые вопросы» файла `architecture.md`, потому что больше некуда. - **Два плана.** `docs/plan.md` healthlog («порядок и его обоснование», 11 шагов) и `/PLAN.md` из `av-dev-tasks` («цели с обоснованием очереди прозой») — один артефакт под двумя именами. - **Дубли спек у jellybit.** Из шести файлов `docs/specs/` три (`recognition`, `review-ux`, `workflow`) описывают поведение, уже покрытое capability в `openspec/specs/`. - **`docs/drafts/` раскладывается без остатка:** `roadmap.md` → цели в «порядок», `conventions-backlog.md` → задачи `[idea]`, `logical-title-model.md` (293 строки, итог «сущность `title` не вводим») → намеренный отказ, то есть ADR. ### Решено **D. «Почему» — ADR как промоут поверх архива.** Обоснование по-прежнему пишет `design.md`; ADR — короткая запись, цитирующая решение и ссылающаяся на архивный `design.md`. Заводит её **шаг «синк документации» пайплайна по названному триггеру** (дорогой откат / намеренный отказ от очевидного / пересмотр прежнего решения), а не человек по вдохновению. *Причина:* ручной ритуал эмпирически не выжил — 6 записей на 52 изменения. Автоматический (`opsx:propose` пишет `design.md` всегда) работает и производит на порядок больше. Чинить надо не дом, а индекс и критерий промоута. **E. `docs/plan.md` растворяется в `/PLAN.md`.** Файл удаляется, 11 шагов становятся целями в «порядке», ссылки в `CLAUDE.md` и паспорте переводятся. **F. Пути жёсткие, оба проекта приводятся к одному виду.** Плагин знает раскладку поимённо; указателя вида `.docs.json` нет. *Причина (словами владельца):* «так проще ориентироваться во множестве проектов, а не видеть слегка похожую, но разную структуру в каждом. Все проекты малого и среднего размера, проще подогнать их под одну структуру. Кроме того, у OpenSpec тоже структура строгая». Цена принята сознательно: плагин перестаёт быть переносимым на чужой репозиторий, а `adopt` из «поправь указатели» превращается в «перенеси файлы». **G. Конвенции и разведка — каталогами с README-индексом.** `docs/conventions/` и `docs/research/`: путь жёсткий, нарезка внутри свободна. Схема хранилища — **отдельный** `docs/database.md` (своя каденция: меняется миграцией, а не архитектурным решением; гейт healthlog уже сверяет миграции с документацией). Конвенции идентификаторов и именования — не схема, они в `conventions/`. **H. Слота для черновиков нет.** Идея → задача `[idea]`; намеренный отказ → ADR; порядок работ → `PLAN.md`; незрелое размышление → `opsx:explore` внутри change. ### Канон ``` CLAUDE.md памятка агенту: что это, стек, инварианты, команды, слоты docs/ passport.md зачем и для кого; чем НЕ является; сценарии; референсы architecture.md как сложено — обзор: принципы, компоненты со ссылками на capability, внешние границы, раскладка, деплой database.md схема хранилища (там, где есть БД) conventions/README.md + <тема>.md как пишем код; README держит правило промоута research/README.md + <тема>.md что показала реальность: чужие форматы, живые данные adr/README.md + template.md + ADR-*.md почему — промоут поверх архивных design.md review-journal.md промахи конвейера ревью ← уточнено в теме 3 review-brief.md предмет ревью — см. тему 3 ← отменено в теме 3 tasks/ av-dev-tasks: items/, PLAN.md, BACKLOG.md, SPRINT.md, REJECTED.md openspec/ config.yaml только нужды генерации + ссылки specs//spec.md что система делает — нормативно changes/archive/ журнал изменений с design.md — сырьё для ADR ``` Слотов **нет** у: `docs/drafts/`, `docs/specs/`, `docs/plan.md`, `BRIEF.md`, `docs/backlog/`, `docs/review/journal.md`. ### Что из этого следует 9. **Переезд healthlog:** `architecture.md` 1611 → обзор (поведение уезжает в `openspec/specs` по разделу за задачу); `conventions.md` → `conventions/README.md`; `local-research.md` 1829 → `research/`; `plan.md` → `docs/tasks/PLAN.md`; `backlog/` → `docs/tasks/`; завести `docs/adr/`. 10. **Переезд jellybit:** `BRIEF.md` → `docs/passport.md` (заодно обновить — не трогался с 13 июня); `docs/specs/architecture.md` → `docs/architecture.md`; `docs/specs/database.md` → `docs/database.md`; `docs/specs/jellyfin-layout.md` → `docs/research/`; `docs/specs/{recognition,review-ux,workflow}.md` сверить с capability и удалить как дубли; `docs/review/journal.md` → `docs/review-journal.md`; `drafts/` растворить по H; `docs/backlog/` → `docs/tasks/`. 11. **`adopt` меняет природу** — теперь он переносит файлы, а не правит указатели. Разбирается в теме про старт проекта. 12. **Открыто до темы 6 (поддержание):** точная формулировка триггера промоута в ADR; нужен ли механический `check` раскладки документов, раз пути жёсткие; как не потерять остаток при постепенной чистке `architecture.md`. ## 3. Брифа ревью нет — бриф это и есть канон (2026-08-03) ### Что было Контракт брифа — 413 строк, 13 разделов, отдельный файл `docs/review-brief.md`, который каждый проход читает как истину. Заполнение на обоих проектах дало 841 и 734 строки, и `REMAINING.md` уже отметил, что часть разделов вырождается в пересказ. Разбор по разделам после решения F (жёсткие пути) показал: **посредник между агентом и файлом не нужен, когда путь известен**. Восемь из тринадцати разделов дублируют канон или снимаются жёсткими путями. ### Решено **I. Отдельного файла-брифа нет.** Проектную конкретику проходам дают документы канона напрямую, по жёстким путям. Формулировка владельца: «артефакты в `docs` и должны стать частями брифа, а для ревью достаточно дать ссылки на эти артефакты». *Причина:* один факт — один дом. Бриф был вторым домом для паспорта, инвариантов и карты, а разошедшийся бриф хуже отсутствующего: он выглядит актуальным. **J. Заводится `docs/security.md`.** Периметр **первой строкой** (целевой и сегодняшний, если контур не развёрнут), недоверенный вход и его каналы, из чего строятся пути и ключи, что разграничивает доступ, что чувствительнее чего, что вне модели. Материал уже есть, но рассыпан: у healthlog — раздел «Аутентификация» в `architecture.md` и строка про секреты в `CLAUDE.md`, у jellybit — секреты в `conventions/config.md`. **Периметра нет ни у одного**, а без него враждебный проход не выбирает между «открыт наружу» и «контур доверенный». **K. `review-journal.md` → `docs/review.md`:** журнал дефектов плюс настройка конвейера под проект. Туда садится остаток брифа, который фактом о проекте не является — типовые узлы, типовые ложноположительные, вопросы к проходам, недоступно проверке. *Причина:* все четыре — производные калибровки, и журнал им источник. `## Вопросы к проходам` сам называет журнал главным источником; `### Перестали проверять сознательно` требует ссылки на его запись. **L. Журнал расширяется до всех воспроизведённых дефектов** с пометкой «проскочил / пойман ревью». Проверочный набор для калибровки — выборка по пометке. *Причина:* пойманные дефекты с оракулом (768 МиБ пика, 5.019 с удержания блокировки) сегодня не сохраняются нигде, кроме отчётов триажа в архиве change, а они и есть лучшая опора для прохода — проектные, воспроизводимые, однажды оказавшиеся правдой. **M. Семантика гейта — в `CLAUDE.md`, расширением раздела «Команды».** Чем краснеет безусловно и почему, где логи, что означает исход, чего в гейте намеренно нет, **кто и когда обязан гонять дорогое вне гейта**, что запускать запрещено (с путями). Гейт краснеет не только в ревью — это факт о проекте. **N. Severity инвариантов дописывается в `CLAUDE.md`** рядом с формулировкой. Контракт брифа сам называл это лучшим исходом; жёсткие пути делают возможным. Оговорка «выведена по обратимости» исчезает вместе с пересказом. ### Канон после темы 3 ``` CLAUDE.md что это, стек, инварианты с severity, команды, семантика гейта, запреты, слоты docs/ passport.md зачем и для кого; чем НЕ является; сценарии; референсы architecture.md как сложено — обзор; окружение, внешние зависимости, наблюдатель, характер потока database.md схема хранилища; представление данных и настройки с числовым значением (таймаут занятости, лимит тела, режим журналирования, ретеншен) security.md периметр первой строкой; недоверенный вход; из чего строятся пути и ключи; разграничение; что вне модели conventions/README.md + <тема>.md research/README.md + <тема>.md наблюдения и измеренные числа с провенансом adr/README.md + template.md + ADR-*.md review.md настройка конвейера под проект + журнал дефектов tasks/ av-dev-tasks openspec/ config.yaml, specs//spec.md, changes/archive/ ``` Слотов **нет** у: `docs/review-brief.md`, `docs/drafts/`, `docs/specs/`, `docs/plan.md`, `BRIEF.md`, `docs/backlog/`, `docs/review-journal.md`. ### Что из этого следует 13. **Скилл `project-brief` растворяется.** Заведение недостающих документов канона — часть скилла старта/адаптации (тема 5, требование Т1). 14. **Девять charter'ов переписываются второй раз.** Сейчас каждый читает «из раздела `## X` брифа»; станет — из файла канона. **Цена названа вслух:** первая переписка (вынос в плагин) осталась незамеренной — `REMAINING.md`, пункт 1. Вторая делает замер по четырём реальным находкам healthlog **обязательным, а не желательным**: два неизмеренных изменения подряд в том самом месте, где присваивается severity. 15. **Теряется соседство фактов, и charter обязан сшивать.** Контракт настаивал, что замер становится находкой только рядом с настройкой: «768 МиБ пика» — аномалия, лишь если известно, что запись лежит сжатой и распаковывается целиком; «5.019 с удержания блокировки» — отказ соседа, лишь если известен таймаут занятости. Теперь это `research/` и `database.md`, и charter'ы `ops`, `adversary`, `reimpl` обязаны прямо говорить «собери из этих двух», иначе проход снимет верное число и честно понизит находку до гипотезы. 16. **Деградация становится поразрядной** — и это лучше прежнего «нет брифа → деградирует всё». Нет `security.md` — деградирует `adversary`; нет `research/` — числа неизвестны `ops`, `adversary` и `reimpl`; нет `passport.md` — архитектурный проход теряет границу домена. Каждый проход пишет свою строку в границы покрытия. 17. **Открытый вопрос из `REMAINING.md` закрыт:** раздел `## Триггеры` удаляется вместе с брифом. Правило выбора профиля остаётся в скилле конвейера; проектная конкретизация, если понадобится, — в `docs/review.md`. ## 4. Границы плагинов (2026-08-03) ### Что было Связь `tasks` ↔ `pipeline` уже сделана **ролями, а не именами**: скиллы говорят «пайплайн проекта», «владелец спринта», «тот, кто ведёт задачи». Жёсткая ссылка по имени ровно одна — `task-pipeline:112` на канонический текст правила про остаток внутри `session`, и рядом обработан случай «плагин не подключён». Слоты `CLAUDE.md` при этом дублировались уже внутри одного плагина: шесть у `tasks`, семь у `session`, три пары — одно и то же. Темы 2–3 растворили ещё часть: «куда переезжает суть» отвечает канон, «оракулы» — семантика гейта (решение M), «где живёт разбор процесса» — `docs/review.md` (решение K). Из тринадцати остаётся около четырёх. ### Решено **O. Три плагина: `av-dev-pm`, `av-dev-pipeline`, `av-dev-git`.** - **`av-dev-pm`** (бывший `av-dev-tasks`) — управление продуктом: канон документов, задачи, цели, спринты, старт и адаптация проекта. Владеет всем `docs/`, включая `docs/tasks/`. - **`av-dev-pipeline`** — исполнение: SDD-цикл, конвейер ревью, девять агентов. - **`av-dev-git`** — стиль коммитов; работает в любом репозитории. *Причина (словами владельца):* «пайплайн можно и переиспользовать в других проектах с более простым подходом к управлению». Это подтверждается разбором: пайплайн зависит от **файлов канона и от OpenSpec, а не от плагина** `av-dev-pm`. В чужом проекте нужных файлов нет — включается поразрядная деградация (следствие 16), и это штатный режим, а не поломка. *Имя:* `pm` = product management, «объединение всех операций по управлению продуктом», и согласуется с `av-dev-git`. **P. Граница «пайплайн не закрывает задачу» снимается.** Закрывает задачу и двигает строки между `SPRINT.md` / `BACKLOG.md` / `REJECTED.md` **агент- оркестратор** — `task-pipeline` и `task-batch`, а не сабагенты внутри них. Зовёт он `tasks.py` через слот «Команда учёта задач» в `CLAUDE.md`. Слот, следовательно, **не исчезает, а становится мостом между плагинами** — и заодно тем, чего в чужом проекте нет, отчего пайплайн там работает как прежде: докладывает исход, записей учёта не трогает. **Q. `av-dev-backlog` помечается устаревшим и остаётся** до перевода jellybit. *(заменено на тему 30: плагин удалён раньше этого срока — условие пережило свою причину.)* Описание переписывается так, чтобы не ловить триггер «добавь задачу в беклог» — иначе агент выбирает между ним и `av-dev-pm` случайно. ### Что из этого следует 18. **Переименование `av-dev-tasks` → `av-dev-pm`** тянет `plugin.json`, `marketplace.json` и пространство имён скиллов: `av-dev-tasks:session` → `av-dev-pm:session`, включая ссылку из `task-pipeline:112`. 19. **Раздел «Стимулы, которые процесс создаёт» в `session` переписывается.** Снятая граница выбила механическую опору у трёх защит: «сжать задачу до остатка», «занизить урожай», «занизить критерии приёмки» — во всех трёх приёмщик и исполнитель теперь совпадают. Остаются: **отчёт триажа** в `openspec/changes//review/` (независимый артефакт, `task-batch` уже сверяет полноту ревью по нему, а не по прозе исполнителя), **`SPRINT.md` под git** с видимой историей и **`reopen --reason`** — закрытие не окончательно, приёмка человеком на сессии его отменяет. Раздел обязан назвать их поимённо, иначе обещает защиту, которой нет. 20. **Конфликт владения `docs/tasks/` снят** — канон и задачи теперь в одном плагине. 21. **Скилл `adopt` из `av-dev-tasks` поглощается** скиллом адаптации проекта уровня канона (требование Т1). Разбирается в теме 5. 22. **Состав `av-dev-pm`:** `tasks`, `session` (есть), `docs` — ведение канона, `project` — старт, adopt, check, upgrade (тема 5). ## 5. Старт проекта и жизненный цикл под каноном (2026-08-03) ### Что было Требование Т1: прийти в любой старый проект и перевести на текущие рельсы; канон сам меняется, значит уже приведённые проекты тоже повышаются. Существующий `adopt` (уровень задач) даёт готовую форму: **`scan` — только чтение, карта → суждение человека → `apply` — запись одним проходом**, с отказом до первой записи при неверной карте и с обязательным разделом «не разложилось» поимённо. Форма переносится на уровень канона как есть. Четыре операции различаются не поровну: `adopt`, `check` и `upgrade` — одна машина сравнения с разными исходами, а `init` — принципиально другой режим, разговор, а не сверка. ### Решено **R. Два скилла: `av-dev-pm:init` и `av-dev-pm:canon`.** `init` — интервью по входному брифу для нового проекта. `canon` — привести к канону: `check`, `adopt`, `upgrade` одной машиной. **S. Скелет канона заводится целиком, незаполненное называется пустым.** Все файлы канона есть с первого дня, но незаполненный держит **одну честную информативную строку**: «наблюдений на живых данных нет — внешний источник один, формат документирован», «прецедентов не накоплено», «внешних зависимостей нет, смотри на диск и на СУБД». *Причина:* это тот же принцип, что был в контракте брифа, поднятый на уровень файлов. Проход читает такую строку **как факт**, а не как пробел, и не тратит обязательный вопрос впустую. Отсутствие файла он прочитать не может никак. **Защита от вырождения в заглушки** берётся у `tasks.py`: пока на месте стоит плейсхолдер шаблона, `check` о нём напоминает. Строка «TBD» — это не «пустое названо пустым», и `check` обязан их различать. **T. Скрипт `docs.py` плюс версия канона в `docs/.pm.json`.** Отдельный скрипт, не расширение `tasks.py`: рефакторинг 2421 работающей строки ради удобства вызова не окупается. `docs.py check` зовёт `tasks.py check` для своей части. **Граница механизируемого объявляется вслух — иначе `check` соврёт.** | Проверяет `docs.py` | Судит агент | | --- | --- | | отсутствующие пути канона | смысловой дубль (`docs/specs/recognition.md` против capability) | | файлы в `docs/` вне канона | поведение, оставшееся в `architecture.md` | | битые относительные ссылки | протухший факт, разошедшийся с кодом | | версия канона и её отставание | достаточность честной строки в пустом слоте | | нетронутый плейсхолдер шаблона | | `check`, отчитавшийся «канон соблюдён» на проекте, где из шести файлов три лишние, хуже отсутствующего. ### Порядок интервью `init` — зависимость, а не удобство Цель и потребители → чем это **не** является и мера успеха → периметр и что недоверенное → стек, хранилище, необратимое → чем краснеет гейт → первые цели в `PLAN.md`. Каждый блок опирается на ответ предыдущего. Вход — свободный текст «что мне нужно и почему» (образец формы: `BRIEF.md` jellybit, 6 КБ). После `init` его дом — `passport.md`; отдельным файлом он не остаётся. **`init` физически не производит полный канон.** В новом репозитории нет кода, а `architecture.md`, `database.md`, `conventions/` и `research/` выводятся из него. Они заводятся скелетом с честной строкой («архитектуры пока нет: кода нет, заводится первой задачей») и наполняются шагом синка документации. ### Что из этого следует 23. **`docs/.pm.json` поглощает `/.tasks.json`.** Меняется цепочка разрешения в `tasks.py` — сегодня он ищет `.tasks.json` вверх от текущего каталога. Нужен переходный период либо чтение обоих. 24. **`tasks.py adopt` становится шагом внутри `canon adopt`**, а не отдельной пользовательской операцией: `docs/tasks/` — часть той же раскладки. 25. **Версия канона — целое число**, не semver: у канона нет обратной совместимости, есть только «приведён» и «не приведён». 26. **Журнал изменений канона** живёт в плагине — `av-dev-pm/skills/canon/references/changelog.md`, запись на версию: что добавилось, что переехало, что удалено, что сделать проекту. 27. **Открыто до темы 6:** звать ли `docs.py check` из гейта проекта. У healthlog `task gate` уже сверяет миграции с документацией, так что место есть; но гейт принадлежит проекту, и плагин может только рекомендовать строкой в отчёте. ## 6. Поддержание документов по ходу разработки (2026-08-03) ### Что было Гейт healthlog **уже изобрёл нужный механизм** для одного документа — `scripts/gate.py:177-181`: миграция изменена, а `docs/database.md` нет → `FAIL`. Документ канона сверяется с кодом красным гейтом, а не напоминанием. Против этого — прямое доказательство, что́ не работает: у `adr/` был список триггеров прозой («выбор технологии, структурные решения, дорогой откат, намеренный отказ»), и он дал **6 записей на 43 изменения**. Прозаический триггер, который некому проверить, не срабатывает. Механизируемы три документа из десяти: `database.md` (миграция), `architecture.md` (capability в `openspec/specs/` без упоминания в обзоре), `tasks/` (`tasks.py check`). Плюс `openspec/specs/` вливает `opsx:archive`. ### Решено **U. Принуждённое отрицание в докладе шага синка.** Шаг обязан назвать **каждый** документ канона: обновлён — чем, либо «не требуется, потому что…». Нетронутые группируются одной строкой с общей причиной. *Причина:* это тот же приём, что «границы покрытия» в отчёте ревью и «пустой пункт называется пустым» в каноне — и единственный, о котором в этом репозитории есть данные, что он работает. Отличить «не написал» от «написал, что не требуется» можно только тогда, когда отрицание обязательно. **Триггер ADR, окончательная формулировка.** Запись заводится, когда верно одно из трёх: **дорогой откат** (переделка стоит дороже переписывания одного файла); **намеренный отказ** от очевидного подхода; **пересмотр прежнего решения** — тогда у старой записи обязателен статус «заменено на». Не заводится для рутины и для того, что видно из кода и `git log`. Источник — архивный `design.md`: ADR его цитирует и на него ссылается, а не пересказывает. **V. `docs.py check` обязателен в гейте проекта.** Скилл `canon` при адаптации добавляет шаг и печатает это в отчёте. *Причина:* гейт — единственный общий станок, который нельзя пропустить. Проверка, которую зовёт агент, может быть не позвана; прецедент в самом healthlog уже есть. Сверка «миграция изменена — `database.md` нет» **обобщается**: путь миграций проекта записывается в `docs/.pm.json` рядом с версией канона, и `docs.py` делает эту проверку сам, а не каждый проект заново. **W. Остаток чистки помечается маркером и считается числом.** Неразобранный раздел получает ``, `docs.py` считает маркеры и печатает остаток. Закрывается порциями, как переоценка задач. **Маркеры гейт не красят.** Это долг, а не отказ: покрасневший гейт на первом маркере сделал бы постепенный переезд невозможным, а разовый — обязательным. Число печатается и убывает на глазах. ### Что из этого следует 28. **Шаг 9 `task-pipeline` переписывается** из четырёх пунктов прозой в построчный доклад по документам канона. 29. **`promote.md`, шаг 3, переписывается:** «вычеркнуть пункт из брифа, правило переезжает в перечень механизированного в разделе `## Карта`» → перечень механизированного живёт в `conventions/README.md`. Брифа нет. 30. **`docs/.pm.json` держит не только версию канона**, но и пути, нужные проверкам: каталог миграций — как минимум. 31. **`docs.py check` получает две сверки с кодом**, а не только раскладку: миграции ↔ `database.md`, capability ↔ упоминание в `architecture.md`. ## 7. Раскладка скиллов и доставка скриптов (2026-08-03) ### Решено **X. Пять скиллов в `av-dev-pm`.** ``` av-dev-pm/skills/ init/ интервью по брифу → канон нового проекта canon/ раскладка: check / adopt / upgrade docs/ содержимое канона: ADR из архивного design.md, промоут конвенций, запись в research/ и review.md, чистка architecture.md tasks/ формат и содержимое задач session/ ритуал спринта ``` *Причина отдельного `docs`:* правила ведения содержимого канона обязаны жить у владельца канона, а не в шаге синка чужого плагина — иначе проект без пайплайна документацию вести не может. Это работает потому, что **вызов скилла через пространство имён между плагинами возможен**, в отличие от `$CLAUDE_PLUGIN_ROOT`: `task-pipeline` уже зовёт `opsx:propose` и `av-dev-pipeline:review-pipeline`. Шаг синка зовёт `av-dev-pm:docs`, а в чужом проекте деградирует до прозаического списка. Симметрия, по которой резалось: **раскладка и содержимое разделены и для документов, и для задач** — `canon` / `docs`, `tasks` / `session`. **Y. Скрипты не копируются — живут вместе со скиллами.** Три вызывающих, три способа дотянуться: | Кто зовёт | Как | | --- | --- | | скиллы `tasks`, `canon`, `docs` | `$CLAUDE_PLUGIN_ROOT` — свой плагин, работает всегда | | `task-pipeline`, `task-batch` | **вызов скилла** `av-dev-pm:tasks`, а не путь | | гейт проекта | путь переменной с умолчанием на канонический путь маркетплейса; пишет `canon adopt`, внятный красный отказ, если не найден | **Слот «Команда учёта задач» всё равно исчезает** — но снимает его не копия, а **вызов скилла через пространство имён**. Тот же приём, которым шаг синка зовёт `av-dev-pm:docs` (решение X): чужой плагин зовёт скилл, скилл разрешает свой `$CLAUDE_PLUGIN_ROOT` сам. Путь наружу не выносится вовсе. *Первоначально здесь было решено вендорить `scripts/tasks.py` и `scripts/docs.py` в проект. Отменено после проверки фактов:* - **CI нет ни в одном проекте** (ни `.github`, ни woodpecker, ни drone). Pre-commit есть только у jellybit — `lefthook` с gofmt/vet/lint/test/gitleaks — и гоняется на той же машине, где установлен плагин. Довод «не работает в CI и у человека без Claude Code» оказался гипотетическим. - **Пара «источник — копия» существует и без вендоринга.** Установленный маркетплейс — git-клон; на момент разбора он стоял на `092d07c`, на четыре коммита позади `master`, и `av-dev-tasks` с `av-dev-pipeline` в нём отсутствовали вовсе. Довод «вендоринг создаёт вторую копию» был слабее, чем подан. - **Обновление маркетплейса — одна точка на все проекты.** При вендоринге каждый проект повышается отдельно, и проекты расходятся друг с другом — ровно та разнородность, против которой принято решение F. **Z. Имени у процесса нет — процесс это `av-dev`.** Маркетплейс уже `av-dev-skills`, плагины `av-dev-*`; в `CLAUDE.md` проекта пишется «процесс av-dev, канон версии N». Имя, которое нигде не работает, — украшение. ### Что из этого следует 32. **Решение P уточняется:** оркестратор закрывает задачи **вызовом скилла** `av-dev-pm:tasks`, а не запуском скрипта по пути. Плагина в проекте нет — вызов не разрешается, и пайплайн, как прежде, только докладывает исход. 33. **Слот исчезает из двух скиллов** — `tasks` (слот 6) и `session` (слот 7), — и из текстов `task-pipeline` и `task-batch`, которые на него ссылаются. 34. **`canon upgrade` отвечает за раскладку и версию в `docs/.pm.json`.** Скрипты обновляются обновлением маркетплейса, а не проектом. 35. **Скрипты живут в `av-dev-pm/skills/{tasks,canon}/scripts/`.** `docs.py` — в `canon`, потому что раскладку проверяет он. 36. **`canon check` сверяет версию канона проекта с версией установленного плагина** и говорит, кто отстал. Это нужно и без вендоринга: маркетплейс — git-клон, обновляется явно, и на момент разбора отставал на четыре коммита. 37. **Установленный маркетплейс требует обновления перед любой работой** — сейчас в нём нет ни `av-dev-tasks`, ни `av-dev-pipeline`. Это первый шаг выката (тема 8), иначе проверять будет нечего. ## 8. Порядок выката (2026-08-03) ### Объём Ссылок на бриф — **168 строк в 19 файлах** `av-dev-pipeline`, из них ~48 уходят вместе с удаляемыми `project-brief/SKILL.md`, `references/project-brief.md` и `references/brief-template.md`. Остальное переписывается на пути канона. ### Решено **AA. Инструмент строится целиком, потом проверяется.** Не пилот руками. *Риск принят сознательно:* если замер покажет деградацию severity, чинить придётся канон, зашитый к тому моменту в три скилла, скрипт и мигрированные файлы healthlog. *Удешевление, которое обязано быть заложено сразу:* **определение канона живёт в единственном reference-файле**, который читают `init`, `canon` и `docs`, а не повторяется в каждом. Правка канона — одно место плюс запись в журнал версий. *Страховка порядка:* **замер ставится перед переездом jellybit**, а не после всего, — он всё ещё блокирует то, что дороже всего откатывать. **BB. Работа ведётся в `docs/tasks/` самого `dev-skills`.** Скилл `tasks` не требует ни OpenSpec, ни языка — задачи для него просто каталог markdown. Цели — крупные куски, задачи — следствия. Заодно первая боевая обкатка собственного инструмента. **CC. `AGENTIC-TASKS.md` сжимается до истории решений и переезжает в `dev-skills`** отдельным `HISTORY.md`: почему не Scrum, числа первого замера, что отвергнуто и почему. Он описывает процесс, а процесс живёт здесь, не в healthlog. Остальное содержимое уже в плагинах, и второй дом для тех же правил — ровно то, против чего документ сам и написан. ### Порядок ``` 0. обновить установленный маркетплейс предусловие всего 0.5 завести docs/tasks в dev-skills, разложить 37 следствий по целям 1. РЕПОЗИТОРИЙ ПЛАГИНОВ 1.1 av-dev-tasks → av-dev-pm, пространство имён 1.2 канон одним reference-файлом — единственный дом определения 1.3 правки tasks и session: слоты, «Стимулы», .pm.json 1.4 новые init, canon, docs + docs.py 1.5 av-dev-pipeline: удалить project-brief, снять ветки деградации OpenSpec, переписать шаг 9, девять charter'ов, promote.md, убрать слот 1.6 av-dev-backlog устаревшим; README; журнал канона v1; HISTORY.md 1.7 REMAINING.md пересобрать — часть его вопросов закрыта этим разбором 2. HEALTHLOG — первая боевая проверка инструмента canon adopt, заполнение канона, security.md, review.md, ADR, маркеры в architecture.md, docs.py check в гейте 3. КАЛИБРОВКА на четырёх находках healthlog БЛОКИРУЕТ шаг 5 4. один-два спринта healthlog на новом процессе 5. JELLYBIT — переезд, удаление дублей specs, растворение drafts ``` ### Что из этого следует 38. **`REMAINING.md` частично устарел:** пункт 2 «Завести брифы» отменён темой 3; закрыты открытые вопросы про `## Триггеры`, `av-dev-backlog`, имя процесса и `AGENTIC-TASKS.md`. Пункт 1 (калибровка) стал обязательным, а не желательным. Пересобрать на шаге 1.7. 39. **Замер — единственный шаг, который нельзя переставить.** Всё остальное в порядке 1–5 можно тасовать; шаг 3 стоит перед шагом 5 жёстко. ## 9. Линтеры скриптов (2026-08-03) ### Что было Три скрипта на python, 3600 строк, ни одной проверки. `tasks.py` — 2450 строк, которые ходят по файловой системе, переименовывают и удаляют файлы задач. Требование к самим скриптам прежнее и не обсуждается: **голый `python3` 3.12, ноль внешних зависимостей** — они лежат рядом со скиллами и запускаются в чужом проекте, где ничего ставить нельзя. ### Решено **DD. `pyproject.toml` в корне `dev-skills`, зависимости через `uv`.** Файл живёт только здесь и не уезжает никуда: он держит **линтеры**, а не зависимости скриптов. Скрипты остаются запускаемыми любым `python3` — это проверено прогоном всех операций через `/usr/bin/python3`, а не через `.venv`. **EE. Ноль зависимостей охраняется двумя способами, и главный — второй.** `banned-api` у ruff ловит частые соблазны по имени (`requests`, `yaml`, `pydantic`, `click`, `rich`) — список заведомо неполный. Настоящий страж — pyrefly: в окружении нет ничего, кроме линтеров, поэтому **любой** сторонний импорт у него не разрешается. Первый способ даёт понятное сообщение, второй — полноту. **FF. Версии линтеров прибиты точно** (`ruff==0.16.1`, `pyrefly==1.2.0`) плюс `uv.lock` в git. Обновление линтера меняет набор находок, а находки правятся руками в скриптах, которые уезжают в чужие проекты. Обновление обязано быть отдельной осознанной правкой, а не побочным эффектом `uv sync`. **GG. `RUF001`–`RUF003` выключены.** Весь текст скриптов русский: сообщения, докстроки, комментарии. «Похожая на латиницу кириллица» здесь норма, а не опечатка, и три этих правила давали **311 срабатываний из 338** — шум, в котором тонут остальные 27. **HH. `av-dev-backlog` исключён из проверки.** *(исчерпано темой 30: плагин удалён, исключение снято из `pyproject.toml` и `copies.py`.)* Плагин помечен устаревшим и живёт до перевода последнего проекта, после чего удаляется целиком. Шесть его находок косметические (`os.replace`, `l` как имя), а правка замороженного кода без тестов — риск без выгоды. Исключение уходит вместе с плагином. **II. Голый `except Exception` разрешён только помеченный.** Правило `BLE` включено, а два места последнего рубежа (`main` обоих скриптов, код выхода 4 по словарю) несут `# noqa: BLE001` с причиной. Так третий такой except не появляется молча. ### Что из этого следует 40. **Найдено и починено 27 находок ruff и 14 pyrefly.** Содержательных две: мёртвая переменная `ques` в `check` (вычислялась и не использовалась — вопросы проверяет `questions_open`) и два места в `check --fix`, где `find_entry_index` может вернуть `None`, а результат идёт прямо в `list.pop` и в `range`. Оба сегодня недостижимы, и недостижимость держалась на рассуждении о вызывающем коде, а не на проверке. *Поправлено по ревью:* там стоит `raise`, а не `continue`. Тихий пропуск превратил бы сломанный инвариант в отчёт «индексы согласованы» — то есть в враньё; громкий отказ кодом 4 честнее. 41. **`os` из `tasks.py` ушёл целиком.** `os.replace` → `Path.replace`, `os.path.basename` → `Path.name`; импорт стал не нужен. 42. **`fail()` в `docs.py` объявлен `NoReturn`.** Без этого `read_config` выглядел как возвращающий неинициализированное значение — и это ровно то, что читатель кода тоже не мог знать наверняка. 43. **Проверка не входит ни в один гейт.** CI у репозитория нет, хука нет; запускается руками командой из README. Заводить хук ради двух скриптов, которые правятся раз в месяц, — плата ритуалом без выгоды. ## 10. Ревью готовых плагинов двумя проходами (2026-08-03) ### Что было Два независимых сабагента `fable` — по одному на `av-dev-pm` и `av-dev-pipeline`. **20 находок, из них две найдены обоими независимо.** Прошлые три круга ревью шли по одному проходу на всё; два прохода с разными предметами дали и больший урожай, и перекрёстное подтверждение самого дорогого дефекта. ### Что оказалось сломано по существу **JJ. Перестановка закрытия за коммит (решение из темы 8) сломала `reopen` и батч — и это нашли оба прохода.** `close --implemented` печатает «дорога назад: файл восстанавливается из git», а `reopen` искал **коммит удаления**, которого в новом порядке ещё нет: шаг 11 идёт последним, и учёт остаётся незакоммиченным. Проверено прогоном: `reopen` отказывал кодом 2 на свежезакрытой задаче — то есть в самом вероятном своём применении. Тем же грязным деревом ломался `task-batch`: `git rebase` и `git worktree remove` отказывают, и **каждая успешно закрывшая задачу ветка** уезжала бы в провалившиеся. Починено с обеих сторон: `reopen` берёт текст из `HEAD`, если коммита удаления нет, а шаг 11 обязан **коммитить учёт вторым коммитом** — иначе закрытие не доезжает до основной ветки и опора «`SPRINT.md` под git» остаётся словами. **KK. Канонический пример `docs/.pm.json` убивал `tasks.py`.** `canon.md`, `skeletons.md`, `tasks/SKILL.md` и `adopt.md` показывали ключ `tasks.sections`, которого скрипт не знает: `_validate_config` отвергает неизвестные ключи кодом 3 на **любой** команде. Проект, заведённый по канону дословно, остался бы без работы с задачами целиком — а `docs.py check` при этом печатал «канон соблюдён», потому что чужой код 3 уходит в «не проверялось». Секции живут в заголовках `##` индекса и второго дома не получают. ### Что из этого следует 44. **Класс находок тот же, что и в прошлые три круга: стыки.** Не новый код, а место, где один файл ссылается на другой. `sprint.md` в пункте «Сделана» всё ещё отсылал к порядку, который сам же тремя экранами ниже отменил; три остатка «шаг 9а» несли **предкоммитную** позицию закрытия; путь отчёта триажа не переживал `opsx:archive`, хотя по нему сверяют полноту ревью четверо. 45. **Инструкция, которую нельзя выполнить, выглядит как выполненная.** Ответ на вопрос по документированной процедуре (снять тег) оставлял задачу незабираемой, потому что судит **раздел**, а не тег; `canon adopt` требовал гнать `docs.py check` «до отсутствия дрейфа», недостижимого без нарушения запрета сочинять цели; урожай спринта, заведённый после `sprint close`, терял автотег молча. 46. **Два прохода по разным предметам дороже одного, но не вдвое.** Перекрытие оказалось ровно в одной находке из двадцати — той самой, что подтвердилась дважды. Практика остаётся: ревью на плагин, а не одно на репозиторий. ## 11. Зависимости между плагинами (2026-08-03) ### Целевая картина, которую проверяли `av-dev-git` ни от чего не зависит. `av-dev-pipeline` сам по себе: задача приходит **и обычным текстом**, и из `tasks`. `av-dev-pm` оперирует абстрактным «сделать задачу» и не знает, чем она выполняется. ### Что показала проверка **LL. Первые две цели выполняются, третья в исходной формулировке недостижима — и формулировку надо поправить, а не картину.** `av-dev-pm` **владеет конфигурационным файлом конвейера**: `docs/review.md` держит «Вопросы к проходам» и «Триггеры профиля», то есть перечисляет проходы поимённо, а скелет `review.md` несёт форму журнала дефектов. Кто-то этим словарём владеть обязан — канон и есть схема данных, которую конвейер читает. Честная формулировка цели: **`av-dev-pm` не зовёт пайплайн и не требует его наличия**. Она выполняется. **MM. Настоящая протечка была одна — необъявленная деградация опор приёмки.** «Стимулы» в `session` и приёмка в `sprint.md` держались на «сохранённом отчёте триажа» по конкретному OpenSpec-пути. В проекте без конвейера ревью защита от занижения урожая исчезала **молча**: сверять не с чем, а текст об этом не говорил. Теперь опора названа абстрактно («независимый отчёт ревью»), путь `av-dev-pipeline` дан как частный случай, а отсутствие конвейера обязано попадать строкой в доклад спринта. **NN. Ветка деградации шага 9 была неисполнима — ровно в том случае, ради которого написана.** «Плагина нет — открой `av-dev-pm/skills/canon/references/canon.md`»: путь в дерево маркетплейса, из проекта без установленного плагина не разрешается ниоткуда. Кросс-плагинные пути в дерево маркетплейса теперь не используются вообще: пайплайн ходит в **свой** `references/project-facts.md`, а ссылки в чужой плагин даются через `Skill <плагин>:<скилл>`. ### Что из этого следует 47. **Знаниевый цикл есть и он законен, но каждый его контракт обязан иметь единственный дом.** Пайплайн описывает раскладку `pm`, `pm` описывает артефакты пайплайна — пять симметричных контрактов, из них два уже разошлись: форма журнала дефектов (шесть полей против пяти, «Причина» потеряна) и список читателей `docs/research/` (`specs` выпал). Дома назначены: форма журнала — у конвейера, список читателей — у канона; в обеих копиях стоит явное указание на дом. 48. **Пайплайн больше не называет внутренние имена файлов `pm`.** `items/.md` и `SPRINT.md` в его тексте были вторым домом для раскладки, которую проект вправе переименовать через `docs/.pm.json`. 49. **Описания плагинов в манифестах врали умолчанием.** Ни `marketplace.json`, ни `plugin.json` не говорили, что `av-dev-pm` для конвейера **опционален**, а задача принимается текстом. Теперь говорят — это первое, что читает человек, выбирая, что подключать. ## 12. Механическая проверка копий (2026-08-03) ### Что было Разделение плагинов оставлено (тема 11), но цена его названа: пять симметричных контрактов в двух домах, два уже разошлись — форма журнала дефектов потеряла в копии поле «Причина», список читателей `docs/research/` потерял `specs`. Оба раза копия выглядела актуальной, и оба раза расхождение прошло мимо трёх ревью подряд. ### Решено **OO. Копия допустима, но обязана быть дословной и помеченной.** Разметка — HTML-комментарии, невидимые в отрендеренном markdown: `` … `` и `` … ``. `scripts/copies.py` требует побайтового совпадения текста между маркерами. *Почему комментарии, а не манифест копий отдельным файлом:* маркер уезжает в репозиторий проекта вместе со скелетом, и там он **полезен** — говорит читателю, что у текста есть дом и правится он там. Манифест остался бы в маркетплейсе и проекту ничего не сказал. **PP. Идентификатор строгий — буквы, цифры, дефис — и повторяется в закрывающем маркере.** Иначе документация о самом механизме объявляет дом и роняет проверку: это случилось на первом же прогоне, `README.md` объявил дом примером. Теперь пример пишется ``, угловые скобки под шаблон не подходят. **QQ. Ограда блока кода в сверку не входит.** В доме текст обрамлён своей ```, а в скелете тот же текст лежит внутри чужой, объемлющей ограды. Сверяется содержимое, а не разметка вокруг него. **RR. Коды выхода — общий словарь** (0 сошлось, 1 расхождение, 2 разметка, 3 не тот каталог, 4 сбой). Третий скрипт репозитория, и третий по тем же кодам. ### Что из этого следует 50. **Помечены два контракта:** форма записи журнала дефектов (дом — конвейер ревью, копия — скелет канона; это кросс-плагинная пара) и «когда заводить ADR» (дом — канон, копия — его же скелет). Второй пришлось сперва **сделать** дословным: копия говорила «обязателен статус», дом — «обязателен статус „заменено на"», и это ровно тот класс, который и ищется. 51. **Чего проверка не ловит — копию, которую забыли пометить.** Помечать остаётся решением человека, и это названо в `README.md` вслух: иначе зелёный прогон читался бы как «копий больше нет». 52. **Дом без копий — расхождение, а не замечание.** Маркер, обещающий дисциплину, за которой не за чем следить, — такая же ложная запись, как разошедшаяся копия. 53. **Запись в журнал версий канона проверка не заменяет.** Она видит, что копия отстала, но не видит, что проект уже унёс старую версию к себе. Это остаётся на человеке и сказано в обоих домах. ## 13. Секции `PLAN.md` переименованы (2026-08-03) ### Что было Секции назывались **«линия»** и **«кусты»** — метафора, требующая расшифровки при каждом употреблении. В текстах она и расшифровывалась: «звено упорядоченной линии продукта», «тематический куст — цель, в последовательность не встающая». Если название приходится объяснять рядом с каждым употреблением, объясняет не название. ### Решено **SS. «порядок» и «темы».** Заголовок называет ровно то свойство, которым секции различаются: в первой очередь значима и обоснована прозой, во второй порядка нет вовсе. Расшифровывать нечего — правило написано в самом имени. **TT. Записи в журнал версий канона не требуется — канон этих имён не знает.** `canon.md` называет файл `docs/tasks/PLAN.md` и ничего не говорит о его секциях: их дом — заголовки `##` индекса, а умолчание живёт в `tasks.py`. Версия канона поэтому не меняется, и проект вправе называть секции по-своему. Причина названа вслух, потому что соблазн повысить версию «на всякий случай» здесь сильный, а повышение обязало бы каждый проект что-то делать — при том что делать нечего. ### Что из этого следует 54. **Умолчание одно и живёт в `DEFAULT_PLAN_SECTIONS`.** Имена секций по-прежнему настраиваются `--plan-sections`, а домом остаются заголовки `##` индекса — переименование не трогает механику, только умолчание и тексты. 55. **Метафора — плохое имя для секции индекса.** Секция читается человеком без контекста, часто из вывода `list`, и второго шанса объяснить себя у неё нет. ## 14. Умолчания режимов прогона перевёрнуты (2026-08-03) ### Что было Оба скилла держали одно и то же умолчание — «по очереди», — хотя цена очереди у них разная. `review-pipeline` гнал проходы последовательно и требовал для параллельности **двух** условий (явная просьба **и** поимённо названный набор). `task-batch`, наоборот, планировал волны параллельных задач с потолком 2–3 и считал параллельность нормой прогона. Перепутаны оказались уровни. Проход ревью — чтение и рассуждение: он ничего не поднимает, ни за что не дерётся и по построению не видит выводов соседа. Задача батча — полный цикл пайплайна: гейт, поднятие сервиса вживую, вложенное ревью, общие порты и рабочие каталоги. Дешёвое стояло в очереди, дорогое гонялось разом. ### Решено **UU. В ревью умолчание — параллельно.** Стадии по-прежнему идут по порядку, параллельность касается только проходов внутри стадии. Последовательно гоняем по трём особым причинам, и каждая называется в отчёте: сказал оператор; проходы меряют; машина занята — причём занятость видит вызывающий, а не конвейер. Просьба «гони последовательно» **набора не требует**: очередь ничего не портит, она только дольше, и домысливать тут нечего — в отличие от прежнего правила, где неназванный набор блокировал отступление. **VV. Меряющая пара — правило стадии, а не решение прогона.** `adversary` и `ops` идут по очереди всегда: оба доказывают находки числами и оба меряют одно железо, а испорченный оракул хуже отсутствующего. Общее «гони параллельно» этого не отменяет; отменяет только прямое слово оператора **про эту пару**, и тогда в границы покрытия идёт строка про замеры под соседней нагрузкой. **WW. В батче умолчание — по одной задаче, параллельность — по графу зависимостей.** План собирается как граф (рёбра — жёсткие зависимости и сериализуемые пересечения) и в умолчании линеаризуется в один порядок. Просьба «гони параллельно» разрешает использовать **ширину графа**, а не гнать всё разом: потолок 2–3, замеряющая задача — волной по одной. Прежние правила волн сохранены целиком, они просто перестали быть умолчанием. ### Что из этого следует 56. **Режим батча задаёт режим ревью внутри задачи, и его называет charter.** Батч идёт по одной — машина свободна, сабагент гонит проходы параллельно; батч идёт волнами — сабагенту предписан последовательный режим с этой самой причиной. Сабагент своего соседа не видит, поэтому решать это ему нельзя. 57. **Ранний выход из ревью переехал на границу стадии.** Стадии идут по порядку в любом режиме, так что остановиться между ними можно всегда; остановка **внутри** стадии осталась побочной выгодой последовательного режима — но не поводом его выбирать. 58. **Цена параллельного батча проверяется до первой волны.** Тесты, делящие фиксированный порт или файл БД, и проект, умеющий поднимать один экземпляр, — основание гнать по одной даже после просьбы, сказанное строкой: просьба была про параллельность, а не про сломанные тесты. ## 15. Порядок проходов ревью — граф зависимостей (2026-08-03) ### Что было Решение 14 перевернуло умолчание, но оставило порядок в прежней форме: «стадии идут по порядку номеров, параллельность — только внутри стадии». Номер стадии при этом ничего не означает: между стадиями 1–4 ни один проход не читает вывод другого, так что очередь между ними была платой ни за что. А правило про замеры держалось на **двух именах** — `adversary` и `ops`, — и рассыпалось бы в тот день, когда мерить начнёт третий проход или проект добавит свой. ### Решено **XX. Порядок задаёт граф; стадии остаются единицей состава.** Профиль по-прежнему набирается стадиями, но запускается всё, у чего закрыты входящие рёбра. Рёбер три вида, и смешивать их нельзя: **зависимость** (гейт → все опиниативные, все проходы → триаж), **конфликт за ресурс** (ненаправленный, между теми, кто держит машину), **барьер стоимости** (только `deep`). **YY. Сериализует ресурс, а не имена.** Пометка «держит машину» — таблицей в скилле: `gate`, `adversary`, `ops`, `triage`; читают и рассуждают — `specs`, `code`, `reimpl`, `architecture`, `rubric`. Проект вправе пометить свой проход в `docs/review.md`; снимать пометку с перечисленных нельзя. Правило теперь самораспространяется: начнёт проход мерить — попадёт в цепочку по факту, а не по поправке. **ZZ. Ранний выход заменён барьером стоимости.** Он стоит там, где ранний выход зарабатывал: перед `reimpl` (пишет реализацию целиком) и `architecture`. В `quick`/`standard` барьера нет — стадий 3–4 там не бывает; в `design` нет по другой причине — предметом там и является форма, защищать нечего. **AAA. Ребро — это порядок, никогда не данные.** В обычном графе задач ребро тянет за собой вывод предшественника; здесь это запрещено: проход, увидевший чужие находки, соглашается с ними, и разведённость — вся ценность конвейера — обнуляется. Сказано в самом правиле, потому что графовый словарь провоцирует ровно эту ошибку. Исключение одно и оно же сток: триаж. **BBB. Диаграммы в скиллах — `mermaid`.** Граф, описанный прозой, читается как инструкция и теряет форму; диаграмма показывает её целиком. В конвейере четыре: общий граф прогона, граф профиля `design`, пример графа задач батча, веер финальной сверки. **Критерий, где диаграмма уместна: структура — граф или автомат, и проза вынуждена его пересказывать.** По этому критерию диаграммы заведены ещё в шести местах: жизненный цикл записи по индексам (`tasks`), четыре шага сессии с причинами на рёбрах (`session`), исходы задачи в спринте (`sprint.md`), одиннадцать шагов пайплайна с развилкой «тривиальная» (`task-pipeline`), храповик промоута с обратным ребром (`promote.md`), счётчик `retune` до `drop` (`calibration.md`) и граф вызовов между плагинами (`README.md`). Где структура — таблица соответствий (чек-лист синка в `docs`, профили ревью, коды выхода), диаграмма не заводится: она бы дублировала таблицу и разошлась с ней. Все диаграммы прогоняются через `mermaid-cli` перед коммитом — синтаксическая ошибка в блоке не видна при чтении и молча ломает рендер. ### Что из этого следует 59. **Триаж — сток по определению, а не «стадия 5».** Отсюда без отдельного обоснования следует правило, которое раньше приходилось защищать: на неполном графе триаж не запускается, потому что агрегировал бы половину и выглядел бы полным. 60. **Словарь рёбер общий у ревью и батча.** «Жёсткая зависимость» и «сериализуемое пересечение» в `task-batch` — те же два вида рёбер; формулировки сведены, и в обоих скиллах стоит ссылка на другой. 61. **Значения режима стали `по графу` и `линейно`.** Прежние «параллельно» и «последовательно» описывали способ запуска, а не структуру; линеаризация осталась отступлением с тремя причинами (оператор, занятая машина, разбор самого конвейера). 62. **Проход, держащий машину, знает об этом из своего charter'а.** `adversary` и `ops` получили по абзацу: цепочка гарантирует им чистое железо, значит их число — оракул, и шум в нём объясняется замером, а не соседом. 63. **У каждой диаграммы объявлено старшинство — это цена второго дома.** Схема и проза вокруг неё описывают один факт, и разойтись они могут молча: то самое, против чего написан `copies.py`. Механической сверки здесь нет — дословного соответствия между текстом и графом не существует, — поэтому работает объявление: **в `review-pipeline` старший граф** (он и есть алгоритм планировщика, проза объясняет рёбра), **в остальных местах старшая проза** (диаграмма там сводка). Для агента это не философия: без объявления он идёт за тем, что конкретнее, то есть чаще за схемой. 64. **Рендер диаграмм проверяется скриптом, а не памятью автора.** `scripts/diagrams.py` вынимает все блоки `mermaid` и гонит их через `mmdc` или `npx @mermaid-js/mermaid-cli`; коды выхода — общий словарь, нет рендерера — код 3, а не молчаливый успех. Причина та же, что у остальных проверок репозитория: **ошибка в блоке не видна при чтении** — текст правдоподобен, дифф разумен, падает только рендер. Расхождение с прозой скрипт не ловит и не притворяется, что ловит: это работа правила 63. ## 16. Каталог вместо файла в `docs/` — отложено до переезда healthlog (2026-08-04) ### Что было Вопрос: разрешить документам в корне `docs/` быть не только файлом, но и каталогом — когда документ описывает несколько принципиальных решений или перерастает 400–500 строк. Паспорт остаётся файлом в любом случае: компактность и есть его функция. Механизм в каноне уже работает — `conventions/`, `research/`, `adr/` каталоги с обязательным `README.md`-индексом, — так что вопрос не «можно ли», а «от чего лечим». ### Решено **CCC. Порог в строках триггером не становится.** Замер по проектам: у порога ровно один документ — `healthlog/docs/architecture.md`, 1662 строки. В нём десять маркеров долга, а разделы — «Слои гранулярности» (211 строк), «Тренировки и прочие секции» (222), «Условный запрос», «Свёртка и размер ответа», «Форма ответа». Это **поведение**, чей нормативный дом `openspec/specs/`, где у проекта уже лежат пять capability. Остальные документы 108–438 строк, `jellybit` — 169. Порог сработал бы ровно там, где надо не разносить, а доводить переезд, и дал бы долгу постоянное жильё: разложить 1662 строки по файлам дешевле, чем вынести их в спеки, а после раскладки давление исчезнет и второй дом поведения останется навсегда. **DDD. Шов выноса — другой читатель или другой срок жизни, а не размер.** По этому критерию кандидатов два. `review.md` — сильнее прочих: у него уже записаны два раздела с разными сроками жизни, настройка конвейера стабильна и читается проходами, а журнал дефектов растёт неограниченно. `architecture.md` — по шву «окружение, деплой, наблюдатель», у которого отдельный читатель `ops`. А вот расщепление архитектуры **по принципиальным решениям отвергнуто**: у факта «почему решено так» дом `adr/`, и вынесенные разделы немедленно станут его вторым домом. **EEE. `security.md` и `passport.md` каталогом не становятся.** У `security.md` ценность именно в цельности: периметр первой строкой и «что вне модели» читаются враждебным проходом за один раз, а разнесённые — расходятся первыми. У `database.md` механизм заводить не под что: 241 и 211 строк. **FFF. Если вводить — точка входа остаётся одна.** `docs/architecture.md` упомянут в репозитории 66 раз: девять charter'ов, карта `project-facts.md`, `docs.py`, скелеты. Развилка «файл или каталог» размножится на девять «прочитай либо обойди». Поэтому форма жёсткая: каталог легален только при `<имя>/README.md`, и он **и есть** прежний документ — обзор целиком со ссылками на вынесенное, а не оглавление к нему. Каждый файл каталога обязан быть достижим ссылкой из `README.md`; это проверяется сегодняшним механизмом ссылок `docs.py` и ловит файл-сироту. Вынеся раздел, `README.md` на него **ссылается, а не пересказывает** — тот же приём, которым в архитектуре уже описаны компоненты со ссылкой на capability. **GGG. Решение отложено до конца переезда `healthlog` (шаг 2 TODO).** Порядок: довести поведение в спеки, замерить остаток. Жмёт после этого — вводить каноном версии 3, и сразу для `review.md` и `architecture.md`, а не для всех документов корня скопом. ### Что из этого следует 65. **Цена изменения — версия канона, а не правка одного файла.** Обратной совместимости у канона нет, поэтому в счёт входят: `docs.py` (`check_stray` с его `ALLOWED_FILES`/`ALLOWED_DIRS`, `check_required` — обязательный путь становится развилкой, `check_capabilities` — сегодня читает ровно один файл), `skeletons.md`, `project-facts.md`, девять charter'ов, запись в `changelog.md` канона и ветка `upgrade` в скилле `canon`. 66. **Раздутый документ канона — сначала подозреваемый, потом кандидат на вынос.** Диагностика перед раскладкой — счёт маркеров долга (`grep -c "`, а требовал ``; нашлось это первой же попыткой ими воспользоваться. Пример в докстроке — тот же образец, что плейсхолдер в схеме. ## 29. Обкатка `doc-consistency` на самом dev-skills (2026-08-05) Первый прогон агента — по репозиторию, который его же и содержит. Два прохода (av-dev-pm; пайплайн плюс верхний уровень), 17 находок, все подтверждены по файлам. **ЧЧШШ. Агент нашёл ровно тот класс, ради которого заводился, и в свежей работе.** Пять находок — остатки прежней модели типов в файлах, которые я не дошёл поправить двумя коммитами раньше: `adopt.md` держал имена секций **канона 2**, `from-review.md` и `TODO.md` — упразднённый `[idea]`, `task-batch` в другом плагине — «задачи-идеи». Правку модели я вёл от документов, которые менял, а не от документов, которые на них ссылаются, — и обратный обход не сделал ни разу. **ЩЩЪЪ. Самая дорогая находка была моей и свежей.** Таблица типов в `canon.md` объявляла цель у `fix` запрещённой, а `tasks/SKILL.md` и `task-fix.md` — необязательной; код на стороне вторых. Копия разошлась с домом **за один день** — я написал обе половины в одном коммите. Это и есть цена второго дома в чистом виде: не «когда-нибудь разойдётся», а «разошлось прежде, чем высохли чернила». Исход не «поправить значение», а **убрать причину**: `canon.md` дважды объявлял, что фиксирует только словарь типов, — значит колонкам «разделы» и «цель» в нём не место. Осталась таблица из двух колонок и ссылка на дом схемы. **ЫЫЬЬ. Копии перечня «чем держат проект» разъехались втроём.** `canon.md`, `tasks/SKILL.md` и `task-goal.md` пересказывали его своими словами: «метрики и логи» против «мониторинга», «проверки» есть в двух из трёх. При этом `tasks/SKILL.md` **ссылался на дом рядом с собственным пересказом** — ссылка не мешает копии разойтись, если копия всё равно стоит. **ЭЭЮЮ. Находка про коммиты снята как неверная, и это дефект самого агента.** Он прочитал `av-dev-git/skills/commit/SKILL.md` («без `Co-Authored-By`») как описание практики этого репозитория и предъявил 38 коммитов с трейлером. Но dev-skills — **маркетплейс плагинов**: скилл коммита здесь продукт, уезжающий в чужие проекты, а не правило, которому подчиняется сам репозиторий. Устав агента не различает «документ описывает этот репозиторий» и «документ описывает то, что репозиторий производит». **ЮЮЯЯ. Счётчики в документах отменены как класс.** `REMAINING.md` держал «после разбора двенадцати тем и 16 коммитов» (стало 28 и 52) и «три неизмеренных изменения подряд» (стало больше). Оба числа обязан двигать человек, и оба отстали молча. Заменены на формулировки, которые не надо поддерживать, и в шапку записана причина. ### Что из этого следует 109. **Правка модели идёт по обратным ссылкам, а не по изменённым файлам.** Меняешь дом — обойди тех, кто на него ссылается: `grep` по упразднённому слову дал бы все пять остатков за минуту. Это дешевле любого агента и должно идти до него. 110. **Ссылка на дом не отменяет копию, стоящую рядом.** Проверять надо не «есть ли ссылка», а «есть ли пересказ»; `tasks/SKILL.md` имел и то и другое. 111. **Копия расходится с домом в пределах одного коммита.** Прежняя оценка («разойдётся на первой правке») занижена: расхождение возникает при написании, если оба места пишет один проход. 112. **Агент, читающий репозиторий-продукт, обязан различать «про нас» и «про то, что мы производим».** Иначе он предъявляет продукту практику его потребителя. Устав `doc-consistency` этого различения не содержит — остаток записан в REMAINING. 113. **Число в документе — обязанность, которую никто не берёт.** Счётчик тем, коммитов, правок протухает молча; формулировка без числа дешевле его сопровождения. ## 30. `av-dev-backlog` удалён (2026-08-05) Плагин был помечен устаревшим решением Q и жил до перевода jellybit. Удалён раньше этого срока. **ААББВВ. Замороженный плагин стоит дороже, чем кажется.** Он не менялся, но платил собой в каждой проверке репозитория: `exclude` в `pyproject.toml`, `SKIP_DIRS` в `copies.py`, два абзаца README, оговорка в описании маркетплейса, чтобы не ловить триггер «добавь задачу в беклог». Пять исключений ради кода, который никто не читает, — и каждое надо было объяснять всякий раз, когда кто-нибудь спрашивал, почему проверка обходит каталог. **ААББГГ. Понимание старой раскладки уехало из плагина раньше самого плагина.** `docs/backlog/` читает не `backlog.py`, а `av-dev-pm:tasks` — `adopt.md` и адаптер в `tasks.py` держат ту же раскладку как **вход миграции**. Плагин перестал быть единственным, кто её знает, ещё когда писался `adopt`; условие «живёт до перевода последнего проекта» с тех пор охраняло пустоту. **ААББДД. Опасение про порядок снятия не подтвердилось.** Удаление опередило снятие: на jellybit плагин оставался включённым, когда записи в маркетплейсе уже не было, и ожидалась ручная чистка `enabledPlugins` и `installed_plugins.json`. `claude plugin uninstall` отработал штатно — он идёт **по реестру, а не по манифесту маркетплейса**, и отсутствие записи там ему безразлично. Предупреждение из README снято, вместо него записан проверенный факт. ### Что из этого следует 114. **Устаревшее удаляют, а не замораживают.** Заморозка выглядит бесплатной, но растекается исключениями по конфигам и требует объяснения в каждом месте, куда попала. Если удалять пока рано — назвать условие и срок; условие без срока переживает свою причину. 115. **Условие «живёт до X» проверяют на живость, а не на X.** Здесь X (перевод jellybit) не наступил, но причина условия отпала раньше: знание раскладки переехало в `adopt`. Перепроверять надо основание, иначе условие держит само себя. 116. **Порядок снятия и удаления из маркетплейса свободный.** `uninstall` живёт реестром, манифест ему не нужен. Правило записано после проверки, а не из осторожности, — и осторожность здесь стоила бы лишнего абзаца в README про починку, которой не бывает. ## 31. Ревизия покрытия `av-dev-pm` продакт-оптикой (2026-08-05) Сабагент в роли продакт-менеджера оценил покрытие жизненного цикла личного проекта (один человек, недели-месяцы) скиллами и агентами `av-dev-pm`. Скоуп сужен по ходу разбора: деплой и разбор инцидентов на проде делаются вручную, скиллов под них не заводим. Осталось планирование, разработка и доработка. **ААББЕЕ. Шаг 2 сессии требовал чисел, которых процесс отказался собирать решением.** `cadence.md` делал обязанностью пересмотр «ориентира по размеру спринта, прироста беклога на закрытую задачу, времени на задачу» и «сколько заняли задачи **против ожидания**» — с обоснованием «иначе обязанность висит ничья». Данных под это нет: у записи нет дат заведения, взятия и закрытия, `close --implemented` удаляет файл, `sprint close` очищает `SPRINT.md`. Хуже того, «против ожидания» и «время на задачу» требуют оценки и тайм-бокса, а `session/SKILL.md` в «Почему не Scrum» их прямо не берёт: пункт противоречил решению, стоящему через файл от него. Исход — **выкинуть, а не подпереть данными**. На практике числа не пересматривались ни разу, и заводить под них учёт дат значило бы обслуживать обязанность, которой никто не брал. Осталось качественное: что сломалось в процессе, что оказалось дороже, чем выглядело при заведении, какие правила не сработали. Шаг 3 пункт 9 переименован из «переоценки по измеренному» в «переоценку по пройденному», судит человек по памяти о спринте. Рядом записано, что замеров нет **намеренно** — иначе следующий читатель заведёт их обратно как недостающие. **ААББЖЖ. `doc-consistency` переехал с каждого синка на сессию, к `doc-code-drift`.** Агент на `opus` зовётся шагом 9 пайплайна, то есть на каждой задаче: 5–8 opus-проходов за спринт по документам, которые за спринт меняются на несколько абзацев. Обоснование в каноне («сверка текста с текстом дёшева») верно относительно второго агента, но не в абсолюте на одиночке. Довод сильнее денег: **расхождение между двумя документами по определению требует двух документов**, а на большинстве задач синк правит один. И пачка, отбираемая работой, не видит того, чего работа не касалась, — а расхождение живёт ровно там: правка отменяет решение в одном документе, парный статус нужен в другом. Это был открытый вопрос `REMAINING` про охват ADR при пересмотре; переезд его закрыл. Цена — потеря привязки находки к задаче, которая её породила: по теме 29 именно эта привязка дала пять самых точных находок. Принято сознательно. **ААББЗЗ. Отмена цели получила порядок, но не флаг.** `close` запрещал закрыть цель с живыми задачами, а что делать с этими задачами, не говорил нигде: шаг 3 сессии знал только «та ли цель», `task-goal.md` описывал одно достижение, а `session/SKILL.md` вдобавок утверждал «цель постоянна». Человек получал отказ с перечнем и никакой подсказки. Порядок записан: сперва задачи поштучно (`close --reason` своей причиной либо `edit --goal` на другую цель), потом сама цель через `close --reason` в `REJECTED.md`, а не в `Готово` — отменённая цель не умеет ничего. Флаг `--cascade` отвергнут: отмена цели редка и дорога, и поштучный разбор здесь не церемония, а единственный момент, когда видно, что из задач переживёт цель. Каскад превратил бы его в один Enter. **Причина у каждой задачи своя**: «цель отменена» это пересказ команды, в `REJECTED.md` от него нет пользы через квартал. Место процедуры — переоценка на сессии, а не отдельный заход: отмена цели **и есть** разбор всех её задач, а разбор задач — шаг 3. **ААББИИ. У брошенного спринта появился второй законный исход, без порога.** `--dissolve` во всех текстах был привязан к блокеру, и скрипт отказывал словами «роспуск объясняется блокером». Вернувшийся к набору, который стоял месяц, не имел законного хода: двигать нельзя (заморозка), распускать не по чему. Теперь роспуск объясняется блокером **или тем, что набор протух**. Порога в неделях сознательно нет — это тот же класс, что выкинутые числа шага 2: счётчик простоя пришлось бы вести руками, а решает всё равно человек. Признак не срок, а **что набор перестал быть твоим**: перечитываешь, зачем эти задачи вместе — он протух. Туда же добавлена точка входа «вернулся, а спринт открыт»: `check`, `SPRINT.md`, развилка продолжать/распустить. Середины у развилки нет намеренно — «доделаю пару штук и решу» это работа по набору, которого ты не понимаешь. **ААББКК. Журнал канона прогоняется как есть, а проверка исхода поручена судьям.** Схлопнуть записи 3 и 4 в один переход «с 2 на 4» отвергнуто: журнал описывает не только *что сделать*, но и порядок, в котором это делалось, и слитая запись экономит один проход ценой невоспроизводимости остальных. Оба живых проекта пройдут 2→3→4 по записям. Взамен появилась проверка исхода: **шагом 6 `adopt` и шагом 6 `upgrade` зовутся оба судьи документов**. Это прямой ответ на открытый вопрос REMAINING «как проверять, что канон не разошёлся с проектами после `upgrade`»: `check` сверяет **число** в `.pm.json` с версией скрипта и про существо записи не знает ничего. Проект несёт `"canon": 4` и может не иметь того, чего требовала любая из пройденных версий — записи применяются руками, а ручной проход по трём записям подряд ровно то место, где половина шага делается и забывается. У `adopt` добавка другого рода: там судьи ловят не недоделанную миграцию, а последствия переноса — факт, растащенный по двум домам, поведение, осевшее в `architecture.md`, ADR, оторванный от своего `design.md`. Им передаётся объявленное переходное состояние из шага 5, иначе честная строка в незаполненном слоте вернётся находкой. ### Что из этого следует 117. **Обязанность без источника данных отменяют, а не механизируют.** Первый позыв — дать шагу данные (дописать даты, сводку спринта). Но обязанность, не исполнявшуюся ни разу, дешевле снять: механизация под неё производит учёт, который надо вести, ради разбора, который не делается. 118. **Требование, противоречащее решению через файл от него, — не мелочь, а признак копии.** «Против ожидания» пережило решение «не берём оценки», потому что стояло в другом документе. Обратный обход по решению «что мы не берём» нашёл бы это сразу — тот же приём, что и следствие 109. 119. **Частота вызова агента выводится из того, что он ищет.** Судья расхождений **между** документами бессмысленен там, где документ один; значит его место не на задаче, а на наборе задач. Цена вызова подтвердила вывод, но не она его дала. 120. **Запрет обязан называть выход.** `close` верно не давал осиротить задачи, но текст отказа перечислял препятствия и молчал о ходе. Проверка без названного следующего шага — половина работы: она защищает данные и бросает человека. 121. **Признак вместо порога там, где счётчик пришлось бы вести руками.** «Набор перестал быть твоим» проверяется в момент вопроса и ничего не требует хранить; «прошло N недель» требует учёта, который никто не ведёт, и всё равно кончается решением человека. 122. **Версионирование без единого переехавшего проекта — не журнал миграций, а история правок.** Довод за схлопывание был верен по факту и отвергнут по принципу: обкатка на живых проектах и проверяет, работает ли механизм. Схлопнуть значило бы не прогнать его ни разу и оставить вопрос открытым. 123. **Проверка версии не есть проверка миграции.** Число в `.pm.json` двигает тот же проход, что делал шаги, — и двигает независимо от того, все ли сделаны. Механической проверки существа нет; там, где её нет, ставится судья, а не отметка. ## 32. Сквозной проход по словарю: пять слов сняты, девять закрыты списком (2026-08-05) Проход упрощения (тема 31) уткнулся в один и тот же класс у всех пяти агентов: слово, живущее в трёх-шести файлах разом. Правка в одном месте развела бы словарь, правка во всех — уже не упрощение текста скилла. Каждый агент честно остановился и записал слово в свой отчёт, и одни и те же слова всплыли в разных отчётах. Разобрано отдельным проходом. **ААББЛЛ. «Слово прижилось» не проверяется, поэтому заменено списком.** Оговорка в `language.md` звучала так: не переводится «термин, у которого нет точного русского эквивалента и который в команде уже прижился». Проверить это на глаз нельзя — прижившимся выглядит любое слово, встреченное трижды, и ровно так пять агентов подряд и рассудили. Оговорка заменена **закрытым списком из девяти терминов** с колонкой «что называет»: интейк, триаж, провенанс, дедуп, чек-лист, дифф, промпт, сущности OpenSpec, роды проходов ревью. Слово не из списка и не из таблицы имён вещей — находка, а не принятый стиль. Список заведён домом `язык-словарь` в `language.md` и копией в уставе `doc-wording`. Копия обязательна: агент работает в репозитории проекта, где плагина может не быть, и без списка предъявил бы «интейк» как англицизм. **ААББММ. Пять слов сняты, и все пятеро выглядели словарём, не будучи им.** `конфляция` → смешение (4 места), `декорреляция` → разведённость (6), `непоймание` → почему не поймали (9), `эвал-сет` → проверочный набор (4), `гайд` → руководство (6). Латинизм или калька при живом русском слове в каждом случае. Разбор `декорреляции` показателен: проект **уже владел** нужным словом — «агенты разведены по глубине», «разведены по охвату» — и держал рядом латинский синоним того же понятия. Это не англицизм, а второй дом для слова. `непоймание` снято ещё и потому, что форма журнала дефектов, которую канон кладёт в проекты, спрашивает «Почему не поймали» — а проза рядом называла это «причиной непоймания». Скелет и проза о скелете говорили разными словами. **ААББНН. Снятое записано вместе с оставленным, в одном списке.** Иначе снятое возвращается: слово уходит из текстов, но ничто не мешает следующему проходу завести его заново — оно ведь короткое и точное. Пять слов названы поимённо с заменой каждого. ### Что из этого следует 124. **Escape hatch без перечня — это разрешение, а не исключение.** «Термин, который прижился» освобождает от правила любое слово: проверка «прижился ли» возвращает «да» всякий раз, когда слово встретилось. Исключение из правила обязано быть списком, иначе оно съедает правило. 125. **Слово, от которого агент отказался править, — материал для отдельного прохода, а не мусор отчёта.** Пять независимых агентов сошлись на одном наборе слов, ни разу друг друга не видя. Список «что не тронул» оказался полезнее списка правок именно этим. 126. **Снятое слово называется вместе с заменой и остаётся записанным.** Убрать из текстов недостаточно: без записи «это снято и вот чем заменено» слово возвращается первым же, кто найдёт его удачным. ## 33. Стоимость ревью: снят самый дорогой проход и самая дорогая модель (2026-08-06) Прогоны стали долгими, а счёт в токенах — заметным. Разбор шёл не по находкам, а по статьям расхода: что в конвейере стоит больше всего и что из этого окупается. Две статьи названы прямо оператором. **ААББОО. Проход независимой реализации снят целиком, и с ним профиль `deep`.** `reimpl` писал свою реализацию узла, не открывая существующую, и диффил по решениям. Его счёт определялся **объёмом вывода** — он один писал код, а не читал его, — и на прогоне это была самая большая строка расхода. Снят по решению о стоимости. Профиль `deep` от этого не «похудел», а исчез: `reimpl` был **единственным**, чем он отличался от `wide` (обоим оставалось бы 0, 1, 2, 4, 5). Держать два имени для одного состава нельзя — ровно от этой болезни лечилась ступень `wide` (решение JJJ): у профиля обязан быть один правильный ответ, иначе реестр состава нечем проверять. Ступеней теперь три: `quick`, `standard`, `wide`. Вместе с профилем ушло всё, что обслуживало только его: - **барьер стоимости** — он существовал ровно затем, чтобы дорогой проход не писал реализацию против кода, который через час перепишут. Дорогого прохода нет, и граф стал плоским во всех профилях: от гейта до триажа. Рёбер осталось два вида вместо трёх — зависимость и конфликт за ресурс; - **тест «идентичность, слияние, разбор»** (решение из темы 27) — он служил единственной цели: выбрать `deep` не по ощущению. Выбирать больше нечего, и полторы страницы теста сняты вместе с проектным перечнем мест в `docs/review.md`; - **стадии перенумерованы**: 0 гейт, 1 сверка, 2 враждебный и эксплуатационный, 3 архитектурный, 4 триаж. Дыра на месте третьей читалась бы как пропущенная стадия. **ААББПП. Снятие записано как сознательное сужение, а не как «класс оказался пустым».** `calibration.md` требует замера на двух проектах перед удалением прохода, и замера не было — было решение о цене. Значит и в «Честном пределе» стоит честная строка: **«не знаю, чего не знаю» больше не достаёт никто.** Остаток независимого взгляда дают профиль `design` (код пишется под его находки) и `architecture` (второй способ, лишние слои), но альтернативной реализации, с которой можно сдиффить решения, у конвейера нет. Класс уходит в границы покрытия каждого прогона, а у проекта — в подраздел «перестали проверять сознательно». Без этой записи снятие через месяц читается как «проверено и признано лишним», и вернуть проход было бы не на чем. **ААББРР. Самая дорогая модель снята со всех проходов.** На ней сидели трое: `review-triage`, `review-architecture` и `doc-code-drift` из `av-dev-pm`. Все трое переведены на `opus`. Основание для верхней модели — «ошибка распространяется дальше самой находки» — никуда не делось, но оно объясняет, почему эти двое **не опускаются до `sonnet`**, а не почему им нужна ступень выше `opus`: разницы в пользу более дорогой модели не показал ни один прогон, а время и счёт она множила. Палитра цветов схлопнулась до двух: `sonnet` → green, `opus` → yellow. Красного в репозитории больше нет, и `frontmatter.py` теперь отвергнет модель вне этих двух — раскладка проверяется механически, как и раньше. ### Что из этого следует 127. **Профиль, у которого не осталось собственного прохода, — не профиль.** Ступень стоимости определяется тем, что она **добавляет**; сняли добавку — сняли ступень, а не оставили имя. Иначе два имени указывают на один прогон, и состав снова нечем проверить. 128. **Удаление по цене и удаление по замеру записываются по-разному.** Первое обязано назвать класс, который перестал проверяться, и оставить его в границах покрытия. Второе — сослаться на замер. Смешение их даёт самый дорогой вид тишины: пробел, выглядящий как решённый вопрос. 129. **Механика, обслуживающая один проход, снимается вместе с ним.** Барьер стоимости, тест выбора верхней ступени и проектный перечень мест держались только на `reimpl`. Оставшись, они выглядели бы работающими правилами и тратили бы внимание на каждом прогоне. ## 34. Пропускная способность против глубины: тяжёлые проходы уехали в верхнюю ступень (2026-08-06) Тема 33 сняла самую большую разовую статью расхода, но не тронула главную — **частоту**. Меряющая пара стояла в `standard`, то есть на большинстве задач, и именно она делала прогон долгим: два прохода держат машину, идут цепочкой и доказывают находки запуском. Разбор шёл от цели, названной прямо: **лучше поправить в следующей задаче, чем держать одну два часа.** **ААББСС. `adversary` и `ops` переехали в `wide`, и это решение по цене, а не по ценности.** Стадия осталась самой урожайной за всю историю замеров — пять из семи выживших находок дозапуска и единственная находка про молчаливый старт отката. Но её ценность оплачивается на **каждой** задаче, а получается на немногих: оракул добывается запуском, запуск — это машина, цепочка и часы. Ступень, которая раньше была умолчанием, стала исключением на 5–10% задач. **ААББТТ. Заведён `review-basics` — мелкая осадка двух тяжёлых проходов, без единого запуска.** Он стоит только в `standard` и берёт ту половину вопросов, на которые отвечают **чтением**: таймаут и отказ соседа, идемпотентность и одновременная запись, остановка на середине, частичный откат при двух версиях, наблюдаемость и тишина, очевидный рост объёма — плюс два вопроса архитектурного: второй способ мимо единой точки (грепом, не картой) и что отсюда удалить. Потолок 4 находки, машину не держит, ничего не меряет. Отдельная его обязанность — **вопрос 4, частичный откат**. Без него правило «миграция схемы не поднимает ступень» рассыпалось бы: раньше миграцию разбирал `ops`, а он теперь в `wide`. Проход заведён не «до кучи», а затем, чтобы у `standard` остался хоть один взгляд на ось времени. Модель у него верхняя, `opus`, и это не противоречит слову «средний»: усилие режется **входом и потолком**, а не моделью. Дешёвая модель на опиниативном проходе платит триажем — это записанный замер, и отменять его без нового замера нельзя. **ААББУУ. Объём и незнакомость изменения вошли в правило выбора ступени.** Раньше ступень выбиралась только по классу («вводит ли новое понятие»), и правило прямо запрещало смотреть на размер. Теперь вопросов два: крупное или незнакомое (трогает несколько узлов, переносит ответственность, форму решения нащупывают по ходу) → `wide`; мелкое (один узел, форма очевидна заранее, откат — обратная правка) → `quick`; всё остальное → `standard`. Причина смены: цена разбирательства растёт именно с объёмом и неизвестностью, а не с классом правила. Отрицательный тест `quick` сохранил прежнюю мудрость в новой рамке: **что после мерджа не откатывается обратной правкой — не `quick`, каким бы маленьким ни был дифф.** Три строки миграции идут в `standard`. **ААББФФ. Спорный случай решается вниз, и асимметрия объяснена ценой.** Между `standard` и `wide` — в пользу `standard`: ошибка сюда стоит находки на следующей задаче, ошибка обратно стоит трёх тяжёлых проходов на каждой задаче, выбранной неверно. Между `quick` и `standard` — тоже в пользу `standard`, но по другой причине: там разница в один дешёвый проход, зато единственный, кто на нижних ступенях смотрит на отказы. Доля `wide` 5–10% записана как **проверка правила, а не пожелание**: если ступень уходит каждой третьей задаче, её выбирают по ощущению важности. **ААББХХ. Сделка записана вместе с механизмом обратной связи, иначе это тихая потеря качества.** На `quick` и `standard` не проверяется ничего, что требует запуска: построенный путь, эксперимент против драйвера, любое число. Это самая крупная граница покрытия конвейера, и она обязана идти строкой в каждом таком прогоне поимённо. Обратная связь — журнал дефектов `docs/review.md`: класс, который ловят только меряющие проходы, начал всплывать после мерджа — значит ступень выбирают слишком низко. Плюс сам `basics` обязан сигналить строкой, если видит, что ступень занижена: он единственный, кто смотрит на дифф целиком на нижних ступенях. ### Что из этого следует 130. **Стоимость прохода — это его цена, умноженная на частоту, и вторая переменная важнее.** Тема 33 убрала самый дорогой проход, тема 34 — самый частый. Второе дало больше, хотя снятый проход был дешевле каждого отдельного `reimpl`. 131. **Урожайность прохода не отвечает на вопрос, где ему стоять.** Меряющая пара осталась самой ценной и всё равно уехала вверх: ценность оправдывает существование прохода, но не его частоту. 132. **Замена тяжёлого прохода лёгким записывается как сужение, а не как эквивалент.** `basics` задаёт те же вопросы чтением, и его ответы поэтому слабее — условия вместо оракулов. Назвать это «покрыли то же дешевле» значит соврать себе на первом же прогоне. 133. **Ступень, выбираемая по классу изменения, слепа к объёму.** Правило, запрещавшее смотреть на размер, защищало от выбора по ощущению важности — и заодно отправляло трёхстрочную правку и переборку пяти узлов в один профиль. Признаков нужно два: класс отвечает за обратимость, объём — за цену разбирательства. ## 35. Ревизия моделей: переведены двое из девяти, и критерий оказался не тот (2026-08-06) Сквозной проход по тринадцати уставам с одним вопросом: кого из девяти `opus`-агентов можно опустить на `sonnet` без потери. Ответ — двоих, и по дороге выяснилось, что критерий, которым конвейер до сих пор раздавал модели, отвечает не на тот вопрос. **ААББЦЦ. Модель выбирается по цене ошибки, а не по роду прохода.** Прежнее деление — applicative против generative — раздаёт модели по тому, **откуда** проход берёт критерий. Но платит проект не за происхождение критерия, а за разбирательство с находкой. Рабочий признак: - находка приходит **со ссылкой на записанный источник** (строка спеки, цель в манифесте, значение в конфиге, номер правила) — её опровержение стоит одного открытия файла. Дешёвая модель ошибается здесь **проверяемо**; - находка есть **суждение** («это второй способ», «этот оракул негоден», «эти два документа противоречат») — опровержение стоит рассуждения, а рассуждение стоит триажа или человека. Признак объясняет прежнюю раскладку лучше, чем она сама себя: `gate`, `code` и `ops` не потому дёшевы, что применяют чек-лист, а потому, что каждая их находка показывает пальцем на строку. **ААББЧЧ. `doc-code-drift` → `sonnet`.** У него закрытый перечень из восьми правил, и каждое — пара «факт в документе ↔ команда, которой он проверяется». Устав прямо запрещает суждение («верность и полноту не проверяешь»), требует формы «написано X, в коде Y, проверено командой Z» и правила «нечем проверить — не находка». Ложная находка опровергается **той же командой, которая её породила**. Это самый чистый случай признака за весь разбор. **ААББШШ. `task-form` → `sonnet`.** Семь пронумерованных правил с таблицами форм и поимённым перечнем подмен. Но решило не это, а потребитель: его находка — готовая формулировка, которую человек читает и отклоняет командой, а не оркестратор, который **молча реализует**. Довод, державший `triage` на верхней модели, здесь не работает вовсе: ошибка стоит строки чтения. **ААББЩЩ. `review-specs` рассмотрен и оставлен на `opus` — по причине, обратной общей.** Он самый частый `opus`-проход конвейера (идёт и в `design`, и на коде, то есть дважды за задачу), и по устройству он applicative: SKILL.md сам называет стадию 1 «два applicative-прохода, оба дешёвые», хотя платит за одного `sonnet`, а за другого `opus`. Расхождение разобрано и закрыто текстом: держит его наверху направление `code → spec`, где надо заметить **отсутствие** — тихий фолбэк, самодеятельный дефолт, проглоченную ошибку. Прочие держат `opus` из-за цены ложных находок, этот — из-за цены пропущенных, а пропуск не оставляет следа нигде: ни в отчёте, ни в границах покрытия. **ААББЭЭ. Остальные шестеро оставлены, и у каждого своя причина.** `adversary` и `rubric` порождают критерий по построению (второй — с запретом открывать код в первой фазе). `architecture` — чистое суждение о структуре. `triage` — сток, его ошибка становится кодом. `doc-consistency` ошибается ровно в ту сторону, которую дороже всего опровергать: путает «упомянуто в двух местах» с «оба утверждают». `basics` заведён час назад, половина его вопросов — суждение, и модель у него выбрана решением оператора в этой же сессии. **ААББЮЮ. Это разбор уставов, а не замер, и так и записано.** `calibration.md` двигает модель инъекцией дефекта; здесь инъекции не было. Двое переведены потому, что их ошибка **обнаруживается той же проверкой, что породила находку**, — то есть цена ошибки ограничена сверху независимо от модели. Для остальных такой границы нет, и трогать их без замера нельзя. ### Что из этого следует 134. **Дешёвая модель безопасна там, где её ошибку опровергает та же команда, что породила находку.** Не «где критерий записан» — записанный критерий бывает и у суждения, и у сверки, а разница между ними в том, чем кончается спор. 135. **Ошибка бывает двух родов, и модель защищает от разных.** Ложная находка стоит триажа и видна; пропущенная не стоит ничего сегодня и не видна вовсе. Проход, у которого дороже второе, держится на верхней модели даже будучи applicative. 136. **Потребитель находки — часть её цены.** Одна и та же ошибка стоит строки чтения, если её читает человек, и разросшегося кода, если её молча реализует оркестратор. Модель раздаётся с оглядкой на это, а не только на устройство прохода. ## 36. Темы ревью: документ проекта стал направлением проверки (2026-08-06) Замечено при сверке документов канона с составом ступеней: **три документа остались без читателя ниже `wide`** — `security.md`, `database.md` и `adr/`. Проект поддерживал их, а на 90% задач их не открывал никто. Причина оказалась не в переезде проходов, а в том, как описан состав прогона. **ААББЯЯ. Тема первична, проход вторичен, и это правило 0 конвейера.** Список тем нигде не был записан: он существовал побочным продуктом списка проходов. Проход уезжал в верхнюю ступень — и тема уезжала с ним **беззвучно**: отчёт честно говорил «`ops` не запускался» и не говорил «эксплуатацию не смотрел никто», а нужно второе. Теперь прогон описывается таблицей «тема → дом → глубина → кто закрывает», и таблица есть в каждом отчёте. **АВААА. Тема есть документ, и список тем открытый.** Всё, что проект кладёт в `docs/`, становится темой ревью; запретить нельзя, разрешения не надо. Не темы ровно две: `docs/tasks/` и `docs/review.*` (настройка самого конвейера — слой над темами). Отсюда главное следствие: **`docs/` перестал быть документацией и стал конфигурацией конвейера.** Проект настраивает проверку тем, что пишет о себе, а не отдельным файлом настроек, который разошёлся бы с документами. Ядро — шесть тем: `requirements`, `autotests`, `conventions`, `architecture`, `security`, `operations`. Их дома канон обещает. Всё сверх — темы проекта, и их разбирает `basics`: именных проходов конечное число, а тем столько, сколько заведёт проект, поэтому приёмник обязателен. **АВААБ. Тема живёт файлом или каталогом, на выбор проекта.** `docs/security.md` и `docs/security/` — одно и то же. Прежде форма была задана поимённо (`conventions`, `research`, `adr` — каталоги, остальные — файлы), и обосновать это было нечем; заодно в TODO висел открытый вопрос «а если `architecture.md` разрастётся». Теперь ответ механический: разросся — стал каталогом с `README.md`, и это не смена версии канона. Обе формы сразу — ошибка, и `docs.py` её ловит: два дома для одного факта расходятся молча. **АВААВ. Ступень выбирает разметчик, а не автор.** Заведён `review-scope` (`sonnet`), стадия 0, до гейта: находит документы, выводит темы, назначает глубины, выбирает ступень с обоснованием. Довод сильнее, чем синхронизация документов: **до сих пор профиль называл тот же оркестратор, который написал код** — то есть в точке выбора глубины проверки разведённости с автором не было вовсе, и решала она под давлением «я почти закончил». Вызывающий пайплайн профиль больше не передаёт. Право у разметчика симметричное — поднять и понизить, — но обоснование обязательно всегда, а не только при отступлении от умолчания. **АВААГ. Разметчик передаёт адреса, а не пересказ.** Проект однажды уже держал файл-посредник между документами и проходами (`review-brief.md`) и убрал его: второй дом расходится с первым и выглядит актуальным. Пересказ в задании — тот же посредник, живущий один прогон. Исключение одно: **отсутствие дома** — этого проход сам дёшево не выяснит. `sonnet` ему хватает потому, что вывод устроен как **список**: каждый файл в `docs/` обязан попасть в план темой или строкой «не тема, потому что», и план сверяется с `ls docs/` за секунду. Выбор ступени — суждение, но у него три независимых корректора: отрицательный тест `quick`, правило «спорный случай вниз» и сигнал `basics` о заниженной ступени. **АВААД. `quick` и `standard` совпали составом и разошлись глубиной.** Требование «нижние ступени закрывают все темы, просто не так глубоко» иначе не выполняется: темы одни и те же, а различать ступени больше нечем. Глубин три и они про способ доказательства, а не про старательность: **сверка** (открыть дом, открыть дифф, сравнить), **разбор** (построить сценарий рассуждением), **доказательство** (прогнать, померить, построить путь). Третья есть только в `wide` — она одна и требует машины. Цена принята: это единственное место конвейера, где профиль не выводится из списка проходов, поэтому глубина объявляется в отчёте наравне со ступенью. **АВААЕ. `review-code` переписан: технический разбор плюс конвенции.** Обнаружено по ходу: **никто не читал код как код.** `specs` сверял с требованиями, `basics` — с отказами окружения, `architecture` — с устройством, а `code` был проходом только по прозаическим конвенциям и прямо объявлял, что дефекты рантайма и логики не его. «Здесь ошибка в логике» не говорил никто, и это была самая крупная дыра конвейера — крупнее любой недосмотренной темы. Теперь у прохода две половины: девять классов технического дефекта (необработанная ветка отказа, пустое и нулевое, граница диапазона, перепутанный операнд, неосвобождённый ресурс, изменение под итерацией, неверно применённый интерфейс библиотеки, недостижимая ветка, «сделано соседнее») и прежняя сверка с конвенциями. Модель поднята до `opus` по признаку темы 35: цена **пропущенной** находки — дефект в проде, и она не оставляет следа ни в отчёте, ни в границах покрытия. **АВААЖ. Вопросы проекта переадресованы темам.** В `docs/review.*` было «Вопросы к проходам» в форме `ops: <вопрос>` — и когда `ops` уехал в `wide`, вопрос перестал задаваться молча. Стало «Вопросы по темам». Туда же «Недоступно проверке» — по темам, обоими подразделами. ### Что из этого следует 137. **Состав, описанный исполнителями, теряет предмет при перестановке исполнителей.** Список проходов отвечает «кто работал», а нужен ответ «что проверено». Первое выглядит полным ровно тогда, когда второе неверно. 138. **Открытый список нуждается в приёмнике, иначе он обещание.** Разрешить проекту завести свою тему и не назначить, кто её разбирает, — то же, что не разрешать. 139. **Регулятор глубины проверки нельзя оставлять в руках автора.** Не потому что он злонамерен, а потому что давление «я почти закончил» действует всегда и в одну сторону. 140. **Дыру в покрытии находят не там, где ищут находки.** Три осиротевших документа нашлись сверкой канона с составом ступеней, а отсутствие технического ревью кода — сверкой оптик проходов между собой. Ни то ни другое не всплыло бы на прогоне: прогон честно сообщал, что все запущенные проходы отработали. ## 37. `gate` и `autotests` сведены к одному имени (2026-08-07) Тема звалась `autotests`, закрывающий её проход — `gate`, и на всех трёх ступенях это была одна и та же клетка таблицы. Одна сущность под двумя именами — та же ошибка, что и два разных под одним, только тише: она не путает, а **теряет**. Вопрос проекта в `docs/review.*` адресуется теме; адресованный проходу — не приезжает никуда, и ровно этот отказ уже случился однажды с `ops` (тема 36, АВААЖ). **АГААА. Победило имя темы, а не имя прохода.** Три довода, по убыванию веса: 1. **Тема первична (правило 0), а имена тем — это имена документов.** `docs/autotests.md` проект напишет: что покрыто, что нарочно нет, где `testdata`. `docs/gate.md` не напишет никто — гейт это команда, а не предмет. 2. **Слово «гейт» уже занято дважды** — команда проекта и ребро графа («пока гейт красный, опиниативные не идут»). Третье значение сделало бы отчёт нечитаемым: «гейт красный» и «гейт нашёл» — про разное. 3. **Тема шире гейта.** «Хватает ли проверок» и «чего в гейте намеренно нет» за пределы красного/зелёного выходят. Назвать целое именем инструмента — тихо его сузить. Цена названа честно: `autotests` звучит уже своего содержимого — линт, типы, сканер уязвимостей тестами не являются. Гасится строкой в уставе: тема — это «проверено ли машиной», а не «есть ли тесты», и гейт в ней инструмент, а не граница. ### Что из этого следует 141. **Тема и проход, совпадающие один в один на всех ступенях, обязаны носить одно имя.** Пока имён два, у сущности два адреса, а адресуют её по одному — и какой из двух окажется живым, решает случай. 142. **Слово, уже значащее что-то в предметной области проекта, нельзя брать именем роли конвейера.** «Гейт» принадлежит проекту раньше, чем ревью, и спор за него ревью проигрывает. ## 38. Шов между плагинами: канон не называет имён проходов (2026-08-07) Замечено при сведении тем документации с ревьюверами: `av-dev-pm` в шести местах называл конвейер поимённо — от прозы канона до **вывода `docs.py` пользователю** («свои темы проекта: … — их разбирает `review-basics`»). Плагины при этом раздельные: `av-dev-pm` работает без конвейера, `av-dev-pipeline` — без канона, поразрядно деградируя. **АДААА. Общий словарь — имена тем и имена ступеней, и только они.** Ими проект настраивает ревью: вопросы по темам и триггеры профиля. Имён проходов канон не называет нигде. Направление зависимости при этом несимметрично и это верно: **конвейер называет документы канона поимённо, потому что он их читатель**, а обратной ссылки быть не может — документ живёт дольше, чем раскладка проходов. Заодно вычищены описательные адресации того же класса: «архитектурный проход судит», «враждебный проход выдумает», «там идут враждебный, эксплуатационный и архитектурный проходы». Последняя — худшая из них: это утверждение о **составе ступени**, живущее на стороне, которая о составе не знает. **АДААБ. Пример в правиле не должен нарушать само правило.** Объяснение, почему вопросы адресуются темам, звучало так: «вопрос, адресованный `ops`, перестал задаваться в тот день, когда `ops` уехал в верхнюю ступень». Правило про нестабильность имён, иллюстрированное именем. Стало «адресованный проходу» — и работает даже после того, как проход переименуют. ### Что из этого следует 143. **Ссылка из вывода скрипта дороже ссылки из прозы.** Устаревшую строку в документе чинит тот, кто её читает; устаревшее имя в сообщении `docs.py` доезжает до чужого проекта и там объясняется недоумением. 144. **Список, который никто не ведёт, честнее списка, который ведут двое.** Читателей документа не перечисляет ни одна сторона — читатель назначается планом прогона. Прежняя ссылка на «таблицу читателей» пережила саму таблицу и обещала то, чего нет, — с той самой правки, которая таблицу и убрала. ## 39. Спринт без цели — законный случай (2026-08-07) Цель была обязательной: `sprint start --goal` требовал слаг, `check` считал ошибкой набор без названной цели, `sprint take` отказывал задаче под чужой целью. Модель описывала только спринт развития — а спринт бывает под багфикс, под техдолг, под здоровье проекта. Такой набор собран **по работоспособности, а не по направлению**, и цели у него нет не по недосмотру. Обходной путь существовал и был хуже прямого: завести цель-пустышку («Здоровье проекта») и вешать под неё `fix`-и. Тогда `ROADMAP.md` — документ про то, что приложение умеет, — обрастает строками про то, что оно не ломается, а тег `goal:` перестаёт значить направление. **АЕААА. Цель у спринта необязательна, но её отсутствие — ответ, а не молчание.** `sprint start` принимает `--goal <слаг>` **или** `--no-goal`, и голое отсутствие обоих — отказ с объяснением. Причина в стимуле: цель называет человек, и это единственный продуктовый вопрос всей сессии. Разреши мы заводить спринт просто без флага — забытый флаг, лень спросить и осознанное решение стали бы неотличимы на выходе, а дешевле всего из трёх агенту именно не спрашивать. **АЕААБ. В спринте без цели цель не проверяется вовсе.** Набор берёт что угодно готовое к взятию, включая задачи под разными целями: сверять не с чем. Правило «набор служит одной цели» не ослаблено, оно просто не применяется — целей в таком наборе не больше одной, их ноль. Взамен машинной проверки остаётся показ набора человеку до заморозки: у бесцельного спринта это **единственная** проверка состава, и в скилле это сказано прямо. **АЕААВ. Признак «спринт идёт» — слаг, а не цель.** Прежде код спрашивал цель и получал заодно ответ про то, открыт ли спринт; теперь эти вопросы разошлись. Слаг подходит на роль признака лучше цели по существу: он есть у любого спринта, потому что без него нечем проставить `sprint:<слаг>`, то есть нечем собрать урожай. Поле «Цель» в шапке остаётся на месте и у бесцельного набора — пишется прозой без ссылки: **«цели нет» и «цель потерялась» обязаны различаться**. ### Что из этого следует 145. **Необязательное поле, которое всё же решают, заводится парой «значение или явный отказ».** Умолчанием тут был бы не выбор, а его отсутствие — и отличить его от забывчивости уже не смог бы никто, включая автора. 146. **Признак «сущность существует» нельзя вешать на её необязательное поле.** Пока цель была обязательной, `sprint_goal()` отвечал сразу на два вопроса, и это работало ровно до тех пор, пока второй ответ не понадобился отдельно. 147. **Снятая проверка называет, что осталось вместо неё.** Цель не проверяется — значит, за состав отвечают показ человеку и строка доклада; иначе послабление читается как «здесь можно не думать». ## 40. Три категории документов: не всякий документ — тема ревью (2026-08-07) Решение 36 объявило: **каждый документ проекта — тема ревью**. Правило дало открытый список тем и сделало `docs/` конфигурацией конвейера — это работает и остаётся. Но оно же оказалось неверным ровно наполовину, и потому вредным целиком. Паспорт и схему хранилища ревью читает, но темами они не являются: по ним нельзя сказать «в этом изменении сделано не так», они задают границу, по которой судит **чужая** тема. Журнал решений и журнал наблюдений ревью изменения не нужны вовсе: ADR объясняет прошлое решение, а не предъявляет требование к изменению. Ломалось это механически. Разметчик, применявший правило буквально, обязан был либо завести фантомные темы `passport`, `adr`, `database`, `research` и продублировать ими работу тем `architecture` и `operations`, либо потерять четыре документа молча. Обе ветки случались; в собственном образце плана разметчика `docs/passport.md` не попадал ни строкой, а его же обязательная арифметика покрытия («документов найдено N, все N разнесены») при этом не сходилась. **АЕАБА. Разрез один и проверяемый: можно ли по документу сказать «в этом изменении сделано не так».** Отсюда три категории. **Тема** — да, прямо (`conventions`, `security`, `architecture`, свои документы проекта). **Источник темы** — нет, но он задаёт границу для чужой темы (`passport`, `database`, `CLAUDE.md`, `openspec/specs/`). **Процессный документ** — нет, он про то, как мы работаем (`tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json`). **АЕАББ. Открыта одна категория из трёх.** `источник` и `процессный` перечислены поимённо и проектом не пополняются; открыта только `тема`. Прежняя формулировка «не темы ровно две» противоречила собственной раскладке канона — `.pm.json` был третьим, и правило-исправление жило в чужом плагине, в коде `docs.py`. Теперь документ, которого нет в раскладке, — однозначно своя тема проекта, и решать нечего. **АЕАБВ. «Не судит по нему» и «не открывает» — разные вещи.** `docs/review.*` проходы читают на каждом прогоне: там вопросы по темам, журнал дефектов, типовые узлы, типовые ложноположительные. Это чтение конвейером **своей обвязки**, а не критерия. `adr/`, `research/` и `tasks/` не открывает никто. **АЕАБГ. Цена решения записана, а не подразумевается.** Расхождение изменения с записанным решением прогоном больше не ловится — это работа сверки документации между спринтами. Измеренные числа проекта из ревью тоже ушли: проход, опирающийся на число, обязан **снять его сам, на этом прогоне**, и приложить команду замера. Обе потери идут обязательными строками в границы покрытия каждого прогона, и пишет их триаж — не проход, потому что проход о том, чего в конвейере нет, пожаловаться не может. ### Что из этого следует 148. **Плоское правило, верное наполовину, хуже двух правил.** Оно не даёт половине случаев легального ответа, и исполнитель выбирает между двумя плохими ветками — фантомной сущностью и молчащей потерей. Заметно это становится не на определении, а на первом же образце вывода. 149. **Открытым делается одно множество, а не все.** Открытый список ценен тем, что в него попадает незнакомое; если открыты все категории, незнакомое попадает в произвольную. 150. **Отказ читать документ — тоже граница покрытия, и её пишет сток.** Строку «этого не смотрел никто» некому подать снизу: проход, которого нет, отчёта не присылает. ## 41. Разметка задачи: одна величина, посчитанная один раз (2026-08-07) Разметка была стадией 0 **ревью кода** и платилась на каждом прогоне. Перед ревью дизайна ту же самую величину — «крупное или незнакомое?» — называл сам пайплайн задачи, то есть оркестратор, который только что довёл предложение до `propose`. Одно и то же измерялось дважды, и один из двух раз без разведённости с автором — ровно в той точке, ради которой разметчик и заведён. **АЕАВА. Разметка идёт один раз на задачу, сразу после `propose`.** Её план обслуживает обе стадии ревью: состав ревью дизайна и таблицу тем для ревью кода. Диффа она не видит — кода ещё нет; размер оценивается по дельта-спекам и перечню границ задачи. **АЕАВБ. Осей две, ступень — максимум по ним.** **Размер** (малое, среднее, крупное) — про объём; **сложность** (знакомое, незнакомое) — про то, известна ли форма решения заранее. Раньше обе были склеены в один вопрос «крупное **или** незнакомое?»: ответ получался тот же, но разметка не могла сказать «среднее, но совершенно знакомое» — а это и есть рабочее умолчание. **АЕАВВ. Ступень после кода не пересматривается.** Дифф может выйти крупнее ожидания — ступень не двинется. Пересмотр означал бы либо второй запуск разметчика (то, ради устранения чего он и переехал), либо машинный порог, который на нетипичной задаче срабатывает не туда. Расхождение факта с разметкой ловит журнал дефектов, постфактум, — так же, как и всякую другую ошибку выбора ступени. **АЕАВГ. План на диск не пишется.** Файл-план стал бы четвёртым артефактом рядом с `proposal.md`, `tasks.md` и `design.md`, пережил бы задачу и разошёлся бы с ней молча. Прервался пайплайн — разметка повторяется; это самый дешёвый его проход. ### Что из этого следует 151. **Величина, из которой выводится состав, считается один раз и одним агентом.** Два места, считающие одно и то же, расходятся; расходятся они молча, и побеждает то, у которого меньше разведённости с автором. 152. **Разведённость — свойство момента, а не роли.** Тот же агент, спрошенный до написания кода и после, даёт разные ответы; переезд по времени сделал больше, чем сделал бы любой запрет. ## 42. `quick` стал дешевле `standard` тремя способами (2026-08-07) `quick` и `standard` совпадали составом (шесть проходов) и различались глубиной трёх тем: сверка против разбора. На практике это означало один проход, задающий на один вопрос меньше, и потолок 4 вместо 2. Нижняя ступень не экономила почти ничего и называлась отдельной ступенью зря. Отдельно выяснилось, что дешевизна конвейера держалась на двух заявленных рычагах — узкий вход и потолок находок, — и **оба применялись к одному проходу из шести**. У `specs` и `code` потолка не было вовсе, а вход `code` включал чтение дома конвенций «весь и целиком» на каждой задаче. **АЕАГА. `quick` теряет приёмник тем.** Темы `security`, `operations` и `architecture` на этой ступени закрывает `code` сверкой с **записанными инвариантами** `CLAUDE.md`, потолком 1 находка на все три. Это не «глубина ниже» — это **другой дом темы**, куда более узкий, и в плане он так и называется. **АЕАГБ. Приёмник тем запускается тогда и только тогда, когда ему есть что принимать.** Правило было в `wide` («нет своих тем проекта — не запускается») и теперь распространено на `quick`. Совпадение неслучайное: темы ядра `basics` держит ровно на одной ступени из трёх, а приёмником проектных тем работает на всех. **АЕАГВ. Вход и потолок применены к каждому опиниативному проходу.** На `quick` `specs` читает только дельта-спеку, `code` — только индекс конвенций. Потолки напечатаны и раздельны по половинам `code`: 3 технических, 2 конвенционных, 1 по инвариантам. Раздельность обязательна — конвенционных находок больше по построению, и в общем списке они вытеснили бы техническую половину, чей пропуск дороже. **АЕАГГ. Сработавший потолок объявляется.** Проход, срезавший находки, говорит строкой, сколько осталось за срезом и какого рода. Молчащий срез неотличим от «больше не нашлось» — тот же класс молчащего пропуска, против которого написан весь конвейер. **АЕАГД. Отрицательный тест `quick` стал жёстче, а не мягче.** Вопросы «обратима ли миграция» и «что с записями новой версии после отката» задавал приёмник тем; на `quick` его нет. Значит изменение, которое не откатывается обратной правкой, на `quick` не идёт вовсе — каким бы малым оно ни было. ### Что из этого следует 153. **Ступень, не дающая экономии, не нужна.** Две ступени, различающиеся одним вопросом одного прохода, — это одна ступень с шумом в отчёте. 154. **Рычаг, применённый к одному исполнителю, — не рычаг, а исключение.** Заявленный механизм экономии проверяется перечислением: к кому он применён и к кому нет. 155. **Проход без потолка выдаёт столько находок, сколько нашёл поверхностей.** Ровно из-за этого был снят проход независимой реализации; тот же механизм работал у `code` и `specs` и не был замечен, потому что счёт никто не считал. ## 43. Ревью дизайна тоже растёт ступенями (2026-08-07) Состав ревью дизайна включался одним условием: `specs` всегда, `rubric` и `architecture` — вместе, «при крупном или незнакомом». Значит `standard` получал на предложении ровно один проход, то есть не отличался от `quick` ничем. **АЕАДА. Три ступени вместо двух: `quick` — `specs`; `standard` — плюс `rubric`; `wide` — плюс `architecture` и вопрос автору о трёх формах решения.** **АЕАДБ. Рубрика съехала вниз, архитектура осталась наверху, и это не симметричная правка.** Они зарабатывают на разном. Рубрика порождает **свойства узла** и окупается уже на среднем изменении: её выход уезжает приёмочными критериями в `tasks.md` и работает потом на всей задаче. Архитектура отвечает на вопрос «не появился ли второй способ», а он на среднем знакомом изменении отвечается «нет» ещё до запуска — держать её ниже `wide` значит платить за предсказуемый ответ на каждой задаче. **АЕАДВ. Тривиальность задачи больше не решает состав ревью.** Раньше она решала, звать ли ревью предложения вовсе; теперь глубину обеих стадий называет ступень, а тривиальная задача просто получает `quick`. «Пропустить ревью дизайна» и «пройти его одним самым дешёвым проходом» — разные вещи: сверка дельта-спек стоит меньше, чем разбор того, что она поймала бы на готовом коде. ### Что из этого следует 156. **Проходы, включаемые одним условием, стоит разводить по тому, на чём они зарабатывают.** Общее условие — признак того, что их не сравнивали между собой, а не того, что они равноценны. 157. **Средняя ступень обязана отличаться от нижней на обеих стадиях.** Иначе «рабочее умолчание» отличается от исключения только именем. ## 44. Метка задачи: одно значение, по которому выбираются все ревьюверы (2026-08-07) Решения 41–43 развели классификацию на две оси и свели состав обеих стадий ревью к их максимуму. Значения этого максимума назывались `quick`, `standard`, `wide`, а сам он — «ступень». Оба имени описывали **ревью**: как глубоко смотрим, на какой ступеньке идём. Классифицируется же при этом **задача**, и результат классификации принадлежит ей, а не прогону. Расхождение не косметическое. Пока величина называлась свойством ревью, её было естественно пересчитать на каждом прогоне — что конвейер и делал, пока разметка не переехала к `propose`. Имя тянуло назад к устройству, из которого её только что вынули. **АЕАЕА. Классификация выдаёт задаче метку: `small`, `medium` или `large`.** Метка принадлежит задаче, ставится один раз при разметке и дальше только читается. Все проходы обеих стадий получают её в задании и обязаны напечатать в границах покрытия. **АЕАЕБ. Метка — единственный вход выбора исполнителей.** Ни класс задачи, ни её тип, ни тривиальность, ни ощущение важности состав больше не определяют. У конвейера один переключатель, и он напечатан в каждом отчёте. **АЕАЕВ. Слово «ступень» удалено, а не оставлено синонимом.** Два имени одной вещи расходятся — это ровно решение #37 про тему и проход. Метка ordered: `small` < `medium` < `large`, и там, где нужен порядок, говорится «младшая» и «старшая метка», а не вводится второе существительное. **АЕАЕГ. Метка — не синоним размера, и это записано там, где ошибиться легче всего.** Совпадают они в одном углу таблицы из трёх: малое **незнакомое** изменение получает `large`, трогая один узел. Поэтому план печатает три строки — размер, сложность, метка, — каждую со своим обоснованием, и выводить одну из другой запрещено. Проход, определивший объём диффа по метке, ошибётся именно на том случае, ради которого верхняя метка и заведена. ### Что из этого следует 158. **Имя величины должно называть её носителя, а не потребителя.** «Ступень ревью» звала пересчитывать себя на каждом прогоне ревью; «метка задачи» считается там же, где живёт задача. 159. **Переключатель состава должен быть один и печатный.** Пока их два — тривиальность и ступень, — состав выводится из пересечения, а пересечение нигде не напечатано целиком. 160. **Русские слова для осей, английские для значения.** Оси — суждение и читаются прозой (`малое`, `знакомое`); метка — идентификатор, который проходы сравнивают, и потому она английская. Тот же разрез, что «имена файлов английские, текст русский» в каноне, и он же снимает путаницу «крупное» против `large`. ## 45. Корректор метки, доля `small` и корпус оценки (2026-08-07) Три правки по следам решений 41–44, и все три закрывают дыры, которые эти решения и открыли. **АЕАЖА. Сигнал о заниженной метке переехал в `review-code`.** Он жил в `review-basics` — единственном месте. А `basics` с меткой `small` не запускается, если у проекта нет своих тем: значит на типичном проекте задача с меткой `small` шла **без рантайм-проверки** того, что метка верна. Дыра появилась ровно вместе с удешевлением `small` и попала в самую вероятную точку ошибки: занижают туда, где дешевле, а цена занижения там же и выросла — три темы ядра смотрятся только против инвариантов. `code` подходит по построению: он идёт при **любой** метке, видит дифф целиком, а на `small` уже читает инварианты — то есть держит в руках весь материал, из которого сигнал выводится. У `basics` сигнал остаётся вторым, подтверждающим: он смотрит оптикой тем и видит то, чего не видно из кода как кода, — что вопросов, отложенных до `large`, накопилось слишком много. Триаж теперь обязан сказать и когда сигнала **нет**: «корректор отработал, возражений нет» и «корректор не запускался» по молчанию неразличимы. **АЕАЖБ. У `small` появилась доля, и она сформулирована сравнением, а не числом.** `small` не должен обгонять `medium`; ориентир — до трети задач. Проверка нужна именно теперь: пока `quick` и `standard` совпадали составом, дрейф между ними не стоил ничего, и её не было. Сейчас он стоит трёх тем ядра. У дрейфа вниз есть стимул, и он назван: метку выбирает не автор, но по описанию, написанному автором, — занижённое описание даёт занижённую метку без чьего-либо умысла. **АЕАЖВ. Размер оценивается по корпусу из пяти источников, а не по дельта-спекам.** Разметчик читал `proposal.md` и `tasks.md`, но `design.md` не открывал вовсе, а метод был описан одной фразой «размер считается по дельта-спекам». Дельты описывают заказанное **поведение** и молчат об объёме работы: шесть шагов в двух узлах видны в `tasks.md`, а факт, что форму решения выбирали из нескольких, — только в `design.md`. Каждый источник получил свою строку по каждой оси, и каждая цифра в обосновании обязана быть привязана к источнику поимённо. Отсюда два правила, которых раньше не было. **Расхождение источников по объёму разрешается в пользу большего** — и это не «спорное решается вниз»: то правило разрешает ничью при равных данных, а здесь один источник просто видел больше. **Само расхождение — довод за `незнакомое`:** если о задаче написано так, что источники не сходятся в объёме, форму решения по ней не знают. Отсутствие `design.md` у нетривиальной задачи читается так же — «форму знали заранее» ничем не подтверждено. ### Что из этого следует 161. **Корректор обязан идти чаще, чем корректируемое.** Проверяющий, который запускается реже проверяемого, оставляет дыру именно там, где выбор был самым дешёвым, — то есть там, где ошибаются. 162. **Отсутствие сигнала — тоже сигнал, и его надо печатать.** Молчание корректора неотличимо от его отсутствия, а решения по ним разные. 163. **Проверка доли формулируется сравнением, а не порогом.** «Меньше, чем `medium`» считается по любому журналу и не требует спорить о числе; порог «не больше 30%» спорен ровно настолько, насколько несопоставимы задачи. 164. **Оценка по одному источнику — оценка по остатку.** Источники о задаче отвечают на разные вопросы; пропущенный не ухудшает точность понемногу, а оставляет ось без данных. ## 46. Правило выбора метки съехало из скилла в отдельный документ (2026-08-07) **АЕАЗА. У правила выбора метки теперь свой дом — `references/review-levels.md`, а в скилле остался диспетчер.** `SKILL.md` конвейера дорос до 1168 строк, и двести с лишним из них отвечали на вопрос, который на обычной задаче не задаётся вовсе: **как** выбирается метка. Метку называет `review-scope` один раз, до обеих стадий; всем остальным нужна не она, а состав по уже названной метке — три строки таблицы. Переехали правило двух осей, «спорное решается вниз», «максимум по поверхности», разбор того, чем `small` дешевле `medium`, и обе проверки долей. Остались таблица состава, схема процесса и раздача тем. **Форма выбрана одна на все метки, а не по документу на метку.** Предлагался разрез по образцу типов задач в `av-dev-pm:tasks`, где у `fix`, `feature` и `chore` по своему файлу. Аналогия не переносится, и по двум причинам. Типы задач **разъединены** — общее вынесено в `task-format.md`, а в файле типа лежит только своё; метки же **вложены**: `medium` это `small` плюс два прохода, `large` — `medium` плюс доказательство. Три файла повторяли бы костяк трижды, а `copies.py` такое не ловит: он сверяет дословные копии по маркерам, тогда как здесь вышли бы почти-копии с намеренными мелкими отличиями — расхождение, неотличимое от задуманного. Вторая причина сильнее первой: ценность этого текста **в сравнении**. Читателю нужно не «что делает `small`», а «чем `small` отличается от `medium`» — на этот вопрос отвечают и выбор метки, и «спорное вниз», и корректор. Сравнение, разложенное по трём файлам, не читается. **Механика рычагов осталась в скилле, а не уехала с меткой.** Непуск, вход и потолок общие для всех проходов и всех меток, их дом — раздел «Модель по проходу». В переехавшем тексте от них только то, что они делают с `small`, и ссылка на дом; точные потолки не продублированы. ### Что из этого следует 165. **Дом правила — там, где правило выбирают, а не там, где его применяют.** Применяют состав на каждой задаче, выбирают метку один раз; текст, обслуживающий выбор, в потоке применения лежит мёртвым грузом. 166. **Вложенные вещи не режутся по файлу на вещь.** Разъединённое (типы задач) режется, вложенное (метки) — нет: разрез вложенного даёт дублирование общей части, а дублирование намеренно неточное машина не сверит. ## 47. OpenSpec заводится скиллом, а его конфиг — часть канона (2026-08-07) **АЕАИА. `init` заводит OpenSpec сам, а не оставляет это человеку.** Каталог `openspec/` был предпосылкой, о которой канон говорил, но за которой не следил: `openspec/specs/` объявлен домом темы `requirements`, `config.yaml` описан абзацем — а заводилось всё руками, и не проверялось ничего. Новый проект выходил из `init` с полным каноном документов и без каталога, без которого не работают ни `opsx:propose`, ни ревью дизайна, ни сверка требований. Команда названа поимённо (`openspec init --tools claude`) в трёх местах — скилле, каноне и отказе скрипта: отказ без команды заставляет искать её в другом месте. **АЕАИБ. Файл из коробки хуже отсутствующего, и потому проверяется машиной.** `openspec init` кладёт `config.yaml`, где `context` и `rules` — закомментированный пример на английском. Такой файл читается как настроенный: он есть, он валиден, имя правильное. Работает он как пустой, и узнаётся это по уже написанному предложению — на другом языке, с capability по имени пакета, без единого `SHALL`. `docs.py` проверяет четыре вещи, и каждая про молчащий пробел: каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об этом не сообщает); `context` и `rules.specs` не остались примером, а правила называют `SHALL`; `context` называет `passport` и `CLAUDE.md`. **АЕАИВ. Форма конфига — маршрутизатор, и это разрез, а не пожелание.** Утверждение, которое можно опровергнуть, открыв другой файл проекта, — пересказ; строка, которая говорит, какой файл открыть, — ссылка. `context` читается при порождении **каждого** артефакта, туда удобно дописать «чтобы агент знал», и именно поэтому в нём заводятся вторые дома инвариантов, конвенций, гейта и правил ревью. Машина этот разрез не проверяет — отличить ссылку от пересказа она не умеет, — и он отдан `doc-consistency` отдельным абзацем правила «один факт — один дом», с `config.yaml`, добавленным ему во вход. **Обязательными сделаны ровно два адреса — паспорт и `CLAUDE.md`.** Причина в порядке работы: предложение пишется **до** того, как кто-либо откроет `docs/`, и без этих двух строк его пишут, не зная ни границы домена, ни инвариантов. Длинный список адресов превратил бы `context` во второй дом ровно тем способом, против которого правило и заведено. **Образец конфига лёг в канон, а не в конвейер**, как планировалось решением C. Форма документа принадлежит тому, кто владеет каноном документов; конвейер её читатель. Иначе `av-dev-pipeline` завёл бы у себя описание файла, который заводит и проверяет `av-dev-pm`, — тот же шов, что разбирали, убирая имена проходов из канона. ### Что из этого следует 167. **Предпосылка, за которой никто не следит, — не предпосылка, а пожелание.** Если условие названо обязательным, его должен кто-то заводить и кто-то проверять; иначе оно живёт ровно до первого проекта, где о нём забыли. 168. **Заполненная форма и заполненный смысл — разные вещи, и первая маскирует вторую.** Файл на месте, валиден, с правильным именем — и пуст по существу: это худший вид пробела, потому что выглядит он как его отсутствие. ## 48. Форма чужого инструмента держится опросом инструмента, а не памятью (2026-08-07) **АЕАИГ. Схема и артефакты OpenSpec записаны в скрипте как слепок, и у слепка есть сторож.** Проверка формы `config.yaml` знает имя схемы и перечень артефактов (`proposal`, `specs`, `design`, `tasks`). Это не наше решение, а состояние чужого инструмента: OpenSpec переименует артефакт — правила под прежним именем перестанут применяться, конфиг останется выглядеть написанным, а канон продолжит требовать прежнее. Все три стороны при этом молчат. Сторожем сделано сравнение версий: `check` спрашивает `openspec --version` (десятые доли секунды) и сравнивает `major.minor` с той, на которой форма сверялась. Разошлось — **замечание**, не отказ, с именем команды, которая перепроверяет. Патч-версия в сравнение не берётся намеренно: формы она не меняет, а нагоняй на каждый багфикс приучает пролистывать весь блок. **АЕАИД. Перепроверка спрашивает инструмент, а не нас.** `docs.py openspec-form` берёт `openspec templates --json` — перечень артефактов текущей схемы — и печатает, что разошлось с константами. Дорогой вызов вынесен из `check` сознательно: он стоит втрое дороже опроса версии, а ответ меняется только вместе с версией. Дешёвая проверка служит **воротами** дорогой, и дорогая не ржавеет, потому что зовут её не по памяти. **Чинится расхождение в плагине, а не в проекте, и это сказано в трёх местах.** Проект в такой ситуации не виноват и починить ничего не может: у него нет ни констант скрипта, ни скелета, ни журнала версий канона. Замечание поэтому адресовано владельцу плагина, а команда печатает три адреса правки списком. **Заодно поймано ложное срабатывание на живом конфиге.** Первый вариант искал ключи `rules` отступом по всему файлу и нашёл их внутри литерального блока `context: |`: строки «Language: Russian» и «av-dev-pm:review-pipeline» выглядят ключами. Проверка теперь идёт от строки `rules:` до следующего ключа нулевой колонки. Правило, краснеющее на правде, хуже отсутствующего — его перестают читать целиком. ### Что из этого следует 169. **Знание о чужом инструменте, записанное у себя, — это слепок с датой.** Он законен, пока рядом стоит тот, кто заметит, что дата протухла; без сторожа он превращается в уверенное враньё. 170. **Дешёвая проверка как ворота дорогой.** Опрос версии стоит копейки и точно говорит, могла ли измениться дорогая величина. Так дорогая проверка остаётся редкой и при этом не забытой.