журнал решений: разложен по теме на файл, метки решений стали номерами

- DECISIONS.md (4040 строк, 65 тем) → decisions/, файл на тему плюс указатель;
- буквенные метки решений заменены сквозными Р1–Р234, следствия получили
  префикс С при прежних номерах: схема букв выродилась до пятибуквенных и
  сломалась — `АЕАКЛ` была занята и темой 53, и темой 65;
- 42 перекрёстные ссылки переписаны под новые номера и стали живыми; где номер
  означал тему, а слово стояло «решение», формулировка исправлена.
This commit is contained in:
av
2026-08-13 12:40:56 +03:00
parent b411d4edb8
commit bf6a173115
72 changed files with 4253 additions and 4053 deletions
-4040
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -6,7 +6,7 @@
был написан. Остаётся то, чего в плагинах нет и быть не должно: **что отвергнуто
и почему, и числа первого замера**.
Решения текущего круга разбора — [DECISIONS.md](DECISIONS.md).
Решения текущего круга разбора — [журнал решений](decisions/README.md).
## Что отвергнуто и почему
+4 -4
View File
@@ -3,7 +3,7 @@
Личный маркетплейс плагинов для разработки. Процесс имени не имеет — он и есть
`av-dev`.
Что решено и почему — [DECISIONS.md](DECISIONS.md). Что осталось сделать —
Что решено и почему — [журнал решений](decisions/README.md). Что осталось сделать —
[TODO.md](TODO.md) и [REMAINING.md](REMAINING.md). Как процесс дошёл до текущей
формы — [HISTORY.md](HISTORY.md).
@@ -12,8 +12,8 @@
Плагина два: **av-dev** — весь процесс, и **av-dev-git** — сообщения коммитов.
Второй работает в любом репозитории, в том числе не приведённом к канону. До 13 августа 2026 процесс жил тремя плагинами
(`av-dev-docs`, `av-dev-tasks`, `av-dev-code`); раскол делался под раздельную
установку, она не понадобилась ни разу, и плагины слились — тема 64
[DECISIONS.md](DECISIONS.md).
установку, она не понадобилась ни разу, и плагины слились —
[тема 64](decisions/64-three-plugins-merged.md) журнала решений.
Имя **скилла** несёт префикс прежнего плагина: `doc-`, `task-`, `code-`. Вызов
выходит вида `/av-dev:<скилл>`.
@@ -371,7 +371,7 @@ python3 scripts/frontmatter.py # 0 в порядке, 1 расхождени
простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт,
сверка…» — это не текст с двоеточием, а синтаксическая ошибка. Так было
написано часть описаний плагинов, и читались они правильно — замер и разбор
в [DECISIONS.md](DECISIONS.md), решение III;
в [решении Р61](decisions/17-notes-tier-work-kind-roadmap.md) журнала;
- **`name`, разошедшееся с именем каталога скилла или файла charter'а.** Вызов
разрешается по каталогу, а сообщение о промахе говорит «нет такого скилла»,
а не «имя не то»;
+2 -2
View File
@@ -2,9 +2,9 @@
Состояние пересобирается по ходу работы; счётчика тем и коммитов здесь нет
намеренно — он протухает молча, а двигать его некому. Что и когда решено —
[DECISIONS.md](DECISIONS.md), записи датированы.
[журнал решений](decisions/README.md), записи датированы.
План работ — [TODO.md](TODO.md). Решения с причинами — [DECISIONS.md](DECISIONS.md).
План работ — [TODO.md](TODO.md). Решения с причинами — [decisions/](decisions/README.md).
Здесь то, что **не** является работой из плана: незакрытые риски, честно принятые
пределы и вопросы, у которых пока нет ответа.
+6 -4
View File
@@ -3,14 +3,15 @@
**Здесь только работы и их порядок.** Чего здесь нет намеренно:
- **риски, открытые вопросы и принятые пределы** — [REMAINING.md](REMAINING.md);
- **почему решено так** — [DECISIONS.md](DECISIONS.md), записи датированы;
- **почему решено так** — [журнал решений](decisions/README.md), записи
датированы;
- **шаги повышения проекта с версии канона на версию** — журнал версий
([changelog.md](av-dev/skills/doc-canon/references/changelog.md)). Пересказ их
сюда был бы вторым домом, и прежний план на этом уже разъезжался: он повторял
записи версий 3, 4 и 5 построчно, и половина повторов протухла молча.
Сделанное отсюда **удаляется, а не помечается галочкой**. След остаётся в
коммитах и в `DECISIONS.md`; список из двух сотен `[x]` перестают читать целиком,
коммитах и в журнале решений; список из двух сотен `[x]` перестают читать целиком,
и живые пункты в нём теряются — прежний план умер именно так.
## Где мы сейчас
@@ -18,7 +19,8 @@
Плагина два: `av-dev` — весь процесс девятью скиллами (`doc-*` — документы,
`task-*` — учёт работ, `code-*` — работа по задачам), и `av-dev-git`
сообщения коммитов. Прежние три (`av-dev-docs`, `av-dev-tasks`, `av-dev-code`)
слились 13 августа 2026, тема 64 DECISIONS. Общее, что нужно нескольким скиллам,
слились 13 августа 2026,
[тема 64](decisions/64-three-plugins-merged.md) журнала решений. Общее, что нужно нескольким скиллам,
живёт домом в `av-dev/shared/`.
Раскладка — **версия 1**, одна на документы и на каталог задач, в
@@ -27,7 +29,7 @@
14, потом по записи 1 действующего.
Бумажная часть закрыта аудитом четырёх плагинов и четырьмя пропусками правок
(DECISIONS, запись 59). Всё, что ниже, проверяется **только на живом коде**.
([тема 59](decisions/59-four-subagent-audit.md) журнала решений). Всё, что ниже, проверяется **только на живом коде**.
## 1. Живые проекты — вернуть в рабочее состояние
+86
View File
@@ -0,0 +1,86 @@
# 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.**
## Решено
**Р1. OpenSpec — жёсткая предпосылка `av-dev-pipeline`.** Ветки деградации
удаляются, вместо них объявленная зависимость и проверка на старте. Зависимость
на уровне **плагина, а не процесса**: `av-dev-tasks`, `av-dev-git` и будущий
плагин документов от OpenSpec не зависят и работают на python/ansible-проектах.
*Причина:* непроверенная ветка деградации хуже честной строки «требуется
OpenSpec» — она даёт ложную уверенность, что проект без спек поедет.
**Р2. Нормативный дом поведения — `openspec/specs/`.** `architecture.md`
переопределяется как **обзор**: принципы, компоненты со ссылками на capability,
внешние форматы данных, раскладка, деплой, открытые вопросы. Поведения он не
описывает.
*Причина:* `opsx:archive` вливает дельты именно в `openspec/specs/` — любой
другой нормативный дом обязан синхронизироваться руками и разойдётся. Форма
jellybit это уже подтвердила на 43 изменениях.
**Р3. `openspec/config.yaml` → `context` держит только нужды генерации.** Язык,
правила именования capability, придирки валидатора RFC 2119 — и ссылки. Правило
ревью, пересказ конвенций и инварианты оттуда вычищаются: у них есть свои дома.
*Причина:* блок «Ревью (процесс, не артефакт)» в обоих `config.yaml` дословно
повторяет шаги 4 и 7 `task-pipeline`. Это второй дом для правила, которым владеет
плагин, и он разойдётся на первой же правке.
## Что из этого следует
Из Р1:
**С1.** Три места с веткой деградации переписываются на объявленную предпосылку
плюс проверку на старте (есть `openspec/`, разрешаются `opsx:*`) и внятный
отказ: `task-pipeline` предпосылки, `review-pipeline` предпосылки, `task-batch`
предпосылки.
**С2.** Описание `av-dev-pipeline` в маркетплейсе получает строку «требует
OpenSpec».
**С3. Факт для темы «объединять ли tasks и pipeline»:** объединение потянуло бы
зависимость от OpenSpec на управление задачами, которой там сейчас нет.
Из Р2:
**С4.** Правило «поведение — в спеку, устройство и границы — в архитектуру»
становится контрактом плагина документов и правилом шага «синк документации» в
`task-pipeline`.
**С5.** healthlog чистится **не разом**: раздел вычищается той задачей, которая
его касается. Нужен способ не потерять остаток — иначе 950 строк «Хранилища»
останутся навсегда.
**С6. Дыра, которую решение открывает:** «почему» после архивации. Сегодня
`CLAUDE.md` healthlog велит писать причину решения в `architecture.md`а мы её
оттуда выселяем. Спеки нормативны и «почему» не держат; `design.md` живёт внутри
change и уезжает в архив. Либо ADR (как у jellybit), либо явное правило «почему
живёт в архивных change». **Первый вопрос следующей темы.**
Из Р3:
**С7.** `av-dev-pipeline` даёт образец `openspec/config.yaml` отдельным
reference — он владеет связью с OpenSpec. Заполняется при старте проекта и при
`adopt`.
**С8.** У обоих проектов из `config.yaml` вычищается блок «Ревью (процесс, не
артефакт)», пересказ конвенций и инвариантов.
+106
View File
@@ -0,0 +1,106 @@
# 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 шагов)
и `<tasks>/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.
## Решено
**Р4. «Почему» — ADR как промоут поверх архива.** Обоснование по-прежнему пишет
`design.md`; ADR — короткая запись, цитирующая решение и ссылающаяся на архивный
`design.md`. Заводит её **шаг «синк документации» пайплайна по названному
триггеру** (дорогой откат / намеренный отказ от очевидного / пересмотр прежнего
решения), а не человек по вдохновению.
*Причина:* ручной ритуал эмпирически не выжил — 6 записей на 52 изменения.
Автоматический (`opsx:propose` пишет `design.md` всегда) работает и производит на
порядок больше. Чинить надо не дом, а индекс и критерий промоута.
**Р5. `docs/plan.md` растворяется в `<tasks>/PLAN.md`.** Файл удаляется, 11
шагов становятся целями в «порядке», ссылки в `CLAUDE.md` и паспорте
переводятся.
**Р6. Пути жёсткие, оба проекта приводятся к одному виду.** Плагин знает
раскладку поимённо; указателя вида `.docs.json` нет.
*Причина (словами владельца):* «так проще ориентироваться во множестве проектов,
а не видеть слегка похожую, но разную структуру в каждом. Все проекты малого и
среднего размера, проще подогнать их под одну структуру. Кроме того, у OpenSpec
тоже структура строгая». Цена принята сознательно: плагин перестаёт быть
переносимым на чужой репозиторий, а `adopt` из «поправь указатели» превращается в
«перенеси файлы».
**Р7. Конвенции и разведка — каталогами с README-индексом.** `docs/conventions/`
и `docs/research/`: путь жёсткий, нарезка внутри свободна. Схема хранилища —
**отдельный** `docs/database.md` (своя каденция: меняется миграцией, а не
архитектурным решением; гейт healthlog уже сверяет миграции с документацией).
Конвенции идентификаторов и именования — не схема, они в `conventions/`.
**Р8. Слота для черновиков нет.** Идея → задача `[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/<capability>/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](06-docs-upkeep.md) (поддержание):** точная
формулировка триггера промоута в ADR; нужен ли механический `check` раскладки
документов, раз пути жёсткие; как не потерять остаток при постепенной чистке
`architecture.md`.
+115
View File
@@ -0,0 +1,115 @@
# 3. Брифа ревью нет — бриф это и есть канон (2026-08-03)
## Что было
Контракт брифа — 413 строк, 13 разделов, отдельный файл `docs/review-brief.md`,
который каждый проход читает как истину. Заполнение на обоих проектах дало 841 и
734 строки, и `REMAINING.md` уже отметил, что часть разделов вырождается в
пересказ.
Разбор по разделам после решения [Р6](02-project-doc-canon.md) (жёсткие пути)
показал: **посредник между агентом и файлом не нужен, когда путь известен**.
Восемь из тринадцати разделов дублируют канон или снимаются жёсткими путями.
## Решено
**Р9. Отдельного файла-брифа нет.** Проектную конкретику проходам дают документы
канона напрямую, по жёстким путям. Формулировка владельца: «артефакты в `docs` и
должны стать частями брифа, а для ревью достаточно дать ссылки на эти
артефакты».
*Причина:* один факт — один дом. Бриф был вторым домом для паспорта, инвариантов
и карты, а разошедшийся бриф хуже отсутствующего: он выглядит актуальным.
**Р10. Заводится `docs/security.md`.** Периметр **первой строкой** (целевой и
сегодняшний, если контур не развёрнут), недоверенный вход и его каналы, из чего
строятся пути и ключи, что разграничивает доступ, что чувствительнее чего, что
вне модели. Материал уже есть, но рассыпан: у healthlog — раздел
«Аутентификация» в `architecture.md` и строка про секреты в `CLAUDE.md`, у
jellybit — секреты в `conventions/config.md`. **Периметра нет ни у одного**, а
без него враждебный проход не выбирает между «открыт наружу» и «контур
доверенный».
**Р11. `review-journal.md` → `docs/review.md`:** журнал дефектов плюс настройка
конвейера под проект. Туда садится остаток брифа, который фактом о проекте не
является — типовые узлы, типовые ложноположительные, вопросы к проходам,
недоступно проверке.
*Причина:* все четыре — производные калибровки, и журнал им источник. `##
Вопросы к проходам` сам называет журнал главным источником; `### Перестали
проверять сознательно` требует ссылки на его запись.
**Р12. Журнал расширяется до всех воспроизведённых дефектов** с пометкой
«проскочил / пойман ревью». Проверочный набор для калибровки — выборка по
пометке.
*Причина:* пойманные дефекты с оракулом (768 МиБ пика, 5.019 с удержания
блокировки) сегодня не сохраняются нигде, кроме отчётов триажа в архиве change, а
они и есть лучшая опора для прохода — проектные, воспроизводимые, однажды
оказавшиеся правдой.
**Р13. Семантика гейта — в `CLAUDE.md`, расширением раздела «Команды».** Чем
краснеет безусловно и почему, где логи, что означает исход, чего в гейте
намеренно нет, **кто и когда обязан гонять дорогое вне гейта**, что запускать
запрещено (с путями). Гейт краснеет не только в ревью — это факт о проекте.
**Р14. 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/<capability>/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](05-project-start-lifecycle.md),
требование [Т1](README.md)).
**С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`.
+76
View File
@@ -0,0 +1,76 @@
# 4. Границы плагинов (2026-08-03)
## Что было
Связь `tasks``pipeline` уже сделана **ролями, а не именами**: скиллы говорят
«пайплайн проекта», «владелец спринта», «тот, кто ведёт задачи». Жёсткая ссылка
по имени ровно одна — `task-pipeline:112` на канонический текст правила про
остаток внутри `session`, и рядом обработан случай «плагин не подключён».
Слоты `CLAUDE.md` при этом дублировались уже внутри одного плагина: шесть у
`tasks`, семь у `session`, три пары — одно и то же. Темы 2–3 растворили ещё
часть: «куда переезжает суть» отвечает канон, «оракулы» — семантика гейта
(решение [Р13](03-review-brief-is-canon.md)), «где живёт разбор процесса» —
`docs/review.md` (решение [Р11](03-review-brief-is-canon.md)). Из тринадцати
остаётся около четырёх.
## Решено
**Р15. Три плагина: `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`.
**Р16. Граница «пайплайн не закрывает задачу» снимается.** Закрывает задачу и
двигает строки между `SPRINT.md` / `BACKLOG.md` / `REJECTED.md` **агент-
оркестратор** — `task-pipeline` и `task-batch`, а не сабагенты внутри них. Зовёт
он `tasks.py` через слот «Команда учёта задач» в `CLAUDE.md`.
Слот, следовательно, **не исчезает, а становится мостом между плагинами** — и
заодно тем, чего в чужом проекте нет, отчего пайплайн там работает как прежде:
докладывает исход, записей учёта не трогает.
**Р17. `av-dev-backlog` помечается устаревшим и остаётся** до перевода jellybit.
*(заменено на [тему 30](30-av-dev-backlog-removed.md): плагин удалён раньше
этого срока — условие пережило свою причину.)* Описание переписывается так,
чтобы не ловить триггер «добавь задачу в беклог» — иначе агент выбирает между
ним и `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/<id>/review/` (независимый артефакт, `task-batch` уже сверяет
полноту ревью по нему, а не по прозе исполнителя), **`SPRINT.md` под git** с
видимой историей и **`reopen <slug> --reason`** — закрытие не окончательно,
приёмка человеком на сессии его отменяет. Раздел обязан назвать их поимённо,
иначе обещает защиту, которой нет.
**С20. Конфликт владения `docs/tasks/` снят** — канон и задачи теперь в одном
плагине.
**С21. Скилл `adopt` из `av-dev-tasks` поглощается** скиллом адаптации проекта
уровня канона (требование [Т1](README.md)). Разбирается в [теме
5](05-project-start-lifecycle.md).
**С22. Состав `av-dev-pm`:** `tasks`, `session` (есть), `docs` — ведение канона,
`project` — старт, adopt, check, upgrade ([тема
5](05-project-start-lifecycle.md)).
+89
View File
@@ -0,0 +1,89 @@
# 5. Старт проекта и жизненный цикл под каноном (2026-08-03)
## Что было
Требование [Т1](README.md): прийти в любой старый проект и перевести на текущие
рельсы; канон сам меняется, значит уже приведённые проекты тоже повышаются.
Существующий `adopt` (уровень задач) даёт готовую форму: **`scan` — только
чтение, карта → суждение человека → `apply` — запись одним проходом**, с отказом
до первой записи при неверной карте и с обязательным разделом «не разложилось»
поимённо. Форма переносится на уровень канона как есть.
Четыре операции различаются не поровну: `adopt`, `check` и `upgrade` — одна
машина сравнения с разными исходами, а `init` — принципиально другой режим,
разговор, а не сверка.
## Решено
**Р18. Два скилла: `av-dev-pm:init` и `av-dev-pm:canon`.** `init` — интервью по
входному брифу для нового проекта. `canon` — привести к канону: `check`,
`adopt`, `upgrade` одной машиной.
**Р19. Скелет канона заводится целиком, незаполненное называется пустым.** Все
файлы канона есть с первого дня, но незаполненный держит **одну честную
информативную строку**: «наблюдений на живых данных нет — внешний источник один,
формат документирован», «прецедентов не накоплено», «внешних зависимостей нет,
смотри на диск и на СУБД».
*Причина:* это тот же принцип, что был в контракте брифа, поднятый на уровень
файлов. Проход читает такую строку **как факт**, а не как пробел, и не тратит
обязательный вопрос впустую. Отсутствие файла он прочитать не может никак.
**Защита от вырождения в заглушки** берётся у `tasks.py`: пока на месте стоит
плейсхолдер шаблона, `check` о нём напоминает. Строка «TBD» — это не «пустое
названо пустым», и `check` обязан их различать.
**Р20. Скрипт `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>/.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](06-docs-upkeep.md):** звать ли `docs.py check` из
гейта проекта. У healthlog `task gate` уже сверяет миграции с документацией, так
что место есть; но гейт принадлежит проекту, и плагин может только рекомендовать
строкой в отчёте.
+68
View File
@@ -0,0 +1,68 @@
# 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`.
## Решено
**Р21. Принуждённое отрицание в докладе шага синка.** Шаг обязан назвать
**каждый** документ канона: обновлён — чем, либо «не требуется, потому что…».
Нетронутые группируются одной строкой с общей причиной.
*Причина:* это тот же приём, что «границы покрытия» в отчёте ревью и «пустой
пункт называется пустым» в каноне — и единственный, о котором в этом репозитории
есть данные, что он работает. Отличить «не написал» от «написал, что не
требуется» можно только тогда, когда отрицание обязательно.
**Триггер ADR, окончательная формулировка.** Запись заводится, когда верно одно
из трёх: **дорогой откат** (переделка стоит дороже переписывания одного файла);
**намеренный отказ** от очевидного подхода; **пересмотр прежнего решения** — тогда
у старой записи обязателен статус «заменено на». Не заводится для рутины и для
того, что видно из кода и `git log`. Источник — архивный `design.md`: ADR его
цитирует и на него ссылается, а не пересказывает.
**Р22. `docs.py check` обязателен в гейте проекта.** Скилл `canon` при адаптации
добавляет шаг и печатает это в отчёте.
*Причина:* гейт — единственный общий станок, который нельзя пропустить. Проверка,
которую зовёт агент, может быть не позвана; прецедент в самом healthlog уже есть.
Сверка «миграция изменена — `database.md` нет» **обобщается**: путь миграций
проекта записывается в `docs/.pm.json` рядом с версией канона, и `docs.py` делает
эту проверку сам, а не каждый проект заново.
**Р23. Остаток чистки помечается маркером и считается числом.** Неразобранный
раздел получает `<!-- канон: поведение → openspec/specs/<capability> -->`,
`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`.
+83
View File
@@ -0,0 +1,83 @@
# 7. Раскладка скиллов и доставка скриптов (2026-08-03)
## Решено
**Р24. Пять скиллов в `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`.
**Р25. Скрипты не копируются — живут вместе со скиллами.** Три вызывающих, три
способа дотянуться:
| Кто зовёт | Как |
| --- | --- |
| скиллы `tasks`, `canon`, `docs` | `$CLAUDE_PLUGIN_ROOT` — свой плагин, работает всегда |
| `task-pipeline`, `task-batch` | **вызов скилла** `av-dev-pm:tasks`, а не путь |
| гейт проекта | путь переменной с умолчанием на канонический путь маркетплейса; пишет `canon adopt`, внятный красный отказ, если не найден |
**Слот «Команда учёта задач» всё равно исчезает** — но снимает его не копия, а
**вызов скилла через пространство имён**. Тот же приём, которым шаг синка зовёт
`av-dev-pm:docs` (решение Р24): чужой плагин зовёт скилл, скилл разрешает свой
`$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` в нём
отсутствовали вовсе. Довод «вендоринг создаёт вторую копию» был слабее, чем
подан.
- **Обновление маркетплейса — одна точка на все проекты.** При вендоринге каждый
проект повышается отдельно, и проекты расходятся друг с другом — ровно та
разнородность, против которой принято решение [Р6](02-project-doc-canon.md).
**Р26. Имени у процесса нет — процесс это `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](08-rollout-order.md)), иначе проверять будет нечего.
+70
View File
@@ -0,0 +1,70 @@
# 8. Порядок выката (2026-08-03)
## Объём
Ссылок на бриф — **168 строк в 19 файлах** `av-dev-pipeline`, из них ~48 уходят
вместе с удаляемыми `project-brief/SKILL.md`, `references/project-brief.md` и
`references/brief-template.md`. Остальное переписывается на пути канона.
## Решено
**Р27. Инструмент строится целиком, потом проверяется.** Не пилот руками.
*Риск принят сознательно:* если замер покажет деградацию severity, чинить
придётся канон, зашитый к тому моменту в три скилла, скрипт и мигрированные файлы
healthlog.
*Удешевление, которое обязано быть заложено сразу:* **определение канона живёт в
единственном reference-файле**, который читают `init`, `canon` и `docs`, а не
повторяется в каждом. Правка канона — одно место плюс запись в журнал версий.
*Страховка порядка:* **замер ставится перед переездом jellybit**, а не после
всего, — он всё ещё блокирует то, что дороже всего откатывать.
**Р28. Работа ведётся в `docs/tasks/` самого `dev-skills`.** Скилл `tasks` не
требует ни OpenSpec, ни языка — задачи для него просто каталог markdown. Цели —
крупные куски, задачи — следствия. Заодно первая боевая обкатка собственного
инструмента.
**Р29. `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 жёстко.
+67
View File
@@ -0,0 +1,67 @@
# 9. Линтеры скриптов (2026-08-03)
## Что было
Три скрипта на python, 3600 строк, ни одной проверки. `tasks.py` — 2450 строк,
которые ходят по файловой системе, переименовывают и удаляют файлы задач.
Требование к самим скриптам прежнее и не обсуждается: **голый `python3` 3.12,
ноль внешних зависимостей** — они лежат рядом со скиллами и запускаются в
чужом проекте, где ничего ставить нельзя.
## Решено
**Р30. `pyproject.toml` в корне `dev-skills`, зависимости через `uv`.** Файл
живёт только здесь и не уезжает никуда: он держит **линтеры**, а не зависимости
скриптов. Скрипты остаются запускаемыми любым `python3` — это проверено прогоном
всех операций через `/usr/bin/python3`, а не через `.venv`.
**Р31. Ноль зависимостей охраняется двумя способами, и главный — второй.**
`banned-api` у ruff ловит частые соблазны по имени (`requests`, `yaml`,
`pydantic`, `click`, `rich`) — список заведомо неполный. Настоящий страж —
pyrefly: в окружении нет ничего, кроме линтеров, поэтому **любой** сторонний
импорт у него не разрешается. Первый способ даёт понятное сообщение, второй —
полноту.
**Р32. Версии линтеров прибиты точно** (`ruff==0.16.1`, `pyrefly==1.2.0`) плюс
`uv.lock` в git. Обновление линтера меняет набор находок, а находки правятся
руками в скриптах, которые уезжают в чужие проекты. Обновление обязано быть
отдельной осознанной правкой, а не побочным эффектом `uv sync`.
**Р33. `RUF001``RUF003` выключены.** Весь текст скриптов русский: сообщения,
докстроки, комментарии. «Похожая на латиницу кириллица» здесь норма, а не
опечатка, и три этих правила давали **311 срабатываний из 338** — шум, в котором
тонут остальные 27.
**Р34. `av-dev-backlog` исключён из проверки.** *(исчерпано [темой
30](30-av-dev-backlog-removed.md): плагин удалён, исключение снято из
`pyproject.toml` и `copies.py`.)* Плагин помечен устаревшим и живёт до перевода
последнего проекта, после чего удаляется целиком. Шесть его находок
косметические (`os.replace`, `l` как имя), а правка замороженного кода без
тестов — риск без выгоды. Исключение уходит вместе с плагином.
**Р35. Голый `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. Заводить хук ради двух скриптов, которые
правятся раз в месяц, — плата ритуалом без выгоды.
+51
View File
@@ -0,0 +1,51 @@
# 10. Ревью готовых плагинов двумя проходами (2026-08-03)
## Что было
Два независимых сабагента `fable` — по одному на `av-dev-pm` и `av-dev-pipeline`.
**20 находок, из них две найдены обоими независимо.** Прошлые три круга ревью
шли по одному проходу на всё; два прохода с разными предметами дали и больший
урожай, и перекрёстное подтверждение самого дорогого дефекта.
## Что оказалось сломано по существу
**Р36. Перестановка закрытия за коммит (решение из [темы
8](08-rollout-order.md)) сломала `reopen` и батч — и это нашли оба прохода.**
`close --implemented` печатает «дорога назад: файл восстанавливается из git», а
`reopen` искал **коммит удаления**, которого в новом порядке ещё нет: шаг 11
идёт последним, и учёт остаётся незакоммиченным. Проверено прогоном: `reopen`
отказывал кодом 2 на свежезакрытой задаче — то есть в самом вероятном своём
применении. Тем же грязным деревом ломался `task-batch`: `git rebase` и `git
worktree remove` отказывают, и **каждая успешно закрывшая задачу ветка** уезжала
бы в провалившиеся.
Починено с обеих сторон: `reopen` берёт текст из `HEAD`, если коммита удаления
нет, а шаг 11 обязан **коммитить учёт вторым коммитом** — иначе закрытие не
доезжает до основной ветки и опора «`SPRINT.md` под git» остаётся словами.
**Р37. Канонический пример `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. Два прохода по разным предметам дороже одного, но не вдвое.** Перекрытие
оказалось ровно в одной находке из двадцати — той самой, что подтвердилась
дважды. Практика остаётся: ревью на плагин, а не одно на репозиторий.
+52
View File
@@ -0,0 +1,52 @@
# 11. Зависимости между плагинами (2026-08-03)
## Целевая картина, которую проверяли
`av-dev-git` ни от чего не зависит. `av-dev-pipeline` сам по себе: задача
приходит **и обычным текстом**, и из `tasks`. `av-dev-pm` оперирует абстрактным
«сделать задачу» и не знает, чем она выполняется.
## Что показала проверка
**Р38. Первые две цели выполняются, третья в исходной формулировке недостижима —
и формулировку надо поправить, а не картину.** `av-dev-pm` **владеет
конфигурационным файлом конвейера**: `docs/review.md` держит «Вопросы к
проходам» и «Триггеры профиля», то есть перечисляет проходы поимённо, а скелет
`review.md` несёт форму журнала дефектов. Кто-то этим словарём владеть обязан —
канон и есть схема данных, которую конвейер читает. Честная формулировка цели:
**`av-dev-pm` не зовёт пайплайн и не требует его наличия**. Она выполняется.
**Р39. Настоящая протечка была одна — необъявленная деградация опор приёмки.**
«Стимулы» в `session` и приёмка в `sprint.md` держались на «сохранённом отчёте
триажа» по конкретному OpenSpec-пути. В проекте без конвейера ревью защита от
занижения урожая исчезала **молча**: сверять не с чем, а текст об этом не
говорил. Теперь опора названа абстрактно («независимый отчёт ревью»), путь
`av-dev-pipeline` дан как частный случай, а отсутствие конвейера обязано
попадать строкой в доклад спринта.
**Р40. Ветка деградации шага 9 была неисполнима — ровно в том случае, ради
которого написана.** «Плагина нет — открой
`av-dev-pm/skills/canon/references/canon.md`»: путь в дерево маркетплейса, из
проекта без установленного плагина не разрешается ниоткуда. Кросс-плагинные пути
в дерево маркетплейса теперь не используются вообще: пайплайн ходит в **свой**
`references/project-facts.md`, а ссылки в чужой плагин даются через `Skill
<плагин>:<скилл>`.
## Что из этого следует
**С47. Знаниевый цикл есть и он законен, но каждый его контракт обязан иметь
единственный дом.** Пайплайн описывает раскладку `pm`, `pm` описывает артефакты
пайплайна — пять симметричных контрактов, из них два уже разошлись: форма
журнала дефектов (шесть полей против пяти, «Причина» потеряна) и список
читателей `docs/research/` (`specs` выпал). Дома назначены: форма журнала — у
конвейера, список читателей — у канона; в обеих копиях стоит явное указание на
дом.
**С48. Пайплайн больше не называет внутренние имена файлов `pm`.**
`items/<slug>.md` и `SPRINT.md` в его тексте были вторым домом для раскладки,
которую проект вправе переименовать через `docs/.pm.json`.
**С49. Описания плагинов в манифестах врали умолчанием.** Ни `marketplace.json`,
ни `plugin.json` не говорили, что `av-dev-pm` для конвейера **опционален**, а
задача принимается текстом. Теперь говорят — это первое, что читает человек,
выбирая, что подключать.
+53
View File
@@ -0,0 +1,53 @@
# 12. Механическая проверка копий (2026-08-03)
## Что было
Разделение плагинов оставлено ([тема 11](11-plugin-dependencies.md)), но цена
его названа: пять симметричных контрактов в двух домах, два уже разошлись —
форма журнала дефектов потеряла в копии поле «Причина», список читателей
`docs/research/` потерял `specs`. Оба раза копия выглядела актуальной, и оба
раза расхождение прошло мимо трёх ревью подряд.
## Решено
**Р41. Копия допустима, но обязана быть дословной и помеченной.** Разметка —
HTML-комментарии, невидимые в отрендеренном markdown: `<!-- дом: <id> -->`
`<!-- /дом: <id> -->` и `<!-- копия: <id> из <путь> -->` … `<!-- /копия: <id>
-->`. `scripts/copies.py` требует побайтового совпадения текста между маркерами.
*Почему комментарии, а не манифест копий отдельным файлом:* маркер уезжает в
репозиторий проекта вместе со скелетом, и там он **полезен** — говорит читателю,
что у текста есть дом и правится он там. Манифест остался бы в маркетплейсе и
проекту ничего не сказал.
**Р42. Идентификатор строгий — буквы, цифры, дефис — и повторяется в закрывающем
маркере.** Иначе документация о самом механизме объявляет дом и роняет проверку:
это случилось на первом же прогоне, `README.md` объявил дом примером. Теперь
пример пишется `<id>`, угловые скобки под шаблон не подходят.
**Р43. Ограда блока кода в сверку не входит.** В доме текст обрамлён своей ```,
а в скелете тот же текст лежит внутри чужой, объемлющей ограды. Сверяется
содержимое, а не разметка вокруг него.
**Р44. Коды выхода — общий словарь** (0 сошлось, 1 расхождение, 2 разметка, 3 не
тот каталог, 4 сбой). Третий скрипт репозитория, и третий по тем же кодам.
## Что из этого следует
**С50. Помечены два контракта:** форма записи журнала дефектов (дом — конвейер
ревью, копия — скелет канона; это кросс-плагинная пара) и «когда заводить ADR»
(дом — канон, копия — его же скелет). Второй пришлось сперва **сделать**
дословным: копия говорила «обязателен статус», дом — «обязателен статус
„заменено на"», и это ровно тот класс, который и ищется.
**С51. Чего проверка не ловит — копию, которую забыли пометить.** Помечать
остаётся решением человека, и это названо в `README.md` вслух: иначе зелёный
прогон читался бы как «копий больше нет».
**С52. Дом без копий — расхождение, а не замечание.** Маркер, обещающий
дисциплину, за которой не за чем следить, — такая же ложная запись, как
разошедшаяся копия.
**С53. Запись в журнал версий канона проверка не заменяет.** Она видит, что
копия отстала, но не видит, что проект уже унёс старую версию к себе. Это
остаётся на человеке и сказано в обоих домах.
+31
View File
@@ -0,0 +1,31 @@
# 13. Секции `PLAN.md` переименованы (2026-08-03)
## Что было
Секции назывались **«линия»** и **«кусты»** — метафора, требующая расшифровки
при каждом употреблении. В текстах она и расшифровывалась: «звено упорядоченной
линии продукта», «тематический куст — цель, в последовательность не встающая».
Если название приходится объяснять рядом с каждым употреблением, объясняет не
название.
## Решено
**Р45. «порядок» и «темы».** Заголовок называет ровно то свойство, которым
секции различаются: в первой очередь значима и обоснована прозой, во второй
порядка нет вовсе. Расшифровывать нечего — правило написано в самом имени.
**Р46. Записи в журнал версий канона не требуется — канон этих имён не знает.**
`canon.md` называет файл `docs/tasks/PLAN.md` и ничего не говорит о его секциях:
их дом — заголовки `##` индекса, а умолчание живёт в `tasks.py`. Версия канона
поэтому не меняется, и проект вправе называть секции по-своему. Причина названа
вслух, потому что соблазн повысить версию «на всякий случай» здесь сильный, а
повышение обязало бы каждый проект что-то делать — при том что делать нечего.
## Что из этого следует
**С54. Умолчание одно и живёт в `DEFAULT_PLAN_SECTIONS`.** Имена секций
по-прежнему настраиваются `--plan-sections`, а домом остаются заголовки `##`
индекса — переименование не трогает механику, только умолчание и тексты.
**С55. Метафора — плохое имя для секции индекса.** Секция читается человеком без
контекста, часто из вывода `list`, и второго шанса объяснить себя у неё нет.
+54
View File
@@ -0,0 +1,54 @@
# 14. Умолчания режимов прогона перевёрнуты (2026-08-03)
## Что было
Оба скилла держали одно и то же умолчание — «по очереди», — хотя цена очереди у
них разная. `review-pipeline` гнал проходы последовательно и требовал для
параллельности **двух** условий (явная просьба **и** поимённо названный набор).
`task-batch`, наоборот, планировал волны параллельных задач с потолком 2–3 и
считал параллельность нормой прогона.
Перепутаны оказались уровни. Проход ревью — чтение и рассуждение: он ничего не
поднимает, ни за что не дерётся и по построению не видит выводов соседа. Задача
батча — полный цикл пайплайна: гейт, поднятие сервиса вживую, вложенное ревью,
общие порты и рабочие каталоги. Дешёвое стояло в очереди, дорогое гонялось разом.
## Решено
**Р47. В ревью умолчание — параллельно.** Стадии по-прежнему идут по порядку,
параллельность касается только проходов внутри стадии. Последовательно гоняем по
трём особым причинам, и каждая называется в отчёте: сказал оператор; проходы
меряют; машина занята — причём занятость видит вызывающий, а не конвейер.
Просьба «гони последовательно» **набора не требует**: очередь ничего не портит,
она только дольше, и домысливать тут нечего — в отличие от прежнего правила, где
неназванный набор блокировал отступление.
**Р48. Меряющая пара — правило стадии, а не решение прогона.** `adversary` и
`ops` идут по очереди всегда: оба доказывают находки числами и оба меряют одно
железо, а испорченный оракул хуже отсутствующего. Общее «гони параллельно» этого
не отменяет; отменяет только прямое слово оператора **про эту пару**, и тогда в
границы покрытия идёт строка про замеры под соседней нагрузкой.
**Р49. В батче умолчание — по одной задаче, параллельность — по графу
зависимостей.** План собирается как граф (рёбра — жёсткие зависимости и
сериализуемые пересечения) и в умолчании линеаризуется в один порядок. Просьба
«гони параллельно» разрешает использовать **ширину графа**, а не гнать всё
разом: потолок 2–3, замеряющая задача — волной по одной. Прежние правила волн
сохранены целиком, они просто перестали быть умолчанием.
## Что из этого следует
**С56. Режим батча задаёт режим ревью внутри задачи, и его называет charter.**
Батч идёт по одной — машина свободна, сабагент гонит проходы параллельно; батч
идёт волнами — сабагенту предписан последовательный режим с этой самой причиной.
Сабагент своего соседа не видит, поэтому решать это ему нельзя.
**С57. Ранний выход из ревью переехал на границу стадии.** Стадии идут по
порядку в любом режиме, так что остановиться между ними можно всегда; остановка
**внутри** стадии осталась побочной выгодой последовательного режима — но не
поводом его выбирать.
**С58. Цена параллельного батча проверяется до первой волны.** Тесты, делящие
фиксированный порт или файл БД, и проект, умеющий поднимать один экземпляр, —
основание гнать по одной даже после просьбы, сказанное строкой: просьба была про
параллельность, а не про сломанные тесты.
+90
View File
@@ -0,0 +1,90 @@
# 15. Порядок проходов ревью — граф зависимостей (2026-08-03)
## Что было
Решение 14 перевернуло умолчание, но оставило порядок в прежней форме: «стадии
идут по порядку номеров, параллельность — только внутри стадии». Номер стадии при
этом ничего не означает: между стадиями 1–4 ни один проход не читает вывод
другого, так что очередь между ними была платой ни за что. А правило про замеры
держалось на **двух именах**`adversary` и `ops`, — и рассыпалось бы в тот
день, когда мерить начнёт третий проход или проект добавит свой.
## Решено
**Р50. Порядок задаёт граф; стадии остаются единицей состава.** Профиль
по-прежнему набирается стадиями, но запускается всё, у чего закрыты входящие
рёбра. Рёбер три вида, и смешивать их нельзя: **зависимость** (гейт → все
проходы с мнением, все проходы → триаж), **конфликт за ресурс** (ненаправленный,
между теми, кто держит машину), **барьер стоимости** (только `deep`).
**Р51. Сериализует ресурс, а не имена.** Пометка «держит машину» — таблицей в
скилле: `gate`, `adversary`, `ops`, `triage`; читают и рассуждают — `specs`,
`code`, `reimpl`, `architecture`, `rubric`. Проект вправе пометить свой проход в
`docs/review.md`; снимать пометку с перечисленных нельзя. Правило теперь
самораспространяется: начнёт проход мерить — попадёт в цепочку по факту, а не по
поправке.
**Р52. Ранний выход заменён барьером стоимости.** Он стоит там, где ранний выход
зарабатывал: перед `reimpl` (пишет реализацию целиком) и `architecture`. В
`quick`/`standard` барьера нет — стадий 3–4 там не бывает; в `design` нет по
другой причине — предметом там и является форма, защищать нечего.
**Р53. Ребро — это порядок, никогда не данные.** В обычном графе задач ребро
тянет за собой вывод предшественника; здесь это запрещено: проход, увидевший
чужие находки, соглашается с ними, и разведённость — вся ценность конвейера —
обнуляется. Сказано в самом правиле, потому что графовый словарь провоцирует
ровно эту ошибку. Исключение одно и оно же сток: триаж.
**Р54. Диаграммы в скиллах — `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.
+73
View File
@@ -0,0 +1,73 @@
# 16. Каталог вместо файла в `docs/` — отложено до переезда healthlog (2026-08-04)
## Что было
Вопрос: разрешить документам в корне `docs/` быть не только файлом, но и
каталогом — когда документ описывает несколько принципиальных решений или
перерастает 400–500 строк. Паспорт остаётся файлом в любом случае: компактность
и есть его функция.
Механизм в каноне уже работает — `conventions/`, `research/`, `adr/` каталоги с
обязательным `README.md`-индексом, — так что вопрос не «можно ли», а «от чего
лечим».
## Решено
**Р55. Порог в строках триггером не становится.** Замер по проектам: у порога
ровно один документ — `healthlog/docs/architecture.md`, 1662 строки. В нём
десять маркеров долга, а разделы — «Слои гранулярности» (211 строк), «Тренировки
и прочие секции» (222), «Условный запрос», «Свёртка и размер ответа», «Форма
ответа». Это **поведение**, чей нормативный дом `openspec/specs/`, где у проекта
уже лежат пять capability. Остальные документы 108–438 строк, `jellybit` — 169.
Порог сработал бы ровно там, где надо не разносить, а доводить переезд, и дал бы
долгу постоянное жильё: разложить 1662 строки по файлам дешевле, чем вынести их
в спеки, а после раскладки давление исчезнет и второй дом поведения останется
навсегда.
**Р56. Шов выноса — другой читатель или другой срок жизни, а не размер.** По
этому критерию кандидатов два. `review.md` — сильнее прочих: у него уже записаны
два раздела с разными сроками жизни, настройка конвейера стабильна и читается
проходами, а журнал дефектов растёт неограниченно. `architecture.md` — по шву
«окружение, деплой, наблюдатель», у которого отдельный читатель `ops`. А вот
расщепление архитектуры **по принципиальным решениям отвергнуто**: у факта
«почему решено так» дом `adr/`, и вынесенные разделы немедленно станут его
вторым домом.
**Р57. `security.md` и `passport.md` каталогом не становятся.** У `security.md`
ценность именно в цельности: периметр первой строкой и «что вне модели» читаются
враждебным проходом за один раз, а разнесённые — расходятся первыми. У
`database.md` механизм заводить не под что: 241 и 211 строк.
**Р58. Если вводить — точка входа остаётся одна.** `docs/architecture.md`
упомянут в репозитории 66 раз: девять charter'ов, карта `project-facts.md`,
`docs.py`, скелеты. Развилка «файл или каталог» размножится на девять «прочитай
либо обойди». Поэтому форма жёсткая: каталог легален только при
`<имя>/README.md`, и он **и есть** прежний документ — обзор целиком со ссылками
на вынесенное, а не оглавление к нему. Каждый файл каталога обязан быть достижим
ссылкой из `README.md`; это проверяется сегодняшним механизмом ссылок `docs.py`
и ловит файл-сироту. Вынеся раздел, `README.md` на него **ссылается, а не
пересказывает** — тот же приём, которым в архитектуре уже описаны компоненты со
ссылкой на capability.
**Р59. Решение отложено до конца переезда `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 "<!--
канон:"`) и вопрос, не поведение ли это. Разложить дрейф по файлам значит
перестать его видеть.
**С67. Материал для решения даёт `healthlog`, а не `jellybit`.** У второго 169
строк архитектуры — там вопрос не стоит вовсе, и принимать по нему решение
значит принимать его без предмета.
@@ -0,0 +1,100 @@
# 17. Разбор заметок: ступень ревью, род работы, роадмап (2026-08-04)
## Что было
Семь заметок из `NOTES.md`, накопленных по ходу работы: переименование
`PLAN.md`, тип у каждой задачи, цвета сабагентов по модели, кавычки во
фронтматтерах, уровни ревью для проекта, задачи в терминах функций и границ,
язык задач без англицизмов. Разного размера и из разных мест, но три из них
оказались об одном — **о том, можно ли оценить задачу, не открывая код**.
## Решено
**Р60. Цвет charter'а кодирует модель, а не роль прохода.** Раскладка `sonnet`
green, `opus` → yellow, `fable` → red. Роль прохода видна из имени, а стоимость
прогона — ниоткуда; цвет, розданный по ролям, не отвечает ни на один вопрос,
который задают во время прогона. Дом раскладки — таблица «Модель по проходу» в
`review-pipeline/SKILL.md`.
**Р61. Фронтматтеры проверяются машиной, а не вниманием.** Три описания из
четырнадцати содержали `: ` в незакавыченном значении — для YAML это вложенное
отображение, то есть синтаксическая ошибка, которую **нельзя увидеть чтением**:
текст читается правильно. Тот же класс, что у mermaid-диаграмм, и лечится тем же
способом — `scripts/frontmatter.py`. Он же держит раскладку цветов (HHH) и
сверку `name` с именем каталога.
**Р62. Между `standard` и `deep` заведена ступень `wide`.** *(содержание
триггеров пересмотрено темой 18, [Р72](18-tier-raises-pass-not-risk.md):
миграция схемы и публичный контракт ступень не поднимают.)* Прыжок стоил самого
дорогого прохода конвейера, а платить приходилось за одну архитектурную находку:
изменений, которые трогают публичный контракт, но не вводят нового правила
слияния, — большинство. `wide` — это `standard` плюс `architecture` (вход шире
диффа, отсюда имя), семь проходов против восьми у `deep`.
**Р63. Триггер независимой реализации стал триггером профиля.** Раньше условие
«изменение вводит новое правило идентичности, слияния или разбора» стояло
**внутри** `deep`, и профиль означал то семь проходов, то восемь. Реестр
состава, который «сверяется взглядом до коммита», проверять было нечем: у
профиля не было одного правильного ответа. Теперь условие выбирает профиль, а
`reimpl` в `deep` безусловен — и он единственное, чем `deep` отличается от
`wide`.
**Р64. Барьер стоимости остался только в `deep`.** В `wide` за ним стоял бы один
дешёвый проход с потолком в 3 находки, а барьер не бесплатен — он сериализует
то, что могло идти разом. Вторая причина помельче: барьер спрашивает «выживает
ли форма изменения», а `architecture` — как раз тот, кто на этот вопрос
отвечает.
**Р65. Род работы — вторая ось типа, и живёт тегом.** Тип записи
(`goal`/`idea`/`epic`/`task`) отвечает «что это за запись», род
(`feature`/`fix`/`chore`/`research`) — «какого рода работа». В один префикс их
не свести: идея бывает *про* функцию, эпик функцией *и является*. Дом — тег
`kind:<род>`, потому что теги здесь и есть единственный механизм разметки, а
`list --kind` работает даром. Принятая цена: в строку индекса род не попадает
(индексы производны), и состав набора по роду виден командой, а не глазами.
Словарь **закрыт** — открытый разъехался бы на синонимах `bug`/`bugfix`/`fix`.
**Р66. У `chore` тест готовности ослаблен честно.** Вопрос «что станет
наблюдаемо иначе» для обслуживания отвечается разработчику, а не пользователю.
Пока рода не было, такие задачи либо не заводились, либо придумывали себе
пользовательскую пользу — и это второе хуже: оно проходит проверку.
**Р67. Задача называет границы, а не намерения.** Раздел «Затрагивает» —
эндпоинт, таблица и миграция, формат на диске, публичный тип пакета. Без него
задача оценивается по объёму текста, а не по объёму поверхности, и оценка
систематически занижена ровно там, где текст короткий, а границ много. Механизм
проверяет **наличие** непустого раздела: полноту перечня машина не видит, и
делать вид, что видит, хуже, чем не проверять.
**Р68. Род и границы требуются к взятию в спринт, а не к заведению.** Тот же
приём, что уже работает для критериев приёмки, и по той же причине: беклог
пополняется чаще, чем разбирается, а требование на входе выгоняет в заметки то,
что должно лежать задачей. `check` о пропаже напоминает замечанием — иначе два
живых проекта покраснели бы на 98 задачах, заведённых до этого решения.
**Р69. `PLAN.md``ROADMAP.md`, вместе с ключом конфига и токенами команд.**
Слово «план» в репозитории значит три разных вещи — оглавление целей, план
реализации внутри задачи и `PLAN.json` разовой адаптации. Переименовано всё:
`tasks.plan``tasks.roadmap`, `--index plan``--index roadmap`,
`--plan-sections``--roadmap-sections`. Старый ключ в `docs/.pm.json` не
игнорируется молча — скрипт останавливается и называет переименование.
## Что из этого следует
**С68. Версия канона 3 занята этим изменением.** Отложенное решение [темы
16](16-directory-instead-of-file.md) (каталог вместо файла в `docs/`) вводится
теперь версией **4**, а не 3.
**С69. Род работы ничего не предписывает конвейеру.** Профиль ревью выбирается
по факту изменения: `chore` бывает миграцией схемы, `fix` — правкой публичного
контракта. Правило «предписание процесса в теле задачи снимается» родом не
отменяется, а подтверждается.
**С70. Проверка фронтматтеров — третья проверка репозитория того же класса.**
Копии, диаграммы, фронтматтеры: всё это ошибки, невидимые при чтении. Класс
опознаётся по признаку «диff выглядит разумно, а результат ломается», и каждый
его представитель получает скрипт, а не пункт чек-листа.
**С71. Ступеней профиля четыре, и правило выбора читается сверху вниз.** Первое
сработавшее условие и есть ответ: правило слияния → `deep`, контракт или схема →
`wide`, видимое снаружи поведение → `standard`, иначе `quick`.
+107
View File
@@ -0,0 +1,107 @@
# 18. Ступень поднимает проход, а не риск (2026-08-04)
## Что было
Наблюдение с живых проектов: полный набор проходов гоняется чаще, чем оправдано —
архитектура и независимая реализация нужны заметно реже, чем запускаются. Развилка
названа сразу: крупные задачи с частым полным ревью либо мелкие и средние задачи
со средним ревью. Выбран второй путь.
Разбор показал, что размер задач — только половина причины, и не главная.
## Решено
**Р70. Профиль — максимум по поверхности, а не средневзвешенное.** Условия
читаются сверху вниз, первое подошедшее отвечает за весь дифф. Значит цена ревью
растёт быстрее размера задачи: на крупной задаче верхний профиль оплачивается в
том числе за ту её часть, которая сама по себе была бы `quick`. Это и есть
механизм, ради которого выбран путь мелких задач.
**Р71. Ступень поднимает то, что даёт работу новому проходу, а не то, что
кажется рискованным.** Правило вывода, по которому спорные случаи решаются без
нового списка. Проверка нынешних триггеров этим правилом:
| Триггер | Кто закрывает | Где этот проход |
| --- | --- | --- |
| миграция схемы | `gate` (шаг миграций), `ops` (миграция под потоком, откат при двух версиях) | уже в `standard` |
| публичный контракт | `specs`, направление `code → spec` | во всех профилях |
| инвариант проекта | основание для `critical` у любого прохода | во всех |
| новый пакет, новое понятие | `architecture` | только `wide` |
| новое правило слияния | `reimpl` | только `deep` |
Три верхних триггера не добавляли ни одного прохода — они поднимали ступень «на
всякий случай». На проекте с базой и эндпоинтами это делало верхнюю ступень
умолчанием, то есть правило объявляло исключением то, что происходит всегда.
**Р72. Миграция схемы, публичный контракт и инвариант уехали в `standard`.**
`wide` теперь означает ровно одно: изменение вводит **новое понятие или
структурную единицу** — новый пакет или слой, новая точка входа, второй способ
делать то, что уже делается, перенос ответственности между узлами. Добавленное
поле в существующем ответе концептом не является. Это **отменяет часть JJJ темы
17**: ступень `wide` остаётся, её содержание меняется. Проект, где изменение
контракта и правда архитектурное (публичный SDK, чужие потребители), поднимает
его сам в `docs/review.md` — уточнением, а не возвратом прежнего умолчания.
**Р73. Чекпоинт `design` получил то же условие.** `review-specs` в режиме
«дизайн ДО кода» идёт всегда — это самый дешёвый чекпоинт конвейера.
`review-rubric` и `review-architecture` — только при новом понятии. Причина
арифметическая: чекпоинт стоит на **каждой** задаче, поэтому при мелкой нарезке
три прохода умножаются на число задач и становятся самой большой статьёй.
Причина по существу та же, что в SSS: рубрика на узел без нового понятия
порождает свойства уже существующего рода, записанные конвенциями и спеками.
**Р74. Шов нарезки — граница, за которой падает ступень.** Тест декомпозиции
отвечает, **допустим** ли разрез; шов отвечает, **где** его провести. Раздел
«Затрагивает» перечисляет границы; строка, поднимающая ступень выше остальных, и
есть кандидат на отдельную задачу.
**Р75. Костяк из четырёх проходов платится за каждую задачу.** Гейт, спеки, код,
триаж несокращаемы, поэтому разрез, после которого обе половины остаются в одной
ступени, делает ревью **дороже**: тот же объём тем же составом, но костяк
оплачен дважды. Резать — когда разрез снимает дорогой проход с большей части
диффа.
**Р76. Верхняя ступень задана тестом, а не списком.** «Идентичность, слияние,
разбор» — формулировка, пришедшая из одного проекта, и в общем виде она не
читалась: вопрос «как это применить к моему проекту» не имел ответа в тексте.
Теперь класс задан тремя условиями, независимыми от домена и языка: вариантов
несколько и оба защитимы; спека между ними не выбирает; неверный выбор не
падает, а молча меняет смысл данных. Отрицательный тест сильнее положительных —
то, что красит гейт или роняет запрос, в класс не входит. Три слова остались как
**три места**, где такие правила водятся (граница входа данных и место их
встречи), а проект перечисляет свои места в `docs/review.md` — перечень
производен от теста и не расширяет класс.
Оговорка, без которой правило вырождается: триггер — **новое или изменённое по
существу правило**, а не код рядом с ним. Проект, чей домен и состоит из таких
правил, иначе оказывался бы в `deep` всегда — та же болезнь, от которой лечилась
ступень `wide`.
## Что из этого следует
**С72. Порога в числе границ не заводится.** Тот же принцип, что в теме 16
([Р55](16-directory-instead-of-file.md)): размер не триггер. Шов проходит по
скачку ступени, а не по длине перечня.
**С73. Ступень — признак для планирования, но не запись в задаче.** Строка
«делать профилем standard» в теле — тот самый второй дом правила выбора, который
снимает гигиена полей. Профиль выбирает тот, кто видит изменение.
**С74. Дешёвое место заметить разнородную задачу — показ набора спринта.** Там
«Затрагивает» уже написан, а предложение об изменении ещё не заведено: разрез
стоит одного `edit` вместо выброшенного предложения.
**С75. Замер остаётся за обкаткой.** Правило выведено из состава проходов, а не
из статистики прогонов: считать, какая доля задач попадает в каждую ступень,
можно только на спринтах нового процесса (TODO шаг 4).
**С76. Отсутствие верхней ступени — законное состояние проекта.** Бывают
проекты, где данные приходят нормализованными, ничего ни с чем не сливается, а
внешних форматов нет: `deep` там не срабатывает никогда, и придумывать ему повод
не надо. Раньше это читалось как недонастройка.
**С77. Ступень определяет класс правила, а не вид работы.** Миграция схемы —
`standard`, но миграция, переносящая данные по правилу («сложить дубли»,
«привести к одному виду перед сравнением»), несёт правило идентичности и потому
`deep`. Одно слово в описании задачи попадает в разные ступени — это не
противоречие, смотрят не на слово.
+101
View File
@@ -0,0 +1,101 @@
# 19. Роадмап — состояние проекта, а не очередь работ (2026-08-04)
## Что было
Основной инструмент владельца — роадмап и набор целей: «на каком этапе проект,
что сделали и что осталось». Оценка идёт по **поведению**, а не по внутреннему
устройству: что приложение уже может делать и чего ещё не может. Отсюда
требование к формулировкам: цель отвечает на «что приложение будет делать»,
задача — на «что для этого нужно сделать».
Разбор показал, что инструмент отвечал ровно на половину этого вопроса.
## Решено
**Р77. Достигнутая цель из роадмапа не исчезает.** `close --implemented` удалял
у цели и файл, и строку — роадмап по построению показывал только «что осталось».
Свидетельство нашлось в самом роадмапе healthlog: там руками заведена секция
«Что уже пройдено» на двадцать строк прозы, и заканчивается она фразой «Эти
звенья целями не заведены: закрытая цель записи не оставляет, ей хватает коммита
и спеки». Обходной путь и его причина записаны рукой владельца. Теперь строка с
датой переезжает в секцию достигнутого; файл удаляется по-прежнему.
Вторым домом поведения это не делает: нормативное поведение живёт в
`openspec/specs/`, роадмап отвечает **когда и в каком порядке** оно появилось —
другой вопрос. Ссылки на файл в строке нет намеренно: файла больше нет, а битая
ссылка это законная ошибка `check`. Форма строки — как в `REJECTED.md`, и по той
же причине.
**Р78. Цель — возможность приложения, задача — шаг к ней.** Заголовок цели
отвечает на «что приложение будет уметь»: не «Работа со слиянием», а «Исход
слияния не зависит от порядка доставки». **Свойство поведения — тоже
возможность**: «сообщает о своём состоянии», «исход не зависит от порядка» —
законные цели, переформулировки в функцию не требуют. Единственный настоящий
чужак — работа над инструментом и процессом: на вопрос «что приложение будет
уметь» она не отвечает и живёт в отдельной секции роадмапа.
**Р79. Тест готовности задачи сменил защиту.** Требование «что станет наблюдаемо
иначе снаружи» переехало к цели. У задачи вместо него — **какую строку
«Завершения» своей цели она двигает**. «Отрефакторить X» проваливает тест не
потому, что невидим снаружи, а потому, что не находит строки, к которой
относится. Побочная выгода: видно и обратное — строка «Завершения», к которой не
относится ни одна задача, это незакрытая часть возможности. Отсюда требование к
«Завершению» быть **списком**, а не абзацем: на абзац не сошлёшься.
**Р80. Цель обязательна не у всякой задачи.** Прежнее правило — «у каждой задачи
должен быть `goal:`, иначе она не попадёт ни в один спринт» — было угрозой, а не
аргументом, и заставляло операционную работу выдумывать себе направление.
Граница проходит по роду работы: `feature` без цели не бывает (новая возможность
и есть содержание цели), `fix`, `chore` и `research` живут без цели законно и
входят в набор спринта помимо его цели. Это второй раз, когда род работы
окупается, — и первый, когда он что-то определяет за пределами отбора.
**Р81. Тип `[epic]` упразднён.** Зонтик между целью и задачами не нужен:
зонтиком стала цель, а слишком крупный шаг дробится на шаги помельче под ней.
Замер: ноль употреблений на 97 записей двух живых проектов, при том что тип
занимал место в словаре, тесте готовности, автомате переходов, `split.md` и трёх
местах `tasks.py`.
**Р82. Имена секций роадмапа — `Готово` / `Запланировано` / `Направления` /
`Разработка`.** Первый набор (`умеет` / `строим` / `станок`) прожил один заход и
был признан неудачным. Из четырёх предложенных имён отвергнуто одно, и по
проверяемой причине: **`окружение` уже занято** — в `architecture.md` это боевое
окружение приложения, «где работает, что рядом, кто перезапускает», и одно слово
в двух смыслах развело бы документы канона. Взято `Разработка`.
Принятый компромисс назван вслух: `Готово` слегка тянет обратно в трекерную рамку
«состояние работы», тогда как секция про **возможность**. Перевесила читаемость с
первого взгляда, а смысл несут заголовки целей внутри секции. Так же принято, что
цель в `Запланировано` может быть уже наполовину построена: это очередь, а не
«не начато», а «в работе» живёт в `SPRINT.md`.
**Р83. Секции роадмапа канонические, секции беклога — нет.** Разница выведена, а
не назначена: у секций роадмапа есть **семантика** (достигнутое, очередь,
долгое, не про продукт), в первую пишет сам `close`, и роадмап, названный
по-своему, читался бы только своим автором. Секции беклога (`Ядро`, `Инфра`)
семантики не несут — это полки. Поэтому `check` проверяет у роадмапа три вещи:
состав закреплён (чужая секция — ошибка), все четыре обязаны быть, язык один на
весь индекс; `--roadmap-sections` у `init` упразднён. Английский набор — `Done`
| `Planned` | `Directions` | `Tooling`.
Проверено на том самом случае, ради которого правило и заводилось: секция «Что
уже пройдено», которую healthlog вёл руками, теперь называется ошибкой поимённо.
## Что из этого следует
**С78. Ключа `tasks.achieved_section` не появилось.** Секция достигнутого
опознаётся по каноническому имени в любом из двух языков, и лишний knob не
заводится: канонический состав отвечает на тот же вопрос надёжнее конфига.
**С79. `reopen` цели снимает строку достигнутого.** Иначе роадмап продолжает
утверждать, что приложение умеет то, что вернулось в работу.
**С80. Прозаический раздел в индексе — дрейф.** Любой `##` проверка считает
секцией, поэтому «Что уже пройдено» и «Почему в таком порядке» в healthlog
формально были двумя лишними секциями, куда могла уехать задача. При повышении
они разбираются: звенья — строками в `Готово`, обоснование очереди — прозой
внутри `Запланировано`.
**С81. Правил стало пять, и нулевое — про смысл, а не про механику.** «Цель —
возможность, задача — шаг к ней» стоит перед правилами о гниении беклога и
производности индексов, потому что из него следует, зачем эти механики нужны.
@@ -0,0 +1,86 @@
# 20. Форма записи: заголовок, секции, вычитка (2026-08-04)
## Что было
Обкатка обновлённого скилла на выдуманном проекте — консольные крестики-нолики
на JavaScript. Каталог задач заведён с нуля тем же скриптом: шесть целей, девять
задач, отказ, достижение цели, спринт. Смотрели три вещи: тексты, разделы, состав
задач.
Форма вылезла раньше содержания. Индексы вышли с секциями со строчной буквы и без
отбивки после заголовка — читается как список списков, а не как документ. А все
заголовки задач оказались **описательными**: «Лишние символы в ходе молча
отбрасываются», «Поле печатается одним куском кода», «Линтер и тесты гоняются
одной командой». Правило «задача отвечает на «что для этого нужно сделать»» в
скилле стояло с самого начала — но относилось к содержанию задачи, а не к её
заголовку, и потому не применялось там, где заголовок и есть всё, что видно в
списке.
## Решено
**Р84. Заголовок отвечает на вопрос своего типа, и форм три.** Цель —
утверждение о возможности («Соперником может быть компьютер»); задача — глагол в
неопределённой форме, допускается «не» перед ним («Не отбрасывать молча лишние
символы в ходе»); идея — назывное, без обещания. Причина не стилистическая:
описательный заголовок называет **состояние**, а из состояния не видно, чего от
работы ждут — «Ничья объявляется, пока клетки есть» одинаково читается как
жалоба и как задание. В списке, где решают «брать или не брать», это разные
вещи.
Отсюда же разница индексов: роадмап — список возможностей, беклог — список работ.
Перепутанные формы заголовков делают каждый из них похожим на другой.
**Р85. Механизировано ровно то, что механизируется, — счётчиком, а не
замечанием.** `check` считает заголовки, у которых первое слово не оканчивается
на `-ть`/`-ти`/`-чь` (перед ним допускается «не»), и печатает **число** в блоке
здоровья. Замечанием на файл этого делать нельзя: проверка эвристическая, а
беклог, заведённый до правила, переоформляют не «заодно» — десятки одинаковых
строк научили бы пропускать весь блок.
**Р86. Годность формулировки судит отдельный агент `doc-wording`, а не чек-лист
в скилле.** Самопроверка текста слабее всего там, где формулировка казалась
удачной при написании, — а пишет и проверяет иначе один и тот же агент в одном
контексте. Агент читает пачку записей и возвращает **готовые формулировки на
замену**, ничего не правя сам; заголовок и «зачем» подставляются командой и
показываются человеку, потому что именно по ним задачу выбирают. Он намеренно не
проверяет ничего из того, что ловит `tasks.py check`: повторить машинную
проверку словами значит завести правилу второй дом.
**Р87. Заголовок секции — с прописной, после него пустая строка.** Во всех
индексах, включая секции беклога, имена которых выбирает проект: правило про
**оформление**, а не про имя. Канонические имена стали писаться с прописной
(`Готово` | `Запланировано` | `Направления` | `Разработка`, англ. `Done` |
`Planned` | `Directions` | `Tooling`), сверка везде идёт по нижнему регистру,
так что старые индексы читаются по-прежнему и поднимаются `check --fix`.
**Р88. Имя секции принадлежит заголовку индекса, файл на неё только ссылается.**
Это разрешает единственную неоднозначность починки: расхождение файла и
заголовка **в одном регистре** правится в пользу заголовка. Без этого шага
переезд на канон оставил бы `Готово` в роадмапе и `готово` в каждом файле цели —
расхождение безвредное, но вечное, потому что свести его было бы некому.
## Что из этого следует
**С82. Отбивка живёт на записи, а не на вставке.** `spaced_sections` вызывается
в `Plan.index`, через который проходит **каждая** запись индекса. Чинить отбивку
в каждом месте вставки значило бы полагаться на то, что ни одного не забыли, — а
мест вставки три (`--first`, `--after`, в конец).
**С83. Обкатка нашла два дефекта, которых не нашли ни линтеры, ни свои
проверки.** Вставка в пустую секцию съедала отбивку перед следующим заголовком;
мета, разорванная пустой строкой, теряла поля молча, а `check` видел только
следствие («без рода работы») и советовал `edit --kind`, который дописывал
**второе** такое же поле. Оба класса теперь названы: пропуск пустых строк идёт
только до первой непустой, а поле меты в теле — ошибка с названной причиной,
которую `--fix` намеренно не чинит.
**С84. Пустой проект показывает форму хуже живого.** Чтобы увидеть достигнутую
цель, отказ, спринт и все четыре рода работы, проект пришлось поставить на
середину пути. Это довод в пользу того, чтобы обкатку вести на *состоянии*, а не
на *старте*: у старта половина формы не наблюдаема.
**С85. Мелкая цель даёт две задачи, и это не повод её укрупнять.** У цели
«Соперником может быть компьютер» третья задача напрашивалась (выбор уровня
соперника), но не мерджится порознь: без сильного соперника выбирать не из чего.
Тест декомпозиции её отбил — частичный ответ на вопрос «не выродились ли цели в
ярлыки тем».
+66
View File
@@ -0,0 +1,66 @@
# 21. Язык проектных текстов — информационный стиль (2026-08-04)
## Что было
Языковые правила лежали внутри скилла `tasks`, в разделе «Как написана задача»:
англицизмы, неизвестные термины, «сложность формулировки — не признак сложности
работы». Три пункта, выведенные из практики, без общей опоры и без ответа на
вопрос «а что ещё сюда относится».
Дал ссылку на чужой скилл `prepare-jira-text` — там раздел «Язык» с
информационным стилем, таблицей англицизмов-калек и таблицей жаргона. Заодно
попросил найти справку об информационном стиле Максима Ильяхова и адаптировать
его.
## Решено
**Р89. У языка появился один дом — `canon/references/language.md`.** Не в
`tasks`, хотя пришёл он оттуда: правила относятся к документам канона, решениям
ADR, запискам разведки и сообщениям коммитов в той же мере, что к задачам, а
каталог задач и сам часть `docs/`. Раскладка отвечает, **где** текст лежит; этот
файл — **каким он должен быть**. `tasks/SKILL.md` оставил у себя четыре правила,
которые нарушаются чаще прочих, и ссылку.
**Р90. Инфостиль взят не целиком, и отброшенное названо вслух.** Он написан для
рекламы, статей и писем — текстов, где читателя надо удержать; проектный текст
читают потому, что надо. Взято: полезное действие, глагол вместо отглагольного
существительного, активный залог, факт вместо оценки, стоп-слова, «одна мысль —
одно предложение», параллельность, работающий заголовок. Отброшено:
**парцелляция** (рубленые фразы ломают причинную связь, а в решении ценность
именно в ней), **запрет вводных целиком** («если», «иначе», «в отличие от» — это
условия, то есть сведения), **запрет скобок и точки с запятой** (в технической
записи скобки несут уточнение — имя команды, единицы, слаг). Многоточие
запрещено: в проектном тексте оно значит «дописать позже».
Раздел «Что отброшено намеренно» написан не для полноты. Без него правило
читается как «пиши короче», и первый же агент начинает резать «поэтому» и
«иначе» — то есть ровно то, ради чего текст и писался.
**Р91. «Снять корону» переведено на здешнего читателя.** У Ильяхова это «надеть
корону на клиента». Здесь клиент — **ты сам через квартал** и тот, кто возьмёт
задачу. Отсюда конкретное требование: называть состояние и остаток, а не
пересказывать, как было интересно разбираться.
**Р92. Таблицы англицизмов и жаргона уехали в агента помеченной копией.** Устав
агента обязан быть самодостаточным — он не разрешает пути плагина и не ходит по
ссылкам, — а два дома у одного правила уже трижды расходились. Механизм для
этого в репозитории есть (`scripts/copies.py`), и это ровно его случай: копия
дословная и помеченная, проверка ловит расхождение.
## Что из этого следует
**С86. У агента вычитки правил стало двенадцать, и они разделены на две
группы.** «Форма записи» верна только для каталога задач, «язык» — для любого
проектного текста. Разделение не косметическое: находки докладываются группами и
в этом порядке, потому что форма меняет решение «брать или не брать», а язык —
только цену чтения.
**С87. Порог правки записан дважды и одинаково** — в `language.md` и в уставе
агента: правка без нарушенного правила не делается. Это единственная защита от
списка, в котором половина замечаний вкусовые: такой список перестают читать
целиком, и настоящие находки пропадают вместе с ним.
**С88. Переезд на канон 3 языком ничего не требует.** Шаг в changelog так и
записан: прочитать и ничего не переписывать задним числом. Сплошная вычитка
старых документов стоит дороже, чем даёт, а правила применяются к тому, что
правится сейчас.
+40
View File
@@ -0,0 +1,40 @@
# 22. Обкатка агента вычитки: имя, охват и «так везде» (2026-08-04)
## Что было
Агента вычитки прогнали по тестовому набору — 13 записей выдуманного проекта.
Устав он читал сам, как обычный подрядчик.
## Решено
**Р93. Агент называется `doc-wording`, а не `task-wording`.** Имя пришло из
задач, но правила языка относятся ко всем проектным текстам: документам канона,
решениям ADR, запискам разведки. Форма записи — вторая половина устава — верна
только для файлов `docs/tasks/items/`, и теперь это сказано заголовком раздела,
а не подразумевается. Вход агента расширен: список файлов или каталог,
вперемешку тоже.
**Р94. «Так сделано везде» — не оправдание, а признак.** Агент нашёл, что раздел
«Затрагивает» в нескольких записях называет не только границу, но и её будущее
состояние («источник хода становится двумя»), — и **промолчал**, объяснив это
принятым стилем каталога. Записи писал один агент за один заход: систематичность
здесь значит ровно обратное — правило не применялось вовсе.
В устав добавлено: одна и та же ошибка в пяти файлах даёт **одну находку на весь
набор** с перечнем, но не даёт права промолчать. Принятым стилем считается
только то, что назвал зовущий или что записано в конвенциях проекта.
## Что из этого следует
**С89. Находка агента попала в слово из собственного скилла.** «Цель про станок,
а не про игру» — метафора, которую я перенёс в тестовую запись из
`tasks/SKILL.md`. Проверка показала худшее: `станок` в каноне уже занят — «общий
станок» это красная проверка, врывающаяся в замороженный спринт (`canon.md`,
`session/SKILL.md`). Одно слово в двух смыслах, тот же класс, что и `окружение`
в [теме 19](19-roadmap-is-state-not-queue.md). В `tasks/SKILL.md` заменено на
«работа над инструментом и процессом» — как названа и секция роадмапа.
**С90. Одна находка на 13 записей — не провал вычитки.** Тексты писались сразу
по правилам, и находить в них было почти нечего. Показательно другое: агент
удержал порог (вкусовых правок не предложил) и явно сказал, по чему проверял
термины, — то есть отработали обе защиты, а не только та, что ищет.
+54
View File
@@ -0,0 +1,54 @@
# 23. Вычитка разделена на два прохода (2026-08-04)
## Что было
В уставе агента вычитки стоял заголовок «Форма записи — только для
`docs/tasks/items/`». Условная половина устава: на документе канона она молчит,
на задаче включается.
## Решено
**Р95. Проходов два: `task-form` и `doc-wording`.** Разделены не по охвату — по
**глубине**. Язык проверяется по словам и фразам, поштучно, и это подметание:
залог, оценки, стоп-слова, англицизмы, жаргон. Форма записи требует понять, что
задача делает, и **открыть файл цели**, на которую она ссылается, чтобы сверить,
какую строку «Завершения» задача двигает. Слитый проход одну половину делает
дорогой, а вторую — поверхностной.
Отсюда и разные модели: `doc-wording` — sonnet, `task-form` — opus. Первый
подметает, второй судит смысл, и ровно на суждении обкатка показала провал —
агент сам себе объяснил находку «принятым стилем каталога» ([тема
22](22-wording-agent-trial.md)).
**Р96. Условная половина устава — плохая конструкция сама по себе.** Правило,
которое «применяется только если», агент применяет по своему усмотрению, а
усмотрение и есть то, чего от него не ждут. Два коротких устава без условий
надёжнее одного длинного с ними — и это довод, годный за пределами этого случая.
**Р97. Каждый устав отказывается от чужой половины прямо.** «Увидел не по своей
части — скажи строкой в границах покрытия, не находкой». Без такого отказа две
проверки одного места расходятся и начинают спорить, а разнимать их потом
дороже, чем не сводить. Исключение ровно одно и названо: неудачное слово **в
заголовке** судит `task-form`, потому что заголовок целиком его.
**Р98. Порог правки переехал в дом и копируется в оба устава.** Он теперь в
`language.md` помеченным домом `порог-правки`: правка без нарушенного правила не
делается, систематичность нарушения — не довод в его пользу. Дублировать его
руками в двух уставах значило бы получить два разных порога через месяц.
## Что из этого следует
**С91. Шестое правило `task-form` — единственное, что читает больше одного
файла.** Оно же единственное, что смотрит **набор**, а не запись: строка
«Завершения», к которой не относится ни одна поданная задача, докладывается
отдельным блоком. Это граница между вычиткой и разбором, и она проведена внутри
правила, а не между агентами.
**С92. Порядок вызова — сперва `task-form`.** Его находки меняют решение «брать
или не брать», а язык — только цену чтения; и переписанный заголовок
бессмысленно вычитывать до того, как он переписан.
**С93. Помеченных копий стало шесть при пяти домах.** Механизм
`scripts/copies.py` впервые используется не для скелетов канона, а чтобы
удержать одно правило в двух уставах подрядчиков. Случай тот же: текст обязан
быть на месте, потому что подрядчик по ссылкам не ходит.
+41
View File
@@ -0,0 +1,41 @@
# 24. Обкатка двух проходов: два дефекта в собственных правилах (2026-08-04)
## Что было
Оба прохода запущены на тестовом наборе из 13 записей. `task-form` дал три
находки и блок «строки Завершения», `doc-wording` — пять находок. Разделение
окупилось сразу: `task-form` поймал ровно тот класс, на котором слитый агент
промолчал (границы, названные будущим состоянием, — тема 22,
[Р94](22-wording-agent-trial.md)).
Но два его правила разошлись с остальным каноном.
## Решено
**Р99. «Одна мысль — одно предложение» не распространяется на поля меты.**
`doc-wording` предложил разбить «зачем» надвое — а `task-format.md` требует от
«зачем» **одного предложения**: оно повторяется строкой индекса, и второму там
не поместиться. Агент честно выполнил тот документ, который читал; виноват не
он, а правило без оговорки. Оговорка записана и в доме (`language.md`), и в
уставе: тесно — сокращай, но не дели.
**Р100. «Не своё» бывает двух родов, и поступают с ними по-разному.** Чужому
подрядчику — строкой в границах покрытия, чтобы находка не пропала. **Машинной
проверке — вообще ничего, даже строкой**: это не потерянная находка, а уже
проверенное. `doc-wording` отправил в «замечено не по моей части» открытый
вопрос в задаче — а его ловит `tasks.py check`, и строка получилась шумом,
который выглядит как работа.
## Что из этого следует
**С94. Шестое правило нашло то, чего не искали.** Три строки «Завершения»
оказались **закрыты критериями задач, но не заявлены** самими задачами, а одна
строка цели (`checks-one-command`, «названа в README и в описании работы над
проектом») — закрыта наполовину. Агент назвал оба толкования и выбирать не стал,
как и велено. Выбрано сужение цели: описания работы над проектом у выдуманной
игры нет вовсе, и строка обещала то, чего негде исполнить.
**С95. Спорные находки полезны тем, что показывают спор правил, а не вкуса.** Из
пяти языковых находок три приняты сразу, две отклонены — и обе отклонённые
указывали на одно и то же место канона (правило 4 без оговорки). Вкусовых
находок не было ни одной: порог держится.
@@ -0,0 +1,56 @@
# 25. Секция `Сопровождение` и общий словарь трёх мест (2026-08-04)
## Что было
`Разработка` — имя, которое называло слишком много: роадмап **весь** про
разработку, и секция с таким именем не отличалась от остальных ничем. Предложено
`Сопровождение` (англ. `Operations`).
## Решено
**Р101. Секция называется `Сопровождение` / `Operations`, и её смысл расширен.**
Было «инструмент и процесс», стало «чем держат проект: инструмент, процесс,
эксплуатация». Расширение не косметическое: английское `Operations` при узком
смысле обещало бы эксплуатацию, а внутри лежал бы линтер. Либо слово, либо смысл
— сошлись на смысле, потому что метрики, логи, инфраструктура и выкладка в эту
секцию просятся и так.
**Р102. Версия канона не менялась, и это законно.** ~~Ни один проект на каноне 3
не стоит: healthlog и jellybit держат канон 2, повышение только предстоит.~~
**Отменено в тот же день ([тема
26](26-canon-4-retroactive-edit-cancelled.md)).** Посылка была ложной: healthlog
уже переехал на канон 3, и правка записи версии 3 задним числом переписывала то,
по чему он ехал. Правило осталось верным, применение — нет: черновиком запись
версии является ровно до того, как **первый** проект по ней поехал.
**Р103. Сопровождение и эксплуатация — целое и часть, и словарь у трёх мест
общий.** Тема живёт в трёх документах, и раньше каждое место говорило своим
словом. Теперь: сопровождение — всё, чем держат проект (инструмент, процесс,
выкладка, метрики, логи, инфраструктура, дежурство); эксплуатация — его часть,
работа системы на проде.
| Место | Уровень | Что там |
| --- | --- | --- |
| `ROADMAP.md`, секция `Сопровождение` | план | работы, которые собираемся делать |
| `architecture.md`, раздел «Эксплуатация» | состояние | как устроено сейчас |
| эксплуатационный проход ревью | оптика | «это упало через неделю на проде» |
**Сливать три места в одно слово было бы ошибкой**: они отвечают на разные
вопросы — план, состояние, проверка. Синхронизирован **словарь**, а не границы;
дом словаря — `canon.md`.
Слово **«поддержка» запрещено вовсе**: в нём слышится помощь пользователю, а это
третья работа, к этим двум не относящаяся.
## Что из этого следует
**С96. Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
сообщает о своём состоянии» — возможность (наблюдает пользователь сервиса);
«дежурный видит состояние на одном экране» — сопровождение (наблюдаем мы). Одни
и те же метрики попадают в разные секции роадмапа, и это верно.
**С97. `check --fix` чужую секцию не переименовывает — и правильно.** На
переименовании `Разработка``Сопровождение` проверка назвала секцию роадмапа
чужой и остановилась: регистр она правит сама, смысл — нет. Ровно то поведение,
которое нужно проекту при повышении канона.
@@ -0,0 +1,53 @@
# 26. Канон 4: правка задним числом отменена (2026-08-04)
## Что было
Секцию `Разработка` переименовали в `Сопровождение` без повышения версии канона
— на посылке «ни один проект на каноне 3 не стоит» (тема 25,
[Р102](25-maintenance-section-shared-vocab.md)). Посылка оказалась ложной:
healthlog уже переехал, `docs/.pm.json` держит `"canon": 3`, а роадмап — секцию
`Разработка` с прописной. Правка записи версии 3 переписывала то, по чему он
ехал.
## Решено
**Р104. Запись версии — черновик ровно до первого переехавшего проекта.** После
этого она **история**, и любое изменение канона заводит новую версию, даже если
меняется одно слово. Проверять это дёшево: `grep '"canon"' */docs/.pm.json` по
живым проектам. Дорого — обратное: проект, повышенный по тексту, которого больше
не существует, невоспроизводим.
Запись версии 3 восстановлена дословно (`Разработка` | `Tooling`), переименование
уехало в версию 4. jellybit, стоящий на каноне 2, прочтёт обе записи подряд и
заведёт `Разработка`, чтобы через шаг переименовать; в шаг версии 3 добавлена
оговорка «едешь сразу на 4 — заводи `Готово` последней и не переставляй дважды».
Лишний шаг — плата за честную историю, и она мала.
**Р105. `Готово` переехало вниз, и порядок секций стал каноническим.**
Достигнутое **копится**: через год этой секции больше, чем всех остальных
вместе, — и стоя первой она отодвигает за экран ровно то, ради чего роадмап
открывают чаще всего. Порядок теперь проверяется (`roadmap_lint`) и правится
(`check --fix` переставляет секции вместе с содержимым): без проверки порядок
разъедется молча, а переставлять секцию с десятком строк руками — работа, на
которой ошибаются.
**Р106. Индексы позиций считаются из самого кортежа.** `ACHIEVED` был `0` и стал
`3`; хардкод индексов пережил бы перестановку молча и сломал бы `close`. Теперь
`PLANNED, DIRECTIONS, OPERATIONS, ACHIEVED = range(len(ROADMAP_SECTIONS))`
переставили секцию, индексы переехали сами.
## Что из этого следует
**С98. Отбивка нужна и перед заголовком.** Перестановка блоков ставит два
заголовка вплотную — `spaced_sections` правил только строку после. Дефект
нашёлся сразу же, на первой перестановке демо-набора: класс правки, существующий
только потому, что появилась другая правка.
**С99. `check --fix` переставляет, но не переименовывает.** Чужую секцию он
оставляет ошибкой, и на переименовании `Разработка``Сопровождение`
останавливается: имя — решение человека, порядок — механика. Тот же разрез, что
между регистром (правит) и составом (не трогает).
**С100. Версия канона отделяет состояния проектов, а не редакции текста** — и
ровно поэтому её нельзя не поднять, когда состояние хоть одного проекта уже
зафиксировано.
+82
View File
@@ -0,0 +1,82 @@
# 27. Тип записи стал единственной осью и задаёт схему (2026-08-05)
Заметка просила «каждый тип задач сделать своей сущностью»: эмодзи на тип, тип
первым полем меты, категория вместо секции, описание типа с обязательными
разделами и алгоритмом, идеи в конец. Разбор показал, что первый шаг обязан быть
другим — не добавить типу свойств, а **сократить число осей**.
**Р107. Осей было две, и ортогональность была фальшивой.** Тип записи
(`goal`/`idea`/`task`) и род работы (`kind:<род>` тегом) давали двенадцать
клеток произведения, из которых законны шесть: у цели род запрещён, у задачи
обязателен, у идеи пуст и на практике не ставится. Плюс «алгоритм работы над
записью такого типа» крепится не к `task`, а к `fix` и `research` — то есть к
роду. Ось, к которой пишется алгоритм, и была настоящим типом. Оси схлопнуты в
одну из пяти значений: `goal` | `feature` | `fix` | `chore` | `research`.
**Р108. Тип `idea` упразднён: состояние не может быть типом.** Он значил не род
работы, а незаполненность — «первый, второй или третий вопрос теста готовности
не отвечается». Состояние меняется по мере того, как запись дописывают, а тип
меняют командой, и на этом расхождении `idea` и жила: её приходилось «понижать»
и «повышать» вручную. Теперь состояние выводится из заполненности — **`research`
без раздела «Вопрос» это сырьё**, — и различие держит та же машина, что и всё
остальное.
Цена решения названа сразу: `research` теперь вбирает и замер реальности, и
сырую функцию («Подсказка следующего хода»). Обосновано это тем, что у обоих
**один исход — записанный ответ, а не изменение системы**, и одна приёмка. Имя
`rnd` из заметки отклонено в пользу `research`: аббревиатура читается как
`random` и не расшифровывается тому, кто вернётся к беклогу через квартал, а
`research` уже стоял в файлах живых проектов — миграция тронула только бывшие
идеи.
**Р109. Дом типа — поле меты, эмодзи производна.** Прежнее правило «отдельного
поля типа нет: два места для одного факта разъезжаются» отменено не потому, что
разонравилось, а потому, что его аргумент был против **префикса плюс поля**. При
переносе дома в мету дом остаётся один; из индекса тип при этом пропадал бы —
там, где принимают решение «брать или не брать», — и это чинит эмодзи. Она стоит
в H1, а не в строке индекса, чтобы инвариант «заголовок в индексе дословно»
остался нетронутым: одна проверка вместо двух.
**Р110. Поле места назвали по типу, а не одним словом на всех.** «Категория»
вместо «Секции» — просьба заметки, но одинаковое переименование закрепило бы
смешение: у задачи поле называет полку домена, в которую она вернётся из
спринта, у цели — часть роадмапа, то есть состояние очереди. Разные имена
(`Категория` / `Секция`) выбраны именно потому, что **какое поле обязательно,
решает тип** — то самое, ради чего затевалась вся правка.
**Р111. Два новых обязательных раздела появились из уже записанных правил,
которые нечем было проверить.** «Не воспроизводится — это `research`, а не
`fix`» стояло в каноне и не проверялось: раздел `Воспроизведение` делает его
проверяемым. Приёмка разведки — «записанный ответ, а не изменённый код» — тоже
стояла, но `sprint take` требовал от `research` два-пять критериев с оракулами,
и они писались ради проверки; вместо них `Вопрос` и `Куда ляжет ответ`.
**Р112. Сортировка «по важности» отклонена, «сырьё в конец» взято.** Первая
требует, чтобы кто-то важность поддерживал, — это ровно тот приоритет, от
которого правило 4 отказалось сознательно. Вторая **выводится из типа и
заполненности**, а не назначается человеком, и потому проверяется машиной и
приоритетом не становится. Разрез прошёл по признаку «кто источник порядка», а
не по признаку «полезно ли».
## Что из этого следует
**С101. Правило можно отменять его собственным аргументом.** «Отдельного поля
типа нет» держалось на «два места для одного факта»; перенос дома оставил одно
место, и правило перестало применяться. Проверять надо не запись правила, а то,
выполняется ли ещё его посылка.
**С102. Схема, шаблон и проверка растут из одной таблицы.** `TYPE_SCHEMA` кормит
и `body_template`, и `schema_verdict`: иначе `add` кладёт то, на чём `sprint
take` потом откажет. Тот же приём, что нормализатор `spaced_sections` для
оформления индексов.
**С103. `--fix` не угадывает того, чего нет.** Тип переносится из тега `kind:` и
префикса `[goal]`/`[idea]` детерминированно, но записи, заведённые до появления
рода работы, не несут ни того ни другого — `feature` от `chore` машина не
отличает. Они уходят в `НЕОДНОЗНАЧНО` поимённо, а не получают значение по
умолчанию, которое врало бы ровно там, где по нему принимают решение.
**С104. Мигрирующие шаги обязаны читать отложенный текст, а не диск.** Шагов,
правящих мету, стало пять, и второй, перечитавший файл, стёр бы правку первого.
Общий `stage()` поверх `files` снял целый класс отказов, который до этого
держался на том, что шагов было мало.
+65
View File
@@ -0,0 +1,65 @@
# 28. Слаг подкреплён проверкой, обещанный судья заведён (2026-08-05)
Два пункта заметок, оба про одно: правило было записано и никем не исполнялось.
**Р113. Правило про английские слаги существовало и не проверялось ничем.**
`canon.md` говорил «слаги файлов, capability и задач — английские, kebab-case»
одной строкой в хвосте раскладки; `docs.py` имён файлов не смотрел вовсе. Итог
предсказуем и нашёлся в самом плагине: единственный пример ADR в скилле `docs`
назывался `ADR-2026-08-03-ochered-tablicej`. Раскладка канона при этом
приглашала к нарушению — в схеме стояли плейсхолдеры `<тема>.md`, то есть слово
«тема» по-русски там, где надо было писать `<slug>`.
Разрез проверки — по тому, что машина знает точно: кириллица в имени и не-kebab-case
**жёстко**, форма `ADR-ГГГГ-ММ-ДД-slug.md` жёстко, транслит **эвристикой**, то есть
замечанием. Набор маркеров транслита подобран так, чтобы **ложных срабатываний не
было вовсе**: выброшены `ost` (ловит `post`, `cost`), `sch` (`schema`), `ya`
(`yaml`), `nost` (`nostalgia`), хвост `ii` (`radii`). Цена названа: `sostoyanie-partii`
проходит мимо. Правило, краснеющее на правде, приучает пролистывать весь блок —
это дороже пропуска.
**Р114. Канон три версии обещал судью, которого не было.** В `canon.md` есть
таблица «Что проверяет машина, а что человек», и её правая колонка — смысловой
дубль, поведение в `architecture.md`, протухший факт, достаточность честной
строки — описывала работу, которую никто не делал: скилл `canon` предлагал
агенту судить об этом самому, то есть проверять то, что он же и писал. Заведены
`doc-consistency` и `doc-code-drift`, а колонка получила третий столбец с именем
судьи: обещание без адресата и есть тот способ, которым правило перестаёт
исполняться.
**Р115. Агентов двое, разрез по глубине, а не по охвату.** Тот же довод, что
развёл `task-form` и `doc-wording`: сверка текста с текстом дёшева и зовётся на
каждом синке документации, сверка с кодом требует читать репозиторий и зовётся
раз в спринт. Слитый агент делает дешёвую половину редкой либо дорогую —
поверхностной.
**Р116. Перечень фактов, сверяемых с кодом, закрыт.** Имя основной ветки,
команды, пути, зависимости поимённо, настройки с числовым значением, единые
точки проекта, capability, проверяемые инварианты. «Сверить архитектуру с кодом»
— задача без дна, и агент, которому её поставили, выдаёт правдоподобную труху
вместо находок. Отсюда и форма доклада `doc-code-drift`: он начинается
**таблицей проверенного**, а не находками, — по ней видно, чего он не смотрел.
**Р117. Карта домов уехала в устав агента помеченной копией.** Устав ссылался на
файл плагина, а агент работает в репозитории проекта, где плагина может не быть.
Копия дословная, под маркерами `дом`/`копия`, и `copies.py` теперь её сторожит —
механизм для этого в репозитории уже был.
## Что из этого следует
**С105. Записанное правило без проверки не исполняется даже автором.** Слаг ADR
нарушен в единственном примере, который плагин показывает как образец. Тот же
класс, что «прозаический триггер ADR дал 6 записей на 43 изменения»: умолчание
становится отличимым только когда его проверяют.
**С106. Плейсхолдер — часть правила.** `<тема>.md` в схеме раскладки перевешивал
строку правила, стоявшую двумя абзацами ниже: образец читают вместо текста.
**С107. Эвристика настраивается по ложным срабатываниям, а не по полноте.** Ноль
ложных при одном пропуске лучше, чем наоборот: пропуск стоит одной ненайденной
находки, ложное срабатывание — доверия ко всему блоку.
**С108. Докстрока разошлась с кодом ровно там, где её читают.** `copies.py`
показывал закрывающие маркеры как `<!-- /дом -->`, а требовал `<!-- /дом: <id>
-->`; нашлось это первой же попыткой ими воспользоваться. Пример в докстроке —
тот же образец, что плейсхолдер в схеме.
+65
View File
@@ -0,0 +1,65 @@
# 29. Обкатка `doc-consistency` на самом dev-skills (2026-08-05)
Первый прогон агента — по репозиторию, который его же и содержит. Два прохода
(av-dev-pm; пайплайн плюс верхний уровень), 17 находок, все подтверждены по
файлам.
**Р118. Агент нашёл ровно тот класс, ради которого заводился, и в свежей
работе.** Пять находок — остатки прежней модели типов в файлах, которые я не
дошёл поправить двумя коммитами раньше: `adopt.md` держал имена секций **канона
2**, `from-review.md` и `TODO.md` — упразднённый `[idea]`, `task-batch` в другом
плагине — «задачи-идеи». Правку модели я вёл от документов, которые менял, а не
от документов, которые на них ссылаются, — и обратный обход не сделал ни разу.
**Р119. Самая дорогая находка была моей и свежей.** Таблица типов в `canon.md`
объявляла цель у `fix` запрещённой, а `tasks/SKILL.md` и `task-fix.md`
необязательной; код на стороне вторых. Копия разошлась с домом **за один день**
— я написал обе половины в одном коммите. Это и есть цена второго дома в чистом
виде: не «когда-нибудь разойдётся», а «разошлось прежде, чем высохли чернила».
Исход не «поправить значение», а **убрать причину**: `canon.md` дважды объявлял,
что фиксирует только словарь типов, — значит колонкам «разделы» и «цель» в нём
не место. Осталась таблица из двух колонок и ссылка на дом схемы.
**Р120. Копии перечня «чем держат проект» разъехались втроём.** `canon.md`,
`tasks/SKILL.md` и `task-goal.md` пересказывали его своими словами: «метрики и
логи» против «мониторинга», «проверки» есть в двух из трёх. При этом
`tasks/SKILL.md` **ссылался на дом рядом с собственным пересказом** — ссылка не
мешает копии разойтись, если копия всё равно стоит.
**Р121. Находка про коммиты снята как неверная, и это дефект самого агента.** Он
прочитал `av-dev-git/skills/commit/SKILL.md` («без `Co-Authored-By`») как
описание практики этого репозитория и предъявил 38 коммитов с трейлером. Но
dev-skills — **маркетплейс плагинов**: скилл коммита здесь продукт, уезжающий в
чужие проекты, а не правило, которому подчиняется сам репозиторий. Устав агента
не различает «документ описывает этот репозиторий» и «документ описывает то, что
репозиторий производит».
**Р122. Счётчики в документах отменены как класс.** `REMAINING.md` держал «после
разбора двенадцати тем и 16 коммитов» (стало 28 и 52) и «три неизмеренных
изменения подряд» (стало больше). Оба числа обязан двигать человек, и оба
отстали молча. Заменены на формулировки, которые не надо поддерживать, и в шапку
записана причина.
## Что из этого следует
**С109. Правка модели идёт по обратным ссылкам, а не по изменённым файлам.**
Меняешь дом — обойди тех, кто на него ссылается: `grep` по упразднённому слову
дал бы все пять остатков за минуту. Это дешевле любого агента и должно идти до
него.
**С110. Ссылка на дом не отменяет копию, стоящую рядом.** Проверять надо не
«есть ли ссылка», а «есть ли пересказ»; `tasks/SKILL.md` имел и то и другое.
**С111. Копия расходится с домом в пределах одного коммита.** Прежняя оценка
(«разойдётся на первой правке») занижена: расхождение возникает при написании,
если оба места пишет один проход.
**С112. Агент, читающий репозиторий-продукт, обязан различать «про нас» и «про
то, что мы производим».** Иначе он предъявляет продукту практику его
потребителя. Устав `doc-consistency` этого различения не содержит — остаток
записан в REMAINING.
**С113. Число в документе — обязанность, которую никто не берёт.** Счётчик тем,
коммитов, правок протухает молча; формулировка без числа дешевле его
сопровождения.
+41
View File
@@ -0,0 +1,41 @@
# 30. `av-dev-backlog` удалён (2026-08-05)
Плагин был помечен устаревшим решением [Р17](04-plugin-boundaries.md) и жил до
перевода jellybit. Удалён раньше этого срока.
**Р123. Замороженный плагин стоит дороже, чем кажется.** Он не менялся, но
платил собой в каждой проверке репозитория: `exclude` в `pyproject.toml`,
`SKIP_DIRS` в `copies.py`, два абзаца README, оговорка в описании маркетплейса,
чтобы не ловить триггер «добавь задачу в беклог». Пять исключений ради кода,
который никто не читает, — и каждое надо было объяснять всякий раз, когда
кто-нибудь спрашивал, почему проверка обходит каталог.
**Р124. Понимание старой раскладки уехало из плагина раньше самого плагина.**
`docs/backlog/` читает не `backlog.py`, а `av-dev-pm:tasks``adopt.md` и
адаптер в `tasks.py` держат ту же раскладку как **вход миграции**. Плагин
перестал быть единственным, кто её знает, ещё когда писался `adopt`; условие
«живёт до перевода последнего проекта» с тех пор охраняло пустоту.
**Р125. Опасение про порядок снятия не подтвердилось.** Удаление опередило
снятие: на jellybit плагин оставался включённым, когда записи в маркетплейсе уже
не было, и ожидалась ручная чистка `enabledPlugins` и `installed_plugins.json`.
`claude plugin uninstall` отработал штатно — он идёт **по реестру, а не по
манифесту маркетплейса**, и отсутствие записи там ему безразлично.
Предупреждение из README снято, вместо него записан проверенный факт.
## Что из этого следует
**С114. Устаревшее удаляют, а не замораживают.** Заморозка выглядит бесплатной,
но растекается исключениями по конфигам и требует объяснения в каждом месте,
куда попала. Если удалять пока рано — назвать условие и срок; условие без срока
переживает свою причину.
**С115. Условие «живёт до X» проверяют на живость, а не на X.** Здесь X (перевод
jellybit) не наступил, но причина условия отпала раньше: знание раскладки
переехало в `adopt`. Перепроверять надо основание, иначе условие держит само
себя.
**С116. Порядок снятия и удаления из маркетплейса свободный.** `uninstall` живёт
реестром, манифест ему не нужен. Правило записано после проверки, а не из
осторожности, — и осторожность здесь стоила бы лишнего абзаца в README про
починку, которой не бывает.
+126
View File
@@ -0,0 +1,126 @@
# 31. Ревизия покрытия `av-dev-pm` продакт-оптикой (2026-08-05)
Сабагент в роли продакт-менеджера оценил покрытие жизненного цикла личного
проекта (один человек, недели-месяцы) скиллами и агентами `av-dev-pm`. Скоуп
сужен по ходу разбора: деплой и разбор инцидентов на проде делаются вручную,
скиллов под них не заводим. Осталось планирование, разработка и доработка.
**Р126. Шаг 2 сессии требовал чисел, которых процесс отказался собирать
решением.** `cadence.md` делал обязанностью пересмотр «ориентира по размеру
спринта, прироста беклога на закрытую задачу, времени на задачу» и «сколько
заняли задачи **против ожидания**» — с обоснованием «иначе обязанность висит
ничья». Данных под это нет: у записи нет дат заведения, взятия и закрытия,
`close --implemented` удаляет файл, `sprint close` очищает `SPRINT.md`. Хуже
того, «против ожидания» и «время на задачу» требуют оценки и тайм-бокса, а
`session/SKILL.md` в «Почему не Scrum» их прямо не берёт: пункт противоречил
решению, стоящему через файл от него.
Исход — **выкинуть, а не подпереть данными**. На практике числа не
пересматривались ни разу, и заводить под них учёт дат значило бы обслуживать
обязанность, которой никто не брал. Осталось качественное: что сломалось в
процессе, что оказалось дороже, чем выглядело при заведении, какие правила не
сработали. Шаг 3 пункт 9 переименован из «переоценки по измеренному» в
«переоценку по пройденному», судит человек по памяти о спринте. Рядом записано,
что замеров нет **намеренно** — иначе следующий читатель заведёт их обратно как
недостающие.
**Р127. `doc-consistency` переехал с каждого синка на сессию, к
`doc-code-drift`.** Агент на `opus` зовётся шагом 9 пайплайна, то есть на каждой
задаче: 5–8 opus-проходов за спринт по документам, которые за спринт меняются на
несколько абзацев. Обоснование в каноне («сверка текста с текстом дёшева») верно
относительно второго агента, но не в абсолюте на одиночке.
Довод сильнее денег: **расхождение между двумя документами по определению
требует двух документов**, а на большинстве задач синк правит один. И пачка,
отбираемая работой, не видит того, чего работа не касалась, — а расхождение живёт
ровно там: правка отменяет решение в одном документе, парный статус нужен в
другом. Это был открытый вопрос `REMAINING` про охват ADR при пересмотре; переезд
его закрыл. Цена — потеря привязки находки к задаче, которая её породила: по теме
29 именно эта привязка дала пять самых точных находок. Принято сознательно.
**Р128. Отмена цели получила порядок, но не флаг.** `close` запрещал закрыть
цель с живыми задачами, а что делать с этими задачами, не говорил нигде: шаг 3
сессии знал только «та ли цель», `task-goal.md` описывал одно достижение, а
`session/SKILL.md` вдобавок утверждал «цель постоянна». Человек получал отказ с
перечнем и никакой подсказки.
Порядок записан: сперва задачи поштучно (`close --reason` своей причиной либо
`edit --goal` на другую цель), потом сама цель через `close --reason` в
`REJECTED.md`, а не в `Готово` — отменённая цель не умеет ничего. Флаг
`--cascade` отвергнут: отмена цели редка и дорога, и поштучный разбор здесь не
церемония, а единственный момент, когда видно, что из задач переживёт цель.
Каскад превратил бы его в один Enter. **Причина у каждой задачи своя**: «цель
отменена» это пересказ команды, в `REJECTED.md` от него нет пользы через квартал.
Место процедуры — переоценка на сессии, а не отдельный заход: отмена цели **и
есть** разбор всех её задач, а разбор задач — шаг 3.
**Р129. У брошенного спринта появился второй законный исход, без порога.**
`--dissolve` во всех текстах был привязан к блокеру, и скрипт отказывал словами
«роспуск объясняется блокером». Вернувшийся к набору, который стоял месяц, не
имел законного хода: двигать нельзя (заморозка), распускать не по чему. Теперь
роспуск объясняется блокером **или тем, что набор протух**.
Порога в неделях сознательно нет — это тот же класс, что выкинутые числа шага 2:
счётчик простоя пришлось бы вести руками, а решает всё равно человек. Признак не
срок, а **что набор перестал быть твоим**: перечитываешь, зачем эти задачи вместе
— он протух. Туда же добавлена точка входа «вернулся, а спринт открыт»: `check`,
`SPRINT.md`, развилка продолжать/распустить. Середины у развилки нет намеренно —
«доделаю пару штук и решу» это работа по набору, которого ты не понимаешь.
**Р130. Журнал канона прогоняется как есть, а проверка исхода поручена судьям.**
Схлопнуть записи 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](29-doc-consistency-trial.md).
**С119. Частота вызова агента выводится из того, что он ищет.** Судья
расхождений **между** документами бессмысленен там, где документ один; значит
его место не на задаче, а на наборе задач. Цена вызова подтвердила вывод, но не
она его дала.
**С120. Запрет обязан называть выход.** `close` верно не давал осиротить задачи,
но текст отказа перечислял препятствия и молчал о ходе. Проверка без названного
следующего шага — половина работы: она защищает данные и бросает человека.
**С121. Признак вместо порога там, где счётчик пришлось бы вести руками.**
«Набор перестал быть твоим» проверяется в момент вопроса и ничего не требует
хранить; «прошло N недель» требует учёта, который никто не ведёт, и всё равно
кончается решением человека.
**С122. Версионирование без единого переехавшего проекта — не журнал миграций, а
история правок.** Довод за схлопывание был верен по факту и отвергнут по
принципу: обкатка на живых проектах и проверяет, работает ли механизм. Схлопнуть
значило бы не прогнать его ни разу и оставить вопрос открытым.
**С123. Проверка версии не есть проверка миграции.** Число в `.pm.json` двигает
тот же проход, что делал шаги, — и двигает независимо от того, все ли сделаны.
Механической проверки существа нет; там, где её нет, ставится судья, а не
отметка.
+54
View File
@@ -0,0 +1,54 @@
# 32. Сквозной проход по словарю: пять слов сняты, девять закрыты списком (2026-08-05)
Проход упрощения ([тема 31](31-pm-coverage-product-review.md)) уткнулся в один и
тот же класс у всех пяти агентов: слово, живущее в трёх-шести файлах разом.
Правка в одном месте развела бы словарь, правка во всех — уже не упрощение
текста скилла. Каждый агент честно остановился и записал слово в свой отчёт, и
одни и те же слова всплыли в разных отчётах. Разобрано отдельным проходом.
**Р131. «Слово прижилось» не проверяется, поэтому заменено списком.** Оговорка в
`language.md` звучала так: не переводится «термин, у которого нет точного
русского эквивалента и который в команде уже прижился». Проверить это на глаз
нельзя — прижившимся выглядит любое слово, встреченное трижды, и ровно так пять
агентов подряд и рассудили. Оговорка заменена **закрытым списком из девяти
терминов** с колонкой «что называет»: интейк, триаж, провенанс, дедуп, чек-лист,
дифф, промпт, сущности OpenSpec, роды проходов ревью. Слово не из списка и не из
таблицы имён вещей — находка, а не принятый стиль.
Список заведён домом `язык-словарь` в `language.md` и копией в уставе
`doc-wording`. Копия обязательна: агент работает в репозитории проекта, где
плагина может не быть, и без списка предъявил бы «интейк» как англицизм.
**Р132. Пять слов сняты, и все пятеро выглядели словарём, не будучи им.**
`конфляция` → смешение (4 места), `декорреляция` → разведённость (6),
`непоймание` → почему не поймали (9), `эвал-сет` → проверочный набор (4), `гайд`
→ руководство (6). Латинизм или калька при живом русском слове в каждом случае.
Разбор `декорреляции` показателен: проект **уже владел** нужным словом — «агенты
разведены по глубине», «разведены по охвату» — и держал рядом латинский синоним
того же понятия. Это не англицизм, а второй дом для слова.
`непоймание` снято ещё и потому, что форма журнала дефектов, которую канон кладёт
в проекты, спрашивает «Почему не поймали» — а проза рядом называла это
«причиной непоймания». Скелет и проза о скелете говорили разными словами.
**Р133. Снятое записано вместе с оставленным, в одном списке.** Иначе снятое
возвращается: слово уходит из текстов, но ничто не мешает следующему проходу
завести его заново — оно ведь короткое и точное. Пять слов названы поимённо с
заменой каждого.
## Что из этого следует
**С124. Escape hatch без перечня — это разрешение, а не исключение.** «Термин,
который прижился» освобождает от правила любое слово: проверка «прижился ли»
возвращает «да» всякий раз, когда слово встретилось. Исключение из правила
обязано быть списком, иначе оно съедает правило.
**С125. Слово, от которого агент отказался править, — материал для отдельного
прохода, а не мусор отчёта.** Пять независимых агентов сошлись на одном наборе
слов, ни разу друг друга не видя. Список «что не тронул» оказался полезнее
списка правок именно этим.
**С126. Снятое слово называется вместе с заменой и остаётся записанным.** Убрать
из текстов недостаточно: без записи «это снято и вот чем заменено» слово
возвращается первым же, кто найдёт его удачным.
+73
View File
@@ -0,0 +1,73 @@
# 33. Стоимость ревью: снят самый дорогой проход и самая дорогая модель (2026-08-06)
Прогоны стали долгими, а счёт в токенах — заметным. Разбор шёл не по находкам, а
по статьям расхода: что в конвейере стоит больше всего и что из этого окупается.
Две статьи названы прямо оператором.
**Р134. Проход независимой реализации снят целиком, и с ним профиль `deep`.**
`reimpl` писал свою реализацию узла, не открывая существующую, и диффил по
решениям. Его счёт определялся **объёмом вывода** — он один писал код, а не
читал его, — и на прогоне это была самая большая строка расхода. Снят по решению
о стоимости.
Профиль `deep` от этого не «похудел», а исчез: `reimpl` был **единственным**, чем
он отличался от `wide` (обоим оставалось бы 0, 1, 2, 4, 5). Держать два имени для
одного состава нельзя — ровно от этой болезни лечилась ступень `wide` (решение
JJJ): у профиля обязан быть один правильный ответ, иначе реестр состава нечем
проверять. Ступеней теперь три: `quick`, `standard`, `wide`.
Вместе с профилем ушло всё, что обслуживало только его:
- **барьер стоимости** — он существовал ровно затем, чтобы дорогой проход не
писал реализацию против кода, который через час перепишут. Дорогого прохода
нет, и граф стал плоским во всех профилях: от гейта до триажа. Рёбер осталось
два вида вместо трёх — зависимость и конфликт за ресурс;
- **тест «идентичность, слияние, разбор»** (решение из [темы
27](27-record-type-single-axis.md)) — он служил
единственной цели: выбрать `deep` не по ощущению. Выбирать больше нечего, и
полторы страницы теста сняты вместе с проектным перечнем мест в
`docs/review.md`;
- **стадии перенумерованы**: 0 гейт, 1 сверка, 2 враждебный и эксплуатационный,
3 архитектурный, 4 триаж. Дыра на месте третьей читалась бы как пропущенная
стадия.
**Р135. Снятие записано как сознательное сужение, а не как «класс оказался
пустым».** `calibration.md` требует замера на двух проектах перед удалением
прохода, и замера не было — было решение о цене. Значит и в «Честном пределе»
стоит честная строка: **«не знаю, чего не знаю» больше не достаёт никто.**
Остаток независимого взгляда дают профиль `design` (код пишется под его находки)
и `architecture` (второй способ, лишние слои), но альтернативной реализации, с
которой можно сдиффить решения, у конвейера нет. Класс уходит в границы покрытия
каждого прогона, а у проекта — в подраздел «перестали проверять сознательно».
Без этой записи снятие через месяц читается как «проверено и признано лишним»,
и вернуть проход было бы не на чем.
**Р136. Самая дорогая модель снята со всех проходов.** На ней сидели трое:
`review-triage`, `review-architecture` и `doc-code-drift` из `av-dev-pm`. Все
трое переведены на `opus`. Основание для верхней модели — «ошибка
распространяется дальше самой находки» — никуда не делось, но оно объясняет,
почему эти двое **не опускаются до `sonnet`**, а не почему им нужна ступень выше
`opus`: разницы в пользу более дорогой модели не показал ни один прогон, а время
и счёт она множила.
Палитра цветов схлопнулась до двух: `sonnet` → green, `opus` → yellow. Красного в
репозитории больше нет, и `frontmatter.py` теперь отвергнет модель вне этих двух —
раскладка проверяется механически, как и раньше.
## Что из этого следует
**С127. Профиль, у которого не осталось собственного прохода, — не профиль.**
Ступень стоимости определяется тем, что она **добавляет**; сняли добавку — сняли
ступень, а не оставили имя. Иначе два имени указывают на один прогон, и состав
снова нечем проверить.
**С128. Удаление по цене и удаление по замеру записываются по-разному.** Первое
обязано назвать класс, который перестал проверяться, и оставить его в границах
покрытия. Второе — сослаться на замер. Смешение их даёт самый дорогой вид
тишины: пробел, выглядящий как решённый вопрос.
**С129. Механика, обслуживающая один проход, снимается вместе с ним.** Барьер
стоимости, тест выбора верхней ступени и проектный перечень мест держались
только на `reimpl`. Оставшись, они выглядели бы работающими правилами и тратили
бы внимание на каждом прогоне.
+87
View File
@@ -0,0 +1,87 @@
# 34. Пропускная способность против глубины: тяжёлые проходы уехали в верхнюю ступень (2026-08-06)
Тема 33 сняла самую большую разовую статью расхода, но не тронула главную —
**частоту**. Меряющая пара стояла в `standard`, то есть на большинстве задач, и
именно она делала прогон долгим: два прохода держат машину, идут цепочкой и
доказывают находки запуском. Разбор шёл от цели, названной прямо: **лучше
поправить в следующей задаче, чем держать одну два часа.**
**Р137. `adversary` и `ops` переехали в `wide`, и это решение по цене, а не по
ценности.** Стадия осталась самой урожайной за всю историю замеров — пять из
семи выживших находок дозапуска и единственная находка про молчаливый старт
отката. Но её ценность оплачивается на **каждой** задаче, а получается на
немногих: оракул добывается запуском, запуск — это машина, цепочка и часы.
Ступень, которая раньше была умолчанием, стала исключением на 5–10% задач.
**Р138. Заведён `review-basics` — мелкая осадка двух тяжёлых проходов, без
единого запуска.** Он стоит только в `standard` и берёт ту половину вопросов, на
которые отвечают **чтением**: таймаут и отказ соседа, идемпотентность и
одновременная запись, остановка на середине, частичный откат при двух версиях,
наблюдаемость и тишина, очевидный рост объёма — плюс два вопроса архитектурного:
второй способ мимо единой точки (грепом, не картой) и что отсюда удалить.
Потолок 4 находки, машину не держит, ничего не меряет.
Отдельная его обязанность — **вопрос 4, частичный откат**. Без него правило
«миграция схемы не поднимает ступень» рассыпалось бы: раньше миграцию разбирал
`ops`, а он теперь в `wide`. Проход заведён не «до кучи», а затем, чтобы у
`standard` остался хоть один взгляд на ось времени.
Модель у него верхняя, `opus`, и это не противоречит слову «средний»: усилие
режется **входом и потолком**, а не моделью. Дешёвая модель на проходе
с мнением платит триажем — это записанный замер, и отменять его без нового замера
нельзя.
**Р139. Объём и незнакомость изменения вошли в правило выбора ступени.** Раньше
ступень выбиралась только по классу («вводит ли новое понятие»), и правило прямо
запрещало смотреть на размер. Теперь вопросов два: крупное или незнакомое
(трогает несколько узлов, переносит ответственность, форму решения нащупывают по
ходу) → `wide`; мелкое (один узел, форма очевидна заранее, откат — обратная
правка) → `quick`; всё остальное → `standard`. Причина смены: цена
разбирательства растёт именно с объёмом и неизвестностью, а не с классом
правила.
Отрицательный тест `quick` сохранил прежнюю мудрость в новой рамке: **что после
мерджа не откатывается обратной правкой — не `quick`, каким бы маленьким ни был
дифф.** Три строки миграции идут в `standard`.
**Р140. Спорный случай решается вниз, и асимметрия объяснена ценой.** Между
`standard` и `wide` — в пользу `standard`: ошибка сюда стоит находки на
следующей задаче, ошибка обратно стоит трёх тяжёлых проходов на каждой задаче,
выбранной неверно. Между `quick` и `standard` — тоже в пользу `standard`, но по
другой причине: там разница в один дешёвый проход, зато единственный, кто на
нижних ступенях смотрит на отказы.
Доля `wide` 5–10% записана как **проверка правила, а не пожелание**: если ступень
уходит каждой третьей задаче, её выбирают по ощущению важности.
**Р141. Сделка записана вместе с механизмом обратной связи, иначе это тихая
потеря качества.** На `quick` и `standard` не проверяется ничего, что требует
запуска: построенный путь, эксперимент против драйвера, любое число. Это самая
крупная граница покрытия конвейера, и она обязана идти строкой в каждом таком
прогоне поимённо. Обратная связь — журнал дефектов `docs/review.md`: класс,
который ловят только меряющие проходы, начал всплывать после мерджа — значит
ступень выбирают слишком низко. Плюс сам `basics` обязан сигналить строкой, если
видит, что ступень занижена: он единственный, кто смотрит на дифф целиком на
нижних ступенях.
## Что из этого следует
**С130. Стоимость прохода — это его цена, умноженная на частоту, и вторая
переменная важнее.** [Тема 33](33-review-cost-cut.md) убрала самый дорогой
проход, тема 34 — самый частый. Второе дало больше, хотя снятый проход был
дешевле каждого отдельного `reimpl`.
**С131. Урожайность прохода не отвечает на вопрос, где ему стоять.** Меряющая
пара осталась самой ценной и всё равно уехала вверх: ценность оправдывает
существование прохода, но не его частоту.
**С132. Замена тяжёлого прохода лёгким записывается как сужение, а не как
эквивалент.** `basics` задаёт те же вопросы чтением, и его ответы поэтому слабее
— условия вместо оракулов. Назвать это «покрыли то же дешевле» значит соврать
себе на первом же прогоне.
**С133. Ступень, выбираемая по классу изменения, слепа к объёму.** Правило,
запрещавшее смотреть на размер, защищало от выбора по ощущению важности — и
заодно отправляло трёхстрочную правку и переборку пяти узлов в один профиль.
Признаков нужно два: класс отвечает за обратимость, объём — за цену
разбирательства.
+75
View File
@@ -0,0 +1,75 @@
# 35. Ревизия моделей: переведены двое из девяти, и критерий оказался не тот (2026-08-06)
Сквозной проход по тринадцати уставам с одним вопросом: кого из девяти
`opus`-агентов можно опустить на `sonnet` без потери. Ответ — двоих, и по дороге
выяснилось, что критерий, которым конвейер до сих пор раздавал модели, отвечает
не на тот вопрос.
**Р142. Модель выбирается по цене ошибки, а не по роду прохода.** Прежнее
деление — applicative против generative — раздаёт модели по тому, **откуда**
проход берёт критерий. Но платит проект не за происхождение критерия, а за
разбирательство с находкой. Рабочий признак:
- находка приходит **со ссылкой на записанный источник** (строка спеки, цель в
манифесте, значение в конфиге, номер правила) — её опровержение стоит одного
открытия файла. Дешёвая модель ошибается здесь **проверяемо**;
- находка есть **суждение** («это второй способ», «этот оракул негоден», «эти два
документа противоречат») — опровержение стоит рассуждения, а рассуждение стоит
триажа или человека.
Признак объясняет прежнюю раскладку лучше, чем она сама себя: `gate`, `code` и
`ops` не потому дёшевы, что применяют чек-лист, а потому, что каждая их находка
показывает пальцем на строку.
**Р143. `doc-code-drift``sonnet`.** У него закрытый перечень из восьми
правил, и каждое — пара «факт в документе ↔ команда, которой он проверяется».
Устав прямо запрещает суждение («верность и полноту не проверяешь»), требует
формы «написано X, в коде Y, проверено командой Z» и правила «нечем проверить —
не находка». Ложная находка опровергается **той же командой, которая её
породила**. Это самый чистый случай признака за весь разбор.
**Р144. `task-form``sonnet`.** Семь пронумерованных правил с таблицами форм и
поимённым перечнем подмен. Но решило не это, а потребитель: его находка —
готовая формулировка, которую человек читает и отклоняет командой, а не
оркестратор, который **молча реализует**. Довод, державший `triage` на верхней
модели, здесь не работает вовсе: ошибка стоит строки чтения.
**Р145. `review-specs` рассмотрен и оставлен на `opus` — по причине, обратной
общей.** Он самый частый `opus`-проход конвейера (идёт и в `design`, и на коде,
то есть дважды за задачу), и по устройству он applicative: SKILL.md сам называет
стадию 1 «два applicative-прохода, оба дешёвые», хотя платит за одного `sonnet`,
а за другого `opus`. Расхождение разобрано и закрыто текстом: держит его наверху
направление `code → spec`, где надо заметить **отсутствие** — тихий фолбэк,
самодеятельный дефолт, проглоченную ошибку. Прочие держат `opus` из-за цены
ложных находок, этот — из-за цены пропущенных, а пропуск не оставляет следа
нигде: ни в отчёте, ни в границах покрытия.
**Р146. Остальные шестеро оставлены, и у каждого своя причина.** `adversary` и
`rubric` порождают критерий по построению (второй — с запретом открывать код в
первой фазе). `architecture` — чистое суждение о структуре. `triage` — сток, его
ошибка становится кодом. `doc-consistency` ошибается ровно в ту сторону, которую
дороже всего опровергать: путает «упомянуто в двух местах» с «оба утверждают».
`basics` заведён час назад, половина его вопросов — суждение, и модель у него
выбрана решением оператора в этой же сессии.
**Р147. Это разбор уставов, а не замер, и так и записано.** `calibration.md`
двигает модель инъекцией дефекта; здесь инъекции не было. Двое переведены
потому, что их ошибка **обнаруживается той же проверкой, что породила находку**,
— то есть цена ошибки ограничена сверху независимо от модели. Для остальных
такой границы нет, и трогать их без замера нельзя.
## Что из этого следует
**С134. Дешёвая модель безопасна там, где её ошибку опровергает та же команда,
что породила находку.** Не «где критерий записан» — записанный критерий бывает и
у суждения, и у сверки, а разница между ними в том, чем кончается спор.
**С135. Ошибка бывает двух родов, и модель защищает от разных.** Ложная находка
стоит триажа и видна; пропущенная не стоит ничего сегодня и не видна вовсе.
Проход, у которого дороже второе, держится на верхней модели даже будучи
applicative.
**С136. Потребитель находки — часть её цены.** Одна и та же ошибка стоит строки
чтения, если её читает человек, и разросшегося кода, если её молча реализует
оркестратор. Модель раздаётся с оглядкой на это, а не только на устройство
прохода.
+106
View File
@@ -0,0 +1,106 @@
# 36. Темы ревью: документ проекта стал направлением проверки (2026-08-06)
Замечено при сверке документов канона с составом ступеней: **три документа
остались без читателя ниже `wide`** — `security.md`, `database.md` и `adr/`.
Проект поддерживал их, а на 90% задач их не открывал никто. Причина оказалась не
в переезде проходов, а в том, как описан состав прогона.
**Р148. Тема первична, проход вторичен, и это правило 0 конвейера.** Список тем
нигде не был записан: он существовал побочным продуктом списка проходов. Проход
уезжал в верхнюю ступень — и тема уезжала с ним **беззвучно**: отчёт честно
говорил «`ops` не запускался» и не говорил «эксплуатацию не смотрел никто», а
нужно второе. Теперь прогон описывается таблицей «тема → дом → глубина → кто
закрывает», и таблица есть в каждом отчёте.
**Р149. Тема есть документ, и список тем открытый.** Всё, что проект кладёт в
`docs/`, становится темой ревью; запретить нельзя, разрешения не надо. Не темы
ровно две: `docs/tasks/` и `docs/review.*` (настройка самого конвейера — слой
над темами). Отсюда главное следствие: **`docs/` перестал быть документацией и
стал конфигурацией конвейера.** Проект настраивает проверку тем, что пишет о
себе, а не отдельным файлом настроек, который разошёлся бы с документами.
Ядро — шесть тем: `requirements`, `autotests`, `conventions`, `architecture`,
`security`, `operations`. Их дома канон обещает. Всё сверх — темы проекта, и их
разбирает `basics`: именных проходов конечное число, а тем столько, сколько
заведёт проект, поэтому приёмник обязателен.
**Р150. Тема живёт файлом или каталогом, на выбор проекта.** `docs/security.md`
и `docs/security/` — одно и то же. Прежде форма была задана поимённо
(`conventions`, `research`, `adr` — каталоги, остальные — файлы), и обосновать
это было нечем; заодно в TODO висел открытый вопрос «а если `architecture.md`
разрастётся». Теперь ответ механический: разросся — стал каталогом с
`README.md`, и это не смена версии канона. Обе формы сразу — ошибка, и `docs.py`
её ловит: два дома для одного факта расходятся молча.
**Р151. Ступень выбирает разметчик, а не автор.** Заведён `review-scope`
(`sonnet`), стадия 0, до гейта: находит документы, выводит темы, назначает
глубины, выбирает ступень с обоснованием. Довод сильнее, чем синхронизация
документов: **до сих пор профиль называл тот же оркестратор, который написал
код** — то есть в точке выбора глубины проверки разведённости с автором не было
вовсе, и решала она под давлением «я почти закончил». Вызывающий пайплайн
профиль больше не передаёт.
Право у разметчика симметричное — поднять и понизить, — но обоснование
обязательно всегда, а не только при отступлении от умолчания.
**Р152. Разметчик передаёт адреса, а не пересказ.** Проект однажды уже держал
файл-посредник между документами и проходами (`review-brief.md`) и убрал его:
второй дом расходится с первым и выглядит актуальным. Пересказ в задании — тот
же посредник, живущий один прогон. Исключение одно: **отсутствие дома** — этого
проход сам дёшево не выяснит.
`sonnet` ему хватает потому, что вывод устроен как **список**: каждый файл в
`docs/` обязан попасть в план темой или строкой «не тема, потому что», и план
сверяется с `ls docs/` за секунду. Выбор ступени — суждение, но у него три
независимых корректора: отрицательный тест `quick`, правило «спорный случай
вниз» и сигнал `basics` о заниженной ступени.
**Р153. `quick` и `standard` совпали составом и разошлись глубиной.** Требование
«нижние ступени закрывают все темы, просто не так глубоко» иначе не выполняется:
темы одни и те же, а различать ступени больше нечем. Глубин три и они про способ
доказательства, а не про старательность: **сверка** (открыть дом, открыть дифф,
сравнить), **разбор** (построить сценарий рассуждением), **доказательство**
(прогнать, померить, построить путь). Третья есть только в `wide` — она одна и
требует машины.
Цена принята: это единственное место конвейера, где профиль не выводится из
списка проходов, поэтому глубина объявляется в отчёте наравне со ступенью.
**Р154. `review-code` переписан: технический разбор плюс конвенции.** Обнаружено
по ходу: **никто не читал код как код.** `specs` сверял с требованиями, `basics`
— с отказами окружения, `architecture` — с устройством, а `code` был проходом
только по прозаическим конвенциям и прямо объявлял, что дефекты рантайма и
логики не его. «Здесь ошибка в логике» не говорил никто, и это была самая
крупная дыра конвейера — крупнее любой недосмотренной темы.
Теперь у прохода две половины: девять классов технического дефекта
(необработанная ветка отказа, пустое и нулевое, граница диапазона, перепутанный
операнд, неосвобождённый ресурс, изменение под итерацией, неверно применённый
интерфейс библиотеки, недостижимая ветка, «сделано соседнее») и прежняя сверка с
конвенциями. Модель поднята до `opus` по признаку [темы
35](35-model-revision.md): цена **пропущенной** находки — дефект в проде, и она
не оставляет следа ни в отчёте, ни в границах покрытия.
**Р155. Вопросы проекта переадресованы темам.** В `docs/review.*` было «Вопросы
к проходам» в форме `ops: <вопрос>` — и когда `ops` уехал в `wide`, вопрос
перестал задаваться молча. Стало «Вопросы по темам». Туда же «Недоступно
проверке» — по темам, обоими подразделами.
## Что из этого следует
**С137. Состав, описанный исполнителями, теряет предмет при перестановке
исполнителей.** Список проходов отвечает «кто работал», а нужен ответ «что
проверено». Первое выглядит полным ровно тогда, когда второе неверно.
**С138. Открытый список нуждается в приёмнике, иначе он обещание.** Разрешить
проекту завести свою тему и не назначить, кто её разбирает, — то же, что не
разрешать.
**С139. Регулятор глубины проверки нельзя оставлять в руках автора.** Не потому
что он злонамерен, а потому что давление «я почти закончил» действует всегда и в
одну сторону.
**С140. Дыру в покрытии находят не там, где ищут находки.** Три осиротевших
документа нашлись сверкой канона с составом ступеней, а отсутствие технического
ревью кода — сверкой оптик проходов между собой. Ни то ни другое не всплыло бы
на прогоне: прогон честно сообщал, что все запущенные проходы отработали.
@@ -0,0 +1,35 @@
# 37. `gate` и `autotests` сведены к одному имени (2026-08-07)
Тема звалась `autotests`, закрывающий её проход — `gate`, и на всех трёх
ступенях это была одна и та же клетка таблицы. Одна сущность под двумя именами —
та же ошибка, что и два разных под одним, только тише: она не путает, а
**теряет**. Вопрос проекта в `docs/review.*` адресуется теме; адресованный
проходу — не приезжает никуда, и ровно этот отказ уже случился однажды с `ops`
(тема 36, [Р155](36-review-topics-project-docs.md)).
**Р156. Победило имя темы, а не имя прохода.** Три довода, по убыванию веса:
1. **Тема первична (правило 0), а имена тем — это имена документов.**
`docs/autotests.md` проект напишет: что покрыто, что нарочно нет, где
`testdata`. `docs/gate.md` не напишет никто — гейт это команда, а не предмет.
2. **Слово «гейт» уже занято дважды** — команда проекта и ребро графа («пока гейт
красный, проходы с мнением не идут»). Третье значение сделало бы отчёт нечитаемым:
«гейт красный» и «гейт нашёл» — про разное.
3. **Тема шире гейта.** «Хватает ли проверок» и «чего в гейте намеренно нет» за
пределы красного/зелёного выходят. Назвать целое именем инструмента — тихо его
сузить.
Цена названа честно: `autotests` звучит уже своего содержимого — линт, типы,
сканер уязвимостей тестами не являются. Гасится строкой в уставе: тема — это
«проверено ли машиной», а не «есть ли тесты», и гейт в ней инструмент, а не
граница.
## Что из этого следует
**С141. Тема и проход, совпадающие один в один на всех ступенях, обязаны носить
одно имя.** Пока имён два, у сущности два адреса, а адресуют её по одному — и
какой из двух окажется живым, решает случай.
**С142. Слово, уже значащее что-то в предметной области проекта, нельзя брать
именем роли конвейера.** «Гейт» принадлежит проекту раньше, чем ревью, и спор за
него ревью проигрывает.
+35
View File
@@ -0,0 +1,35 @@
# 38. Шов между плагинами: канон не называет имён проходов (2026-08-07)
Замечено при сведении тем документации с ревьюверами: `av-dev-pm` в шести местах
называл конвейер поимённо — от прозы канона до **вывода `docs.py` пользователю**
(«свои темы проекта: … — их разбирает `review-basics`»). Плагины при этом
раздельные: `av-dev-pm` работает без конвейера, `av-dev-pipeline` — без канона,
поразрядно деградируя.
**Р157. Общий словарь — имена тем и имена ступеней, и только они.** Ими проект
настраивает ревью: вопросы по темам и триггеры профиля. Имён проходов канон не
называет нигде. Направление зависимости при этом несимметрично и это верно:
**конвейер называет документы канона поимённо, потому что он их читатель**, а
обратной ссылки быть не может — документ живёт дольше, чем раскладка проходов.
Заодно вычищены описательные адресации того же класса: «архитектурный проход
судит», «враждебный проход выдумает», «там идут враждебный, эксплуатационный и
архитектурный проходы». Последняя — худшая из них: это утверждение о **составе
ступени**, живущее на стороне, которая о составе не знает.
**Р158. Пример в правиле не должен нарушать само правило.** Объяснение, почему
вопросы адресуются темам, звучало так: «вопрос, адресованный `ops`, перестал
задаваться в тот день, когда `ops` уехал в верхнюю ступень». Правило про
нестабильность имён, иллюстрированное именем. Стало «адресованный проходу» — и
работает даже после того, как проход переименуют.
## Что из этого следует
**С143. Ссылка из вывода скрипта дороже ссылки из прозы.** Устаревшую строку в
документе чинит тот, кто её читает; устаревшее имя в сообщении `docs.py`
доезжает до чужого проекта и там объясняется недоумением.
**С144. Список, который никто не ведёт, честнее списка, который ведут двое.**
Читателей документа не перечисляет ни одна сторона — читатель назначается планом
прогона. Прежняя ссылка на «таблицу читателей» пережила саму таблицу и обещала
то, чего нет, — с той самой правки, которая таблицу и убрала.
+47
View File
@@ -0,0 +1,47 @@
# 39. Спринт без цели — законный случай (2026-08-07)
Цель была обязательной: `sprint start --goal` требовал слаг, `check` считал
ошибкой набор без названной цели, `sprint take` отказывал задаче под чужой
целью. Модель описывала только спринт развития — а спринт бывает под багфикс,
под техдолг, под здоровье проекта. Такой набор собран **по работоспособности, а
не по направлению**, и цели у него нет не по недосмотру.
Обходной путь существовал и был хуже прямого: завести цель-пустышку («Здоровье
проекта») и вешать под неё `fix`-и. Тогда `ROADMAP.md` — документ про то, что
приложение умеет, — обрастает строками про то, что оно не ломается, а тег
`goal:` перестаёт значить направление.
**Р159. Цель у спринта необязательна, но её отсутствие — ответ, а не молчание.**
`sprint start` принимает `--goal <слаг>` **или** `--no-goal`, и голое отсутствие
обоих — отказ с объяснением. Причина в стимуле: цель называет человек, и это
единственный продуктовый вопрос всей сессии. Разреши мы заводить спринт просто
без флага — забытый флаг, лень спросить и осознанное решение стали бы неотличимы
на выходе, а дешевле всего из трёх агенту именно не спрашивать.
**Р160. В спринте без цели цель не проверяется вовсе.** Набор берёт что угодно
готовое к взятию, включая задачи под разными целями: сверять не с чем. Правило
«набор служит одной цели» не ослаблено, оно просто не применяется — целей в
таком наборе не больше одной, их ноль. Взамен машинной проверки остаётся показ
набора человеку до заморозки: у бесцельного спринта это **единственная**
проверка состава, и в скилле это сказано прямо.
**Р161. Признак «спринт идёт» — слаг, а не цель.** Прежде код спрашивал цель и
получал заодно ответ про то, открыт ли спринт; теперь эти вопросы разошлись.
Слаг подходит на роль признака лучше цели по существу: он есть у любого спринта,
потому что без него нечем проставить `sprint:<слаг>`, то есть нечем собрать
урожай. Поле «Цель» в шапке остаётся на месте и у бесцельного набора — пишется
прозой без ссылки: **«цели нет» и «цель потерялась» обязаны различаться**.
## Что из этого следует
**С145. Необязательное поле, которое всё же решают, заводится парой «значение
или явный отказ».** Умолчанием тут был бы не выбор, а его отсутствие — и
отличить его от забывчивости уже не смог бы никто, включая автора.
**С146. Признак «сущность существует» нельзя вешать на её необязательное поле.**
Пока цель была обязательной, `sprint_goal()` отвечал сразу на два вопроса, и это
работало ровно до тех пор, пока второй ответ не понадобился отдельно.
**С147. Снятая проверка называет, что осталось вместо неё.** Цель не проверяется
— значит, за состав отвечают показ человеку и строка доклада; иначе послабление
читается как «здесь можно не думать».
+60
View File
@@ -0,0 +1,60 @@
# 40. Три категории документов: не всякий документ — тема ревью (2026-08-07)
Решение 36 объявило: **каждый документ проекта — тема ревью**. Правило дало
открытый список тем и сделало `docs/` конфигурацией конвейера — это работает и
остаётся. Но оно же оказалось неверным ровно наполовину, и потому вредным
целиком.
Паспорт и схему хранилища ревью читает, но темами они не являются: по ним нельзя
сказать «в этом изменении сделано не так», они задают границу, по которой судит
**чужая** тема. Журнал решений и журнал наблюдений ревью изменения не нужны
вовсе: ADR объясняет прошлое решение, а не предъявляет требование к изменению.
Ломалось это механически. Разметчик, применявший правило буквально, обязан был
либо завести фантомные темы `passport`, `adr`, `database`, `research` и
продублировать ими работу тем `architecture` и `operations`, либо потерять четыре
документа молча. Обе ветки случались; в собственном образце плана разметчика
`docs/passport.md` не попадал ни строкой, а его же обязательная арифметика
покрытия («документов найдено N, все N разнесены») при этом не сходилась.
**Р162. Разрез один и проверяемый: можно ли по документу сказать «в этом
изменении сделано не так».** Отсюда три категории. **Тема** — да, прямо
(`conventions`, `security`, `architecture`, свои документы проекта). **Источник
темы** — нет, но он задаёт границу для чужой темы (`passport`, `database`,
`CLAUDE.md`, `openspec/specs/`). **Процессный документ** — нет, он про то, как
мы работаем (`tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json`).
**Р163. Открыта одна категория из трёх.** `источник` и `процессный` перечислены
поимённо и проектом не пополняются; открыта только `тема`. Прежняя формулировка
«не темы ровно две» противоречила собственной раскладке канона — `.pm.json` был
третьим, и правило-исправление жило в чужом плагине, в коде `docs.py`. Теперь
документ, которого нет в раскладке, — однозначно своя тема проекта, и решать
нечего.
**Р164. «Не судит по нему» и «не открывает» — разные вещи.** `docs/review.*`
проходы читают на каждом прогоне: там вопросы по темам, журнал дефектов, типовые
узлы, типовые ложноположительные. Это чтение конвейером **своей обвязки**, а не
критерия. `adr/`, `research/` и `tasks/` не открывает никто.
**Р165. Цена решения записана, а не подразумевается.** Расхождение изменения с
записанным решением прогоном больше не ловится — это работа сверки документации
между спринтами. Измеренные числа проекта из ревью тоже ушли: проход,
опирающийся на число, обязан **снять его сам, на этом прогоне**, и приложить
команду замера. Обе потери идут обязательными строками в границы покрытия
каждого прогона, и пишет их триаж — не проход, потому что проход о том, чего в
конвейере нет, пожаловаться не может.
## Что из этого следует
**С148. Плоское правило, верное наполовину, хуже двух правил.** Оно не даёт
половине случаев легального ответа, и исполнитель выбирает между двумя плохими
ветками — фантомной сущностью и молчащей потерей. Заметно это становится не на
определении, а на первом же образце вывода.
**С149. Открытым делается одно множество, а не все.** Открытый список ценен тем,
что в него попадает незнакомое; если открыты все категории, незнакомое попадает
в произвольную.
**С150. Отказ читать документ — тоже граница покрытия, и её пишет сток.** Строку
«этого не смотрел никто» некому подать снизу: проход, которого нет, отчёта не
присылает.
+40
View File
@@ -0,0 +1,40 @@
# 41. Разметка задачи: одна величина, посчитанная один раз (2026-08-07)
Разметка была стадией 0 **ревью кода** и платилась на каждом прогоне. Перед ревью
дизайна ту же самую величину — «крупное или незнакомое?» — называл сам пайплайн
задачи, то есть оркестратор, который только что довёл предложение до `propose`.
Одно и то же измерялось дважды, и один из двух раз без разведённости с автором —
ровно в той точке, ради которой разметчик и заведён.
**Р166. Разметка идёт один раз на задачу, сразу после `propose`.** Её план
обслуживает обе стадии ревью: состав ревью дизайна и таблицу тем для ревью кода.
Диффа она не видит — кода ещё нет; размер оценивается по дельта-спекам и перечню
границ задачи.
**Р167. Осей две, ступень — максимум по ним.** **Размер** (малое, среднее,
крупное) — про объём; **сложность** (знакомое, незнакомое) — про то, известна ли
форма решения заранее. Раньше обе были склеены в один вопрос «крупное **или**
незнакомое?»: ответ получался тот же, но разметка не могла сказать «среднее, но
совершенно знакомое» — а это и есть рабочее умолчание.
**Р168. Ступень после кода не пересматривается.** Дифф может выйти крупнее
ожидания — ступень не двинется. Пересмотр означал бы либо второй запуск
разметчика (то, ради устранения чего он и переехал), либо машинный порог,
который на нетипичной задаче срабатывает не туда. Расхождение факта с разметкой
ловит журнал дефектов, постфактум, — так же, как и всякую другую ошибку выбора
ступени.
**Р169. План на диск не пишется.** Файл-план стал бы четвёртым артефактом рядом
с `proposal.md`, `tasks.md` и `design.md`, пережил бы задачу и разошёлся бы с
ней молча. Прервался пайплайн — разметка повторяется; это самый дешёвый его
проход.
## Что из этого следует
**С151. Величина, из которой выводится состав, считается один раз и одним
агентом.** Два места, считающие одно и то же, расходятся; расходятся они молча,
и побеждает то, у которого меньше разведённости с автором.
**С152. Разведённость — свойство момента, а не роли.** Тот же агент, спрошенный
до написания кода и после, даёт разные ответы; переезд по времени сделал больше,
чем сделал бы любой запрет.
@@ -0,0 +1,52 @@
# 42. `quick` стал дешевле `standard` тремя способами (2026-08-07)
`quick` и `standard` совпадали составом (шесть проходов) и различались глубиной
трёх тем: сверка против разбора. На практике это означало один проход, задающий
на один вопрос меньше, и потолок 4 вместо 2. Нижняя ступень не экономила почти
ничего и называлась отдельной ступенью зря.
Отдельно выяснилось, что дешевизна конвейера держалась на двух заявленных
рычагах — узкий вход и потолок находок, — и **оба применялись к одному проходу
из шести**. У `specs` и `code` потолка не было вовсе, а вход `code` включал
чтение дома конвенций «весь и целиком» на каждой задаче.
**Р170. `quick` теряет приёмник тем.** Темы `security`, `operations` и
`architecture` на этой ступени закрывает `code` сверкой с **записанными
инвариантами** `CLAUDE.md`, потолком 1 находка на все три. Это не «глубина ниже»
— это **другой дом темы**, куда более узкий, и в плане он так и называется.
**Р171. Приёмник тем запускается тогда и только тогда, когда ему есть что
принимать.** Правило было в `wide` («нет своих тем проекта — не запускается») и
теперь распространено на `quick`. Совпадение неслучайное: темы ядра `basics`
держит ровно на одной ступени из трёх, а приёмником проектных тем работает на
всех.
**Р172. Вход и потолок применены к каждому проходу с мнением.** На `quick`
`specs` читает только дельта-спеку, `code` — только индекс конвенций. Потолки
напечатаны и раздельны по половинам `code`: 3 технических, 2 конвенционных, 1 по
инвариантам. Раздельность обязательна — конвенционных находок больше по
построению, и в общем списке они вытеснили бы техническую половину, чей пропуск
дороже.
**Р173. Сработавший потолок объявляется.** Проход, срезавший находки, говорит
строкой, сколько осталось за срезом и какого рода. Молчащий срез неотличим от
«больше не нашлось» — тот же класс молчащего пропуска, против которого написан
весь конвейер.
**Р174. Отрицательный тест `quick` стал жёстче, а не мягче.** Вопросы «обратима
ли миграция» и «что с записями новой версии после отката» задавал приёмник тем;
на `quick` его нет. Значит изменение, которое не откатывается обратной правкой,
на `quick` не идёт вовсе — каким бы малым оно ни было.
## Что из этого следует
**С153. Ступень, не дающая экономии, не нужна.** Две ступени, различающиеся
одним вопросом одного прохода, — это одна ступень с шумом в отчёте.
**С154. Рычаг, применённый к одному исполнителю, — не рычаг, а исключение.**
Заявленный механизм экономии проверяется перечислением: к кому он применён и к
кому нет.
**С155. Проход без потолка выдаёт столько находок, сколько нашёл поверхностей.**
Ровно из-за этого был снят проход независимой реализации; тот же механизм
работал у `code` и `specs` и не был замечен, потому что счёт никто не считал.
+31
View File
@@ -0,0 +1,31 @@
# 43. Ревью дизайна тоже растёт ступенями (2026-08-07)
Состав ревью дизайна включался одним условием: `specs` всегда, `rubric` и
`architecture` — вместе, «при крупном или незнакомом». Значит `standard` получал
на предложении ровно один проход, то есть не отличался от `quick` ничем.
**Р175. Три ступени вместо двух: `quick``specs`; `standard` — плюс `rubric`;
`wide` — плюс `architecture` и вопрос автору о трёх формах решения.**
**Р176. Рубрика съехала вниз, архитектура осталась наверху, и это не
симметричная правка.** Они зарабатывают на разном. Рубрика порождает **свойства
узла** и окупается уже на среднем изменении: её выход уезжает приёмочными
критериями в `tasks.md` и работает потом на всей задаче. Архитектура отвечает на
вопрос «не появился ли второй способ», а он на среднем знакомом изменении
отвечается «нет» ещё до запуска — держать её ниже `wide` значит платить за
предсказуемый ответ на каждой задаче.
**Р177. Тривиальность задачи больше не решает состав ревью.** Раньше она решала,
звать ли ревью предложения вовсе; теперь глубину обеих стадий называет ступень,
а тривиальная задача просто получает `quick`. «Пропустить ревью дизайна» и
«пройти его одним самым дешёвым проходом» — разные вещи: сверка дельта-спек
стоит меньше, чем разбор того, что она поймала бы на готовом коде.
## Что из этого следует
**С156. Проходы, включаемые одним условием, стоит разводить по тому, на чём они
зарабатывают.** Общее условие — признак того, что их не сравнивали между собой,
а не того, что они равноценны.
**С157. Средняя ступень обязана отличаться от нижней на обеих стадиях.** Иначе
«рабочее умолчание» отличается от исключения только именем.
+50
View File
@@ -0,0 +1,50 @@
# 44. Метка задачи: одно значение, по которому выбираются все ревьюверы (2026-08-07)
Решения 41–43 развели классификацию на две оси и свели состав обеих стадий ревью
к их максимуму. Значения этого максимума назывались `quick`, `standard`, `wide`,
а сам он — «ступень». Оба имени описывали **ревью**: как глубоко смотрим, на
какой ступеньке идём. Классифицируется же при этом **задача**, и результат
классификации принадлежит ей, а не прогону.
Расхождение не косметическое. Пока величина называлась свойством ревью, её было
естественно пересчитать на каждом прогоне — что конвейер и делал, пока разметка
не переехала к `propose`. Имя тянуло назад к устройству, из которого её только
что вынули.
**Р178. Классификация выдаёт задаче метку: `small`, `medium` или `large`.**
Метка принадлежит задаче, ставится один раз при разметке и дальше только
читается. Все проходы обеих стадий получают её в задании и обязаны напечатать в
границах покрытия.
**Р179. Метка — единственный вход выбора исполнителей.** Ни класс задачи, ни её
тип, ни тривиальность, ни ощущение важности состав больше не определяют. У
конвейера один переключатель, и он напечатан в каждом отчёте.
**Р180. Слово «ступень» удалено, а не оставлено синонимом.** Два имени одной
вещи расходятся — это ровно решение [темы 37](37-gate-and-autotests-one-name.md)
про тему и проход. Метка ordered: `small` < `medium` < `large`, и там, где нужен
порядок, говорится «младшая» и «старшая метка», а не вводится второе
существительное.
**Р181. Метка — не синоним размера, и это записано там, где ошибиться легче
всего.** Совпадают они в одном углу таблицы из трёх: малое **незнакомое**
изменение получает `large`, трогая один узел. Поэтому план печатает три строки —
размер, сложность, метка, — каждую со своим обоснованием, и выводить одну из
другой запрещено. Проход, определивший объём диффа по метке, ошибётся именно на
том случае, ради которого верхняя метка и заведена.
## Что из этого следует
**С158. Имя величины должно называть её носителя, а не потребителя.** «Ступень
ревью» звала пересчитывать себя на каждом прогоне ревью; «метка задачи»
считается там же, где живёт задача.
**С159. Переключатель состава должен быть один и печатный.** Пока их два —
тривиальность и ступень, — состав выводится из пересечения, а пересечение нигде
не напечатано целиком.
**С160. Русские слова для осей, английские для значения.** Оси — суждение и
читаются прозой (`малое`, `знакомое`); метка — идентификатор, который проходы
сравнивают, и потому она английская. Тот же разрез, что «имена файлов
английские, текст русский» в каноне, и он же снимает путаницу «крупное» против
`large`.
@@ -0,0 +1,63 @@
# 45. Корректор метки, доля `small` и корпус оценки (2026-08-07)
Три правки по следам тем
[41](41-task-sizing-once.md)[44](44-task-label-single-value.md), и все три
закрывают дыры, которые эти решения и открыли.
**Р182. Сигнал о заниженной метке переехал в `review-code`.** Он жил в
`review-basics` — единственном месте. А `basics` с меткой `small` не
запускается, если у проекта нет своих тем: значит на типичном проекте задача с
меткой `small` шла **без рантайм-проверки** того, что метка верна. Дыра
появилась ровно вместе с удешевлением `small` и попала в самую вероятную точку
ошибки: занижают туда, где дешевле, а цена занижения там же и выросла — три темы
ядра смотрятся только против инвариантов.
`code` подходит по построению: он идёт при **любой** метке, видит дифф целиком, а
на `small` уже читает инварианты — то есть держит в руках весь материал, из
которого сигнал выводится. У `basics` сигнал остаётся вторым, подтверждающим: он
смотрит оптикой тем и видит то, чего не видно из кода как кода, — что вопросов,
отложенных до `large`, накопилось слишком много. Триаж теперь обязан сказать и
когда сигнала **нет**: «корректор отработал, возражений нет» и «корректор не
запускался» по молчанию неразличимы.
**Р183. У `small` появилась доля, и она сформулирована сравнением, а не
числом.** `small` не должен обгонять `medium`; ориентир — до трети задач.
Проверка нужна именно теперь: пока `quick` и `standard` совпадали составом,
дрейф между ними не стоил ничего, и её не было. Сейчас он стоит трёх тем ядра. У
дрейфа вниз есть стимул, и он назван: метку выбирает не автор, но по описанию,
написанному автором, — занижённое описание даёт занижённую метку без чьего-либо
умысла.
**Р184. Размер оценивается по корпусу из пяти источников, а не по
дельта-спекам.** Разметчик читал `proposal.md` и `tasks.md`, но `design.md` не
открывал вовсе, а метод был описан одной фразой «размер считается по
дельта-спекам». Дельты описывают заказанное **поведение** и молчат об объёме
работы: шесть шагов в двух узлах видны в `tasks.md`, а факт, что форму решения
выбирали из нескольких, — только в `design.md`. Каждый источник получил свою
строку по каждой оси, и каждая цифра в обосновании обязана быть привязана к
источнику поимённо.
Отсюда два правила, которых раньше не было. **Расхождение источников по объёму
разрешается в пользу большего** — и это не «спорное решается вниз»: то правило
разрешает ничью при равных данных, а здесь один источник просто видел больше.
**Само расхождение — довод за `незнакомое`:** если о задаче написано так, что
источники не сходятся в объёме, форму решения по ней не знают. Отсутствие
`design.md` у нетривиальной задачи читается так же — «форму знали заранее» ничем
не подтверждено.
## Что из этого следует
**С161. Корректор обязан идти чаще, чем корректируемое.** Проверяющий, который
запускается реже проверяемого, оставляет дыру именно там, где выбор был самым
дешёвым, — то есть там, где ошибаются.
**С162. Отсутствие сигнала — тоже сигнал, и его надо печатать.** Молчание
корректора неотличимо от его отсутствия, а решения по ним разные.
**С163. Проверка доли формулируется сравнением, а не порогом.** «Меньше, чем
`medium`» считается по любому журналу и не требует спорить о числе; порог «не
больше 30%» спорен ровно настолько, насколько несопоставимы задачи.
**С164. Оценка по одному источнику — оценка по остатку.** Источники о задаче
отвечают на разные вопросы; пропущенный не ухудшает точность понемногу, а
оставляет ось без данных.
+38
View File
@@ -0,0 +1,38 @@
# 46. Правило выбора метки съехало из скилла в отдельный документ (2026-08-07)
**Р185. У правила выбора метки теперь свой дом — `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. Вложенные вещи не режутся по файлу на вещь.** Разъединённое (типы задач)
режется, вложенное (метки) — нет: разрез вложенного даёт дублирование общей
части, а дублирование намеренно неточное машина не сверит.
+51
View File
@@ -0,0 +1,51 @@
# 47. OpenSpec заводится скиллом, а его конфиг — часть канона (2026-08-07)
**Р186. `init` заводит OpenSpec сам, а не оставляет это человеку.** Каталог
`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил:
`openspec/specs/` объявлен домом темы `requirements`, `config.yaml` описан
абзацем — а заводилось всё руками, и не проверялось ничего. Новый проект выходил
из `init` с полным каноном документов и без каталога, без которого не работают
ни `opsx:propose`, ни ревью дизайна, ни сверка требований. Команда названа
поимённо (`openspec init --tools claude`) в трёх местах — скилле, каноне и
отказе скрипта: отказ без команды заставляет искать её в другом месте.
**Р187. Файл из коробки хуже отсутствующего, и потому проверяется машиной.**
`openspec init` кладёт `config.yaml`, где `context` и `rules`
закомментированный пример на английском. Такой файл читается как настроенный: он
есть, он валиден, имя правильное. Работает он как пустой, и узнаётся это по уже
написанному предложению — на другом языке, с capability по имени пакета, без
единого `SHALL`. `docs.py` проверяет четыре вещи, и каждая про молчащий пробел:
каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об
этом не сообщает); `context` и `rules.specs` не остались примером, а правила
называют `SHALL`; `context` называет `passport` и `CLAUDE.md`.
**Р188. Форма конфига — маршрутизатор, и это разрез, а не пожелание.**
Утверждение, которое можно опровергнуть, открыв другой файл проекта, — пересказ;
строка, которая говорит, какой файл открыть, — ссылка. `context` читается при
порождении **каждого** артефакта, туда удобно дописать «чтобы агент знал», и
именно поэтому в нём заводятся вторые дома инвариантов, конвенций, гейта и
правил ревью. Машина этот разрез не проверяет — отличить ссылку от пересказа она
не умеет, — и он отдан `doc-consistency` отдельным абзацем правила «один факт —
один дом», с `config.yaml`, добавленным ему во вход.
**Обязательными сделаны ровно два адреса — паспорт и `CLAUDE.md`.** Причина в
порядке работы: предложение пишется **до** того, как кто-либо откроет `docs/`, и
без этих двух строк его пишут, не зная ни границы домена, ни инвариантов.
Длинный список адресов превратил бы `context` во второй дом ровно тем способом,
против которого правило и заведено.
**Образец конфига лёг в канон, а не в конвейер**, как планировалось решением
[Р3](01-openspec-status.md). Форма документа принадлежит тому, кто владеет
каноном документов; конвейер её читатель. Иначе `av-dev-pipeline` завёл бы у
себя описание файла, который заводит и проверяет `av-dev-pm`, — тот же шов, что
разбирали, убирая имена проходов из канона.
## Что из этого следует
**С167. Предпосылка, за которой никто не следит, — не предпосылка, а
пожелание.** Если условие названо обязательным, его должен кто-то заводить и
кто-то проверять; иначе оно живёт ровно до первого проекта, где о нём забыли.
**С168. Заполненная форма и заполненный смысл — разные вещи, и первая маскирует
вторую.** Файл на месте, валиден, с правильным именем — и пуст по существу: это
худший вид пробела, потому что выглядит он как его отсутствие.
@@ -0,0 +1,43 @@
# 48. Форма чужого инструмента держится опросом инструмента, а не памятью (2026-08-07)
**Р189. Схема и артефакты OpenSpec записаны в скрипте как слепок, и у слепка
есть сторож.** Проверка формы `config.yaml` знает имя схемы и перечень
артефактов (`proposal`, `specs`, `design`, `tasks`). Это не наше решение, а
состояние чужого инструмента: OpenSpec переименует артефакт — правила под
прежним именем перестанут применяться, конфиг останется выглядеть написанным, а
канон продолжит требовать прежнее. Все три стороны при этом молчат.
Сторожем сделано сравнение версий: `check` спрашивает `openspec --version`
(десятые доли секунды) и сравнивает `major.minor` с той, на которой форма
сверялась. Разошлось — **замечание**, не отказ, с именем команды, которая
перепроверяет. Патч-версия в сравнение не берётся намеренно: формы она не
меняет, а нагоняй на каждый багфикс приучает пролистывать весь блок.
**Р190. Перепроверка спрашивает инструмент, а не нас.** `docs.py openspec-form`
берёт `openspec templates --json` — перечень артефактов текущей схемы — и
печатает, что разошлось с константами. Дорогой вызов вынесен из `check`
сознательно: он стоит втрое дороже опроса версии, а ответ меняется только вместе
с версией. Дешёвая проверка служит **воротами** дорогой, и дорогая не ржавеет,
потому что зовут её не по памяти.
**Чинится расхождение в плагине, а не в проекте, и это сказано в трёх местах.**
Проект в такой ситуации не виноват и починить ничего не может: у него нет ни
констант скрипта, ни скелета, ни журнала версий канона. Замечание поэтому
адресовано владельцу плагина, а команда печатает три адреса правки списком.
**Заодно поймано ложное срабатывание на живом конфиге.** Первый вариант искал
ключи `rules` отступом по всему файлу и нашёл их внутри литерального блока
`context: |`: строки «Language: Russian» и «av-dev-pm:review-pipeline» выглядят
ключами. Проверка теперь идёт от строки `rules:` до следующего ключа нулевой
колонки. Правило, краснеющее на правде, хуже отсутствующего — его перестают
читать целиком.
## Что из этого следует
**С169. Знание о чужом инструменте, записанное у себя, — это слепок с датой.**
Он законен, пока рядом стоит тот, кто заметит, что дата протухла; без сторожа он
превращается в уверенное враньё.
**С170. Дешёвая проверка как ворота дорогой.** Опрос версии стоит копейки и
точно говорит, могла ли измениться дорогая величина. Так дорогая проверка
остаётся редкой и при этом не забытой.
@@ -0,0 +1,38 @@
# 49. Дом общего правила вышел из плагина (2026-08-09)
**Р191. Язык уехал в `shared/`, потому что общее правило не может принадлежать
половине.** Дом языка лежал в `av-dev-pm/skills/canon/references/language.md`
внутри одного скилла одного плагина. Пока плагин был один, это читалось как «дом
рядом с главным потребителем». Разделение на самодостаточные `docs` и `tasks`
превращает то же место в утверждение, что язык принадлежит канону: плагин задач,
поставленный без канона, потерял бы правила письма вместе с ним. Дом переехал в
`shared/` и не принадлежит ни одному плагину, а плагины везут дословные копии.
Самодостаточность держится **копией, а не ссылкой**: `shared/` нужен этому
репозиторию, а не установленному плагину.
**Р192. Устав вычитки стал копией целиком, а не четырьмя таблицами из десяти.**
`doc-wording` копировал из дома англицизмы, словарь, жаргон и порог правки —
четыре блока; девять правил он излагал своими словами, и эти слова с домом никто
не сверял. Там дрейф и копился молча: в доме правило «одна мысль — одно
предложение» требовало выносить придаточное, в уставе — не резать причинную
связь, и каждая версия выглядела полной. Теперь блок один, `язык-правила`, и
берётся он целиком. Условие переезда: текст правил написан безлично, а всё,
обращённое к проходу («пиши так-то», «про это молчи»), вынесено из блока в свой
раздел устава. **Правило принадлежит дому, способ доложить о нём — уставу.**
**Р193. `порог-правки` остался отдельным блоком, и это следствие разметки, а не
вкуса.** Его берёт `task-form`, который правил языка не проверяет вовсе.
Вложенных блоков `copies.py` не знает — лежи порог внутри `язык-правила`,
забрать его отдельно было бы нечем, и `task-form` вёз бы весь устав чужого
прохода. Разрез домов идёт **по потребителю, а не по теме**: три блока вместо
одного стоят двух лишних маркеров и снимают ложную зависимость.
## Что из этого следует
**С171. Общее правило не хранится внутри одного из тех, кто им пользуется.**
Пока пользователь один, дом рядом с ним выглядит удобством; со вторым
пользователем то же место начинает утверждать, что правило принадлежит первому.
**С172. Пересказ своими словами — это копия, которую никто не сверяет.** Блок,
взятый целиком, читается дороже, но расхождение в нём ловит машина; сокращённое
изложение экономит строки и платит молчаливым дрейфом.
+39
View File
@@ -0,0 +1,39 @@
# 50. Вычитка раздвоилась по плагину, а не по правилу (2026-08-09)
**Р194. Решение ППП отменено, и отменено не по своей оси.** ППП говорило: агент
называется `doc-wording`, а не `task-wording`, потому что правила языка
относятся ко всем проектным текстам — документам канона, решениям ADR, запискам
разведки, — а не к одним задачам. Утверждение верно и сегодня; оно и есть
причина, по которой правила уехали в `shared/`. Но из общности **правила** не
следует общность **прохода**: `docs` и `tasks` расходятся самодостаточными
плагинами, а самодостаточный плагин не может зависеть от агента соседа. Проходов
теперь два, `doc-wording` и `task-wording`, и разведены они **по охвату**
впервые в этом репозитории: и `task-form` против вычитки, и `doc-consistency`
против `doc-code-drift` разведены по глубине.
**Р195. Разрез по охвату дублирует устав, и потому весь общий текст стал
домом.** Два прохода судят по одним и тем же девяти правилам; отличаются они
входом, соседями по границе и тем, чем подставляется находка — командой `edit` у
задач, редактором у документов. Написать уставы порознь значило бы завести ровно
тот дрейф, который днём раньше нашёлся внутри самого `doc-wording`. Общими
домами стали `язык-правила`, `порог-правки` и новый `вычитка-доклад` — форма
находки и границы покрытия. Копий в каждом уставе 151 строка, своего непустого
текста — 61 у `doc-wording` и 75 у `task-wording`, и это ровно то, чем проходы
отличаются: вход, соседи, машинная проверка, способ подстановки.
**Р196. `вычитка-доклад` — контракт прохода, а не правило языка, и лежит он всё
равно в `shared/language.md`.** Заводить под пятнадцать строк отдельный файл
дороже, чем назвать раздел честно. Признак дома здесь не тема, а **число
потребителей больше одного при обязательной дословности**: разойдись два прохода
формой доклада, зовущий скилл разбирал бы два формата вместо одного.
## Что из этого следует
**С173. Общность правила и общность исполнителя — разные оси.** Правило бывает
одно на всех и при этом требует по исполнителю на упаковку: правило принадлежит
предметной области, исполнитель — тому, кто его поставляет.
**С174. Разрез по охвату обязан быть оплачен домом.** Разделение по глубине даёт
два разных текста и держится само; разделение по охвату даёт два одинаковых, и
без помеченной копии они разъезжаются — тем вернее, что каждый по отдельности
выглядит осмысленным.
+40
View File
@@ -0,0 +1,40 @@
# 51. av-dev-pm расколот: владение пошло по тому, что ставится порознь (2026-08-09)
**Р197. Один плагин владел двумя вещами, и это мешало обеим.** `av-dev-pm`
держал документацию проекта и учёт работ. Пока владелец был один, сцепки
выглядели удобством: `docs.py` требовал `docs/tasks/` и звал внутрь `tasks.py`
подпроцессом, настройки задач лежали ключом в `docs/.pm.json`, язык проектных
текстов — внутри скилла `canon`. Каждая из трёх на расколе оказалась не
удобством, а утверждением, что половина принадлежит другой половине. Теперь
плагина два, `av-dev-docs` и `av-dev-tasks`, и каждый ставится сам по себе.
**Р198. Самодостаточность держится копией, а не ссылкой.** Ссылка в дерево
соседнего плагина работает ровно до того момента, когда сосед не установлен, — а
это и есть тот случай, ради которого раскол делался. Поэтому все относительные
ссылки, пересекшие границу, сняты: вместо них имя скилла через пространство имён
и оговорка, что вызов может не разрешиться. То, что нужно обоим **дословно**,
стало общим домом в `shared/`: язык проектных текстов и словарь «Сопровождение и
эксплуатация». Второй выбран не по теме, а по числу владельцев — его делят
роадмап, `architecture.md` и тема ревью `operations`, то есть три плагина, и ни
один им не владеет. Три перечня «чем держат проект» уже разъезжались молча.
**Р199. OpenSpec отдан тому, кто им работает, а не тому, кто о нём написал.**
Версия 7 канона объявила `openspec/` своим слотом, и разрез вышел не по
владению: без каталога не запускается конвейер, а не канон. Заведение и форма
файла уехали в скилл `av-dev-pipeline:openspec`, отсутствие каталога стало для
`docs.py` неприменимостью вместо отказа. **Остаток назван, а не замолчан:**
проверка формы и сторож версии пока остались в скрипте канона, потому что своего
скрипта у конвейера нет ни одного, — то есть у файла сейчас два плагина, один
заводит, другой проверяет. Это записано и в журнале версий как временное
состояние.
## Что из этого следует
**С175. Сцепка внутри одного владельца не видна, пока владелец один.** Она
выглядит удобством ровно до раскола и обнаруживается не рассуждением, а попыткой
поставить половину отдельно. Отсюда и порядок работ: сперва разнести, потом
чинить то, что перестало сходиться.
**С176. Разрез владения идёт по тому, кто инструментом пользуется, а не по тому,
кто о нём написал.** Канон описывал OpenSpec подробнее всех и потому казался его
владельцем; работает по нему конвейер, и слот принадлежит конвейеру.
+25
View File
@@ -0,0 +1,25 @@
# 52. Валидатор поехал за файлом: у конвейера появился свой скрипт (2026-08-09)
**Р200. Названный остаток закрыт, и закрыт он ценой первого скрипта в
конвейере.** Решение 51 отдало OpenSpec конвейеру и честно оставило хвост:
проверка формы `config.yaml` и сторож версии остались в `docs.py`, потому что
своего скрипта у пайплайна не было ни одного. Хвост оказался не косметическим —
это ровно то состояние, против которого написан весь канон: **у файла два
владельца, один заводит, другой проверяет**, и разойтись они могут молча. 252
строки переехали в `av-dev-pipeline/skills/openspec/scripts/openspec.py`; в
`docs.py` от темы не осталось ни константы.
**Переезд оплатился сразу, и не тем, чего ждали.** Прежняя проверка требовала,
чтобы `context` называл `docs/passport.md` и `CLAUDE.md`, **безусловно** — то есть
на проекте без канона документов требовала ссылку на несуществующий файл. Пока
проверка жила в скрипте канона, допущение «канон есть» было незаметным: скрипт
канона запускают там, где канон есть. В скрипте конвейера то же допущение стало
видно на первом же прогоне. Теперь адрес требуется только к документу, который в
проекте есть, а его отсутствие идёт строкой «не проверялось» с названной ценой.
## Что из этого следует
**С177. Неявное допущение видно из другого дома, а не изнутри своего.** «Канон
есть» было верно всюду, где код лежал, и потому не читалось как допущение вовсе.
Переезд — самый дешёвый способ его обнаружить: не разбор, а смена места, из
которого на код смотрят.
+24
View File
@@ -0,0 +1,24 @@
# 53. `canon` и `docs` остаются двумя скиллами (2026-08-09)
**Р201. Слияние отклонено, и довод у него не про объём.** Оба скилла лежат в
одном плагине, и слить их казалось естественным завершением раскола. Мешает
`description`: это не аннотация, а **триггер** — по нему загрузчик решает, звать
ли скилл вообще, и ровно ради его сохранности заведён `frontmatter.py`. Моменты
вызова у этих двух разные. `canon` срабатывает на «проверь документацию»,
«переведи на канон», «пришёл в старый проект»; `docs` — на «задача сделана,
обнови документацию», «заведи ADR», «запиши наблюдение». Одно описание покрывает
оба хуже, чем два покрывают каждое своё, и потеря здесь не в читаемости, а в
том, что скилл перестаёт находиться.
Второй довод — тот же разрез, что репозиторий подтверждал уже трижды:
**раскладка против содержимого**, «где лежит» против «что внутри». Он по
глубине, а такой разрез, в отличие от разреза по охвату ([тема
50](50-wording-split-by-plugin.md)), даёт два разных текста и держится сам, без
помеченных копий.
## Что из этого следует
**С178. Границу между скиллами держит не тема, а момент вызова.** Два текста об
одном предмете живут порознь законно, если зовут их в разные минуты; и наоборот
— один предмет, разрезанный так, что оба куска нужны одновременно, разрезан
неверно.
+96
View File
@@ -0,0 +1,96 @@
# 54. Стык плагинов: правило получило дом, адреса остались у владельцев (2026-08-09)
**Р202. Вопрос пришёл с другой стороны: ревью опирается на документы проекта, но
не должно жёстко предполагать, где файл лежит; напрашивалось оглавление адресов
и сводка возможностей скиллов в `CLAUDE.md` проекта.** Отклонено и то и другое,
но не потому, что проблемы нет.
**Оглавление адресов — второй дом раскладки.** Канон жёсток намеренно: пути
фиксированы, проект подгоняется под них, и цена этого записана в самом каноне.
Указатель в `CLAUDE.md` отменяет ровно эту цену — раскладка получает второе
описание, и разойдутся они молча. Здесь молчание особенно дорогое: прогон ревью
умеет **честно деградировать**, и протухший адрес попадает прямо в эту машинерию —
файл не открылся, в границах покрытия появляется строка «документа в проекте
нет», и отчёт выглядит добросовестным. Прямой путь в той же ситуации ломается
громче.
**Сводка возможностей — второй дом описаний.** `description` во фронтматтере это
триггер, по нему скилл и выбирается; переписанная руками сводка тех же описаний
не сверяется ничем.
**Настоящий пробел был в другом, и он измерен.** Правило обращения к соседнему
плагину стояло в пяти местах в пяти редакциях:
| Где стояло | Довод | Ветка «не разрешился» |
| --- | --- | --- |
| `task-pipeline` | устаревшая проектная копия | нет |
| `task-batch` | то же | нет |
| `review-pipeline` | вшито в пункт про удаление проектных копий | нет |
| `openspec` | путём в чужое дерево — никогда | есть |
| `canon` | — | есть |
Два разных довода, и ни в одном месте не было обоих; три места из пяти молчали о
том, что делать при неразрешившемся вызове, — то есть о единственном, ради чего
правило написано. Плюс невысказанный инвариант: `$CLAUDE_PLUGIN_ROOT` ведёт
только в свой плагин, употреблён двадцать раз и нигде не оговорён — а именно он
соблазняет дописать `/../av-dev-docs/`.
**Сделано:** дом `shared/plugin-boundary.md`, блок `граница-плагинов`, семь
помеченных копий — четыре скилла конвейера и три скилла канона. В дом вошли
полное имя, запрет пути в чужое дерево, ветка «не разрешился» с обязанностью
доклада и признак присутствия по заведённому соседом файлу.
**Разрез, по которому дом наполнялся: правило общее, последствие местное.** «Нет
`av-dev-tasks` — учёт остаётся владельцу» знает только конвейер; «нет конвейера —
`docs.py` о каталоге `openspec/` молчит» знает только канон. Держи дом
последствия — он знал бы наперечёт всех своих потребителей и стал бы вторым
каноном.
**Адреса при этом наружу не поехали.** У них владелец есть: раскладку `docs/`
держит канон, каталог задач — плагин задач. `shared/` заводится **только для
фактов без владельца**; чужое с владельцем остаётся дома, а сходимость упоминаний
в чужих деревьях проверяет машина — `scripts/addresses.py`, тем же заходом.
**Что выяснилось при написании чекера: судить незнакомое нельзя.** Первый прогон
дал шесть находок, и три из них были не дрейфом, а свойством канона: `docs/**`
шаблон, а `docs/accessibility.md` в двух местах — пример **своей темы проекта**,
которую канон разрешает заводить произвольно. Список тем открытый, значит
незнакомое имя опровергнуть нечем, и проверка «есть ли такой документ у
владельца» ловила бы законное. Переименование при этом ловится точно и по другому
основанию: канон, убирая слот, кладёт его в карту переездов `RETIRED` — она и
есть перечень запрещённого. Рядом одна догадка: имя, почти совпавшее с
каноническим, читается как опечатка. Порог замерен по репозиторию — законные
имена дают до 0.64, опечатки от 0.91, и между ними пусто.
Четвёртая находка оказалась настоящей: `REMAINING.md` иллюстрировал смысловой
дубль адресом `docs/specs/recognition.md` — слотом, упразднённым в версии 1
канона, то есть при десяти нынешних.
Пример, который сам протух, — ровно то, ради чего чекер и писался.
## Что из этого следует
**С179. Механизм честной деградации превращает протухший адрес в правдоподобный
доклад.** Там, где отсутствие источника — законный исход с названной ценой,
ошибка адреса неотличима от этого исхода. Значит адрес в таком месте обязан
сверяться машиной, а не аккуратностью: единственная альтернатива — ломаться
громко, а именно её деградация и убирает.
**С180. `shared/` — для фактов без владельца, и только.** У адресов владелец
есть, и вынести их наружу значило бы отобрать у него его же предмет. Признак
верного дома не «нужно нескольким», а «никому из них не принадлежит».
**С181. Общее правило и его последствия живут порознь.** Правило можно вынести в
дом, последствие — нет: оно знает про место, а место про правило знать не
обязано. Дом, вобравший последствия, становится реестром потребителей и
устаревает быстрее их всех.
**С182. Проверять надо запрещённое, а не незнакомое, когда словарь открыт.**
Открытый список делает «нет такого имени» неопровержимым, и проверка на
принадлежность перечню начинает ловить законное. Ловится ровно то, что владелец
объявил упразднённым: карта переездов — не побочный артефакт миграции, а
перечень запрещённого, и стоит она ровно там, где нужна.
**С183. Замер порога записывается рядом с порогом.** Число, выбранное на глаз,
через месяц неотличимо от подогнанного под один случай. Обе стороны разрыва
названы (0.64 и 0.91) — и видно не только, что порог верен, но и насколько он не
на грани.
@@ -0,0 +1,71 @@
# 55. `task-pipeline` стал `resolve`: два плановых стопа вместо полной автономии (2026-08-09)
**Р203. Автоматическое решение задач агентом признано утопией — «работает, но
работает плохо», — и хуже того, автор перестал ориентироваться в собственном
процессе.** Отсюда разворот: задачи решаются по одной, а в цикл возвращается
человек. `task-batch` удалён целиком; `task-pipeline` переписан в `resolve`.
**Прежняя доктрина звучала «умолчание — делать, а не спрашивать», и она не
отменена, а ограничена.** Полностью автономный прогон плох не тем, что ошибается,
а тем, что ошибку видно на готовом коде: развилка, стоившая бы абзаца до
`propose`, стоит переписывания после `apply`. Постоянное же согласование
возвращает ту цену, ради ухода от которой пайплайн и писался. Разрез поэтому по
**месту**, а не по важности решения: развилка, найденная до ближайшего чекпоинта,
копится в него; найденная после последнего — по-прежнему уходит вопросом в запись,
и задача доводится в объявленных границах.
**Чекпоинтов два, и второй обязателен всегда.**
- **«варианты»** — у исследовательской задачи, до первого требования. Признак
ветки не объём работы, а **отсутствие одного очевидного способа решения**:
обсуждать варианты после `propose` поздно, предложение уже воплотило один из
них, и разговор пойдёт не о выборе, а о переделке. Форма ограничена сверху —
2–4 варианта: больше четырёх человек не сравнивает, а признаёт неспособность
сравнить и просит рекомендацию.
- **«объяснение»** — у всякой задачи, **после** ревью дизайна. Порядок обоснован:
человек читает то, что уже просеяла машина, и не тратит внимание на выловимое
`review-specs`. Внимание здесь самый дорогой ресурс процесса.
**Объяснение не стало новым артефактом, и это главная правка первоначального
замысла.** Задумывалось отдельным разделом в `design.md`; при разборе оказалось,
что оно там было бы **третьим домом** одного и того же: в `proposal.md` уже есть
`## Why` («в чём проблема»), в `design.md` — рассмотренные варианты. Поэтому
объяснение **собирается из двух существующих артефактов**, а требование к их
форме уехало в `openspec/config.yaml``rules.proposal` и `rules.design`. Это
единственное место, применяющееся **в момент написания**, а не после.
Побочная выгода: `design.md` с названными причинами отказа — половина будущего
ADR, а промоут ADR читает именно архивный `design.md`.
**Закрыт вопрос, висевший в плане открытым: что делает автоматический участок,
когда ревью кода спорит с одобренным дизайном.** Признак проверяемый —
**меняются ли дельта-спеки**. Не меняются: находка внутри дизайна, дожимается
сама. Меняются: решение стало другим, а одобрено было прежнее — разметка
пересчитывается (правило уже было) и **чекпоинт повторяется**. Чекпоинт, который
можно обойти находкой ревью, не значит ничего, и хуже того — человек уверен, что
одобрил именно то, что уехало в коммит.
**Удаление `task-batch` обошлось дороже своего каталога.** На нём держались:
третий режим `review-specs` (стык после слияния) вместе с исключением «живого
change нет — берём источником актуальные спеки»; единственное исключение из
правила `review-triage` «плана нет — не запускаюсь»; и обоснование имени основной
ветки в каноне — «в неё вливает батч». Первые два — послабления, существовавшие
только ради батча, и с ним они исчезли, сделав оба правила строже.
## Что из этого следует
**С184. Автономность ограничивается местом, а не важностью решения.**
«Спрашивать о важном» неисполнимо: важность оценивает тот же, кто хочет
закончить. «Копить до ближайшего планового стопа» проверяемо и не требует
суждения.
**С185. Чекпоинт ставится после машинной проверки, а не до неё.** Внимание
человека тратится только на то, чего машина не ловит; порядок наоборот сжигает
его на выловимом и обесценивает саму остановку.
**С186. Объяснение для человека не заводит своего артефакта.** Если оно
собирается из уже существующих, оно не может с ними разойтись; отдельный текст
«то же, но понятнее» — третий дом, и расходится он молча.
**С187. Послабление, введённое ради одного потребителя, уходит вместе с ним.**
Исключение переживает своего заказчика и выглядит общим правилом; удаляя
потребителя, ищи его исключения — они и есть настоящий хвост.
+36
View File
@@ -0,0 +1,36 @@
# 56. `av-dev-pipeline``av-dev-code`, `review-pipeline``review` (2026-08-09)
**Р204. Имя описывало устройство, а не предмет.** «Пайплайн» говорит, что внутри
конвейер, — а плагин занят кодом по задачам, и после появления чекпоинтов он уже
не конвейер в чистом виде: между остановками автоматика, на остановках разговор.
**Набор имён стал параллельным, и это довод сам по себе:** `docs` / `tasks` /
`code` / `git` — каждое называет **материал**, которым плагин занят. Прежнее имя
выбивалось: три существительных и одна метафора устройства. По той же причине
отвергнут `av-dev-solve` — глагол в ряду существительных, плюс заикание в главном
вызове (`solve:resolve`), — и `av-dev-work`: «работы» в этом репозитории уже
значат конкретное (цели и задачи роадмапа, секция «Сопровождение»), и имя начало
бы спорить со словарём.
Заодно `review-pipeline` стал `review`: слово «пайплайн» ушло из плагина целиком,
а не наполовину, и скиллы выровнялись — `resolve` / `review` / `openspec`.
**Журнал версий канона переписан вместе со всеми, и это не нарушение правила «не
переписываем задним числом».** Разрез проходит не по типу файла, а по типу
высказывания. Наблюдение и причина — неприкосновенны: их правка есть
фальсификация. **Предписание и адрес обязаны оставаться исполнимыми**: запись
версии 10 велит «проверить, что плагин `av-dev-pipeline` установлен», и проект,
дошедший до неё, выполнит невыполнимое. Журнал решений при этом не тронут — в нём
нет предписаний проекту, только записи о принятых решениях; там прежнее имя
верно, потому что описывает состояние на дату записи.
## Что из этого следует
**С188. Имя плагина называет материал, а не устройство.** Устройство меняется —
конвейер обзавёлся остановками, — а материал остаётся. Имя по устройству
протухает первым и при этом выглядит осмысленным.
**С189. Журнал не переписывается в наблюдениях и обязан оставаться исполнимым в
предписаниях.** Правило «не задним числом» защищает от подделки фактов, а не от
починки инструкций: инструкция, ссылающаяся на несуществующее, — не
свидетельство эпохи, а поломка с отложенным сроком.
+58
View File
@@ -0,0 +1,58 @@
# 57. Спринты отменены: приоритет стал порядком строк, `session` стал `groom` (2026-08-09)
**Р205. Спринт отвечал на вопрос «что делать дальше» замороженным набором, а
между наборами на этот вопрос не отвечал никто.** Процесс идёт задача за
задачей, и набор перестал что-либо удерживать: он не синхронизировал (некого),
не ограничивал по времени (тайм-бокс не брали) и не защищал от врывания
(врывалось ровно два класса, оба назывались правилом). Осталась цена —
обязанность собрать, показать, заморозить и распустить.
**Приоритет вернулся, и вернулся туда, где ему место.** Прежнее правило «порядка
нет, есть цель» было обосновано **набором спринта**, и с ним потеряло опору.
Приоритет — свойство очереди, а не задачи, поэтому его дом **индекс**: то же
исключение из правила «файл — источник истины», что уже было у «в каком индексе
лежит запись». Числом в файле он быть не мог — два соседних файла смогли бы
утверждать одно место, а строка индекса противоречить обоим.
**Гейт готовности стоял на `sprint take` и чуть не исчез вместе с ним.** Это было
единственное место, где запись судили целиком: тип, цель у `feature`, пустой
раздел вопросов, схема типа. Без спринта момента не осталось бы вовсе, а узнают
о недописанной задаче на приёмке, когда сверять уже не с чем. Момент назвали
заново — команда `tasks.py ready <слаг>`, и зовёт её тот, кто берёт задачу.
Отказ там **код 1, а не 2**: запись не дописана — это рабочая ситуация, а не
ошибка употребления.
**`session` стал `groom`, и предмет сузился до двух вопросов** — что сейчас
самое важное и что перестало быть важным. Из четырёх шагов прежней сессии выжили
два (вопросы, переоценка порциями), один заменился (расстановка очереди вместо
набора спринта), два выпали:
- **приёмка закрытых задач** — грумингу не по предмету. Ритуала у неё больше нет,
остаётся `reopen` по требованию. Цена названа прямо: приёмка происходит только
тогда, когда что-то уже бросилось в глаза;
- **разбор процесса** — его якорем был прошедший спринт. Вместе с ним из скилла
ушёл прямой вызов агентов `doc-consistency` и `doc-code-drift`, и это **не
потеря, а починка**: агенты принадлежат `av-dev-docs`, и груминг звал их мимо
правила обращения к соседу, без ветки «плагина нет». Груминг теперь только
**называет повод** сверить канон, а когда их звать — решает их владелец.
**Побочно найдено:** `canon.md` — дом определения канона — объявлял себя версией
7, когда скрипт шёл на 11. Пять версий дом врал о себе, и не заметил никто:
машина сверяет версию проекта с константой скрипта, а прозу в заголовке не
читает.
## Что из этого следует
**С190. Правило, обоснованное механикой, умирает вместе с ней — и надо
проверять, что вопрос умер тоже.** «Порядка нет» держалось на наборе спринта;
набор ушёл, а вопрос «что делать дальше» остался и повис без ответа. Снимая
механику, ищи не только то, что на ней стояло, но и то, на что она отвечала.
**С191. Гейт живёт в моменте, а не в команде.** Проверка готовности была
свойством `sprint take` — и была бы потеряна как деталь удаляемой команды.
Момент «запись впервые судят целиком» существует независимо от того, чем он
назван, и переезжает вместе с процессом.
**С192. Версия в прозе, которую не читает машина, протухает молча.** Дом канона
назвал себя версией 7 при текущей 11: сверка шла по константе скрипта, а
заголовок документа не сверял никто.
+39
View File
@@ -0,0 +1,39 @@
# 58. Судьи документов получили свой скилл — `healthcheck` (2026-08-09)
**Р206. Момент вызова был свойством чужого ритуала и исчез вместе с ним.**
`doc-consistency` и `doc-code-drift` звались шагом сессии между спринтами.
Сессия стала грумингом, груминг судит задачи, а не документы, и звать чужих
агентов он не вправе — они живут в `av-dev-docs`. На живом проекте их не звал бы
**никто**, кроме разовых `adopt` и `upgrade`.
Чинить это возвратом вызова в груминг было нельзя: это ровно то нарушение
границы, которое там и обнаружилось (вызов агента чужого плагина по имени, без
ветки «плагина нет»). Момент нужно было назвать **у владельца** — и оказалось,
что владельца-то у них и нет: `canon` их звал, но владел раскладкой, а не
суждением.
**Скилл `av-dev-docs:healthcheck`.** Предмет — то, чего машина не видит:
разошлись ли документы между собой и с кодом. Разрез с `canon check` проверяемый:
**машина сверяет форму, healthcheck — утверждения.** «Раздел есть» проверит
скрипт; «написано, что зависимость одна, а в манифесте их три» — суждение.
**Почему скилл, а не просто описание агентов.** Триггер у агента и так есть — его
`description`. Но двоим нужна **оркестровка**: позвать обоих на весь канон разом,
передать `doc-code-drift` раздел запретов, разобрать урожай порциями, назвать
границы покрытия и то, кого именно позвал. Этого агент о себе не знает.
**`doc-wording` внутрь не взят, и это разрез, а не забывчивость.** Ему
оркестровка не нужна: он один и работает по названному списку документов. И ритм
другой — он нужен там, где текст только что писали, а не там, где он год лежал.
Скилл, собравший всех троих «потому что все про документы», склеил бы разные
вопросы под одним вызовом.
## Что из этого следует
**С193. Момент вызова — такая же собственность, как сам инструмент.** Агент, чей
момент назначен чужим ритуалом, теряет его вместе с ритуалом и замолкает
беззвучно: он исправен, его просто никто не зовёт.
**С194. Оркестровка — вот что отличает скилл от агента.** Одному исполнителю с
ясным входом скилл не нужен, его находит описание. Скилл заводят там, где надо
решить, кого звать, что передать, в каком объёме и что делать с результатом.
+58
View File
@@ -0,0 +1,58 @@
# 59. Аудит четырьмя сабагентами: описания отстают от механики молча (2026-08-09)
Реорганизация была объявлена законченной «на бумаге», и я запустил по аудитору на
плагин — консистентность, самостоятельность, интегрируемость. Гейт при этом был
зелёным и остался честен: он проверяет ровно то, что умеет.
Нашлось около полусотни расхождений, и они **одного рода**. Каждый раз я правил
механику — вырезал спринт из скрипта, переименовал скиллы, перенёс судей — и
каждый раз не правил то, что механику **описывает вовне**: скелеты документов,
докстринги скрипта, уставы агентов, манифесты плагинов, README.
Самое дорогое: **скелет `CLAUDE.md` уносил слоты спринта в каждый новый проект**
через две недели после отмены спринтов. Скелет не описывает, а порождает: его
отставание не читается, оно исполняется.
**Механизм приоритета не запускался ни разу.** `--section` у `move` был
обязательным, а все три места, где груминг предписывает расстановку, дают команду
без него — usage error. Скилл написан, прогнан не был, и разницы между рабочим и
бумажным процессом не видно, пока его не запустят.
**Правило границы я же и нарушал.** Восемь дословных копий «путь в дерево чужого
плагина не пишется никогда» — и пять мест, где путь написан, одно из них строкой
выше собственного «пути туда конвейер не выносит».
Отдельно: **у описания плагина было два дома**, и три из четырёх разошлись. Класс
закрыт не дисциплиной, а машиной — `frontmatter.py` теперь сверяет `plugin.json` с
`marketplace.json`, а гейт разбужен на `*.json`.
Правки разобраны четырьмя пропусками по одному сабагенту на пропуск, с проверкой
результата каждого: скелеты и канон, исполнимость учёта задач, границы и стыки,
словарь и манифесты. Скриптовые правки проверены поведением на фикстурах, включая
настоящий git-репозиторий для `reopen`.
**Чего аудит не даёт.** Это было чтение. Ни один скилл по-прежнему не исполнялся
на живом проекте, и находки вроде «чекпоинт вырождается в ритуал» такой проверкой
не берутся по построению.
## Что из этого следует
**С195. Механика проверяется прогоном, описание — только чтением.** Поэтому
после каждой правки механики отстают именно описания, и отстают молча. Меняя
механику, ищи её отражения поимённо: скелеты, докстринги, уставы агентов,
манифесты, README.
**С196. Скелет дороже документа: он не описывает, а порождает.** Отставший
документ врёт одному читателю; отставший скелет уезжает в каждый новый проект и
становится там обязательным.
**С197. Копия правила не заставляет его исполнять.** Правило исполняется там,
где его проверяет машина или чужой глаз; восемь копий на видном месте не
помешали автору нарушить его пятью строками.
**С198. Бумажный процесс неотличим от рабочего, пока его не запустили.**
Команда, которую никто не набрал, может не существовать вовсе — и именно так и
было.
**С199. Два дома у факта расходятся не когда-нибудь, а сразу.** Из четырёх пар
описаний плагина совпала одна — та, которую с момента заведения не правили.
@@ -0,0 +1,68 @@
# 60. Служебный файл зовётся по плагину-владельцу; у задач появилась своя версия формата (2026-08-11)
Файл версии канона звался `docs/.pm.json` — по плагину `av-dev-pm`, который
распался на четыре ещё в [теме 56](56-plugin-and-skill-renames.md) и которого
больше нет. Имя пережило владельца на два месяца и указывало в пустоту: читающий
его искал плагин, о котором в репозитории не осталось ни строки. Переименован в
`docs/.docs.json` записью 13 журнала канона.
**Р207. Правило, которое из этого вынуто и теперь держит все три файла:** имя
служебного файла — имя плагина, который его завёл. `.docs.json` — канон,
`.tasks.json` — задачи, `openspec/config.yaml` — конвейер. По этому же следу
скиллы узнают, что сосед в проекте работал, и правило перестало быть просто
перечнем — оно выводимо.
**Р208. Прежнее имя `docs.py` не читает.** Соблазн «прочитать оба и не мешать
людям» здесь стоит дороже, чем везде: по этому числу `upgrade` решает, какие
записи журнала применять, и два дома для него разъехались бы молча в том самом
месте, где расхождение и вредно. Вместо совместимости — узнавание: `check` видит
файл под старым именем и печатает готовую команду `git mv`.
**Р209. У каталога задач появилась своя версия формата** — ключ `tasks` в
`<каталог задач>/.tasks.json` и свой журнал версий в скилле
`av-dev-tasks:tasks`. До сих пор её не было вовсе, хотя `docs.py` в комментарии
уверенно ссылался на «свою версию формата» соседа: описание опережало механику
ровно так, как описано в следствии [С195](59-four-subagent-audit.md). Формат
задач при этом менялся — записями 8, 11 и 12 чужого журнала.
**Р210. Число именно своё, а не копия канонического.** Плагин ставится в
одиночку: проект, взявший учёт работ без канона документов, каталога `docs/` не
имеет вовсе, а значит не имеет и версии канона — сверять было бы не с чем. Копия
чужого числа в `tasks.py` была бы вторым домом одной версии и разъехалась бы при
первом же обновлении одного плагина без другого.
**Р211. Переезды, случившиеся до появления числа, задним числом в новый журнал
не переписаны.** Версия 1 — это формат на день её появления; что проекту нужно
было пройти до неё, названо шагом «догнать формат по журналу канона» с
поимёнными признаками отставания (каталог в `docs/tasks/`, живой `SPRINT.md`).
Второй перечень тех же шагов разошёлся бы с первым — это ровно та ошибка, из-за
которой план однажды повторял записи версий 3, 4 и 5 построчно.
**Р212. Конфиг задач стал обязательным.** Раньше он заводился только ради имён,
отличных от умолчания, и проект с умолчаниями жил без файла вовсе. Версия — не
настройка, от которой можно отказаться, поэтому `init` и `adopt apply` пишут его
всегда, а `check` требует числа.
Отдельно стоит сказать, чтобы не спутали при чтении журнала: `.docs.json`
однажды уже был отвергнут — решением [Р6](02-project-doc-canon.md), но **как
указатель путей**. Отвергнут был указатель, а не имя; сегодняшний файл путями
проекта не распоряжается, он объявляет версию и называет то немногое, чего из
раскладки не вывести.
## Что из этого следует
**С200. Имя служебного файла — часть границы плагинов, а не деталь.** Оно
называет владельца, и по нему же владельца узнают. Пережившее владельца имя врёт
дважды: указывает на несуществующее и прячет того, кто файл ведёт на самом деле.
**С201. Версия нужна каждому формату, который живёт в чужом репозитории.** Без
числа «приведён ли проект» не имеет определённого ответа, и отставший каталог
выглядит здоровым до первой команды, которая об него споткнётся.
**С202. Своя версия — у своего плагина, всегда.** Общее число на два плагина
переживает ровно до первого проекта, где поставлен один из них.
**С203. Версию двигают руками, и это не слабость проверки.** Число отвечает на
вопрос «по какой записи повышать», а не «сделаны ли шаги по существу». Машина,
приписывающая недостающее число сама, объявляет проект приведённым к формату,
которого никто не проходил.
@@ -0,0 +1,68 @@
# 61. Разведка и решение — два сценария одного скилла, а не два скилла (2026-08-11)
Скилл `resolve` вёл обе работы одной цепочкой: у исследовательской задачи были
свои три шага и свой чекпоинт вариантов, после которого она **вливалась в общую
ветку** и продолжалась кодом. Разведка тем самым была не работой со своим
исходом, а прологом к коду: её ответ оседал в `design.md` будущего change, и
разведка, кончившаяся знанием, документов проекта не касалась вовсе.
Сперва я развёл их на два скилла — `resolve` и `research`, с исходом и стопом с
обеих сторон. Через час работы стало видно, чем это плохо: **классифицировать
задачу приходится человеку до вызова**, а «есть ли у неё очевидный способ
решения» видно только после чтения записи. Разделение переносило самое трудное
суждение туда, где для него меньше всего данных.
**Р213. Точка входа одна, сценария два, выбирает сценарий скилл.** Оба
сценария живут справочниками — `references/solve.md` и `references/research.md`,
а в `SKILL.md` остались вход, развилка и правила, не зависящие от сценария.
Тем же приёмом сложен скилл задач: общая часть в `SKILL.md`, алгоритм каждого
типа в `references/task-*.md`.
**Р214. Порознь и одинаково — это отдельное решение.** Сперва разведка уехала в
справочник, а решение осталось в теле скилла: так вышло само, потому что решение
там уже лежало. Асимметрия читается как старшинство — сценарий в теле выглядит
основным, а сценарий в справочнике оговоркой, — и удерживает шестисотстрочный
файл, который грузится целиком даже ради разведки.
**Р215. Что у разведки появилось своего.** Исход — знание, а не пролог: ответ
уезжает в документы канона (`av-dev-docs:docs`), задачи заводятся и уточняются
(`av-dev-tasks:tasks`), написанное коммитится, запись закрывается. Кода сценарий
не пишет вовсе. OpenSpec ему не нужен — это единственное место скилла, где тот
не предпосылка.
**Р216. Переход между сценариями — событие с названным исходом.** Решение,
упёршееся в незнание способа, останавливается; разведка, выбравшая способ,
доводится до конца и **не переходит в код тем же прогоном** — следующий
запускает человек. Причина не в церемонии: разведка только что переписала
постановку, и брать её в работу тем же заходом значит решать за человека, стоит
ли делать это сейчас, — а это приоритет.
**Р217. Канон пришлось тронуть, и это версия 14.** ADR цитировал только архивный
`design.md`. У решения, принятого разведкой, `design.md` нет по построению —
change по нему не будет никогда, — и такое решение либо не попадало в `adr/`
вовсе, либо попадало сочинённым заново. Теперь источников два, и оба называются
в записи.
## Что из этого следует
**С204. Разделять работы стоит по моменту для человека, а не по роду работы.** У
разведки и решения он разный: варианты обсуждают до первого требования,
объяснение — после ревью дизайна. Всё остальное различие (пишем код или нет) из
этого уже следует.
**С205. Точку входа не разделяют по признаку, который виден только внутри.**
Классификация, требующая прочитать запись, не может быть условием вызова:
человек либо ошибётся, либо прочитает запись сам — и тогда скилл ему не нужен.
**С206. Сценарий в справочнике дешевле скилла.** Скилл стоит описания, границ,
копии правил и своего места в графе вызовов; справочник наследует их у хозяина.
Заводить второй скилл имеет смысл, когда его зовут отдельно, а не когда он
просто длинный.
**С207. Равные сценарии лежат одинаково.** Оставить один в теле скилла, а второй
вынести — значит назначить первому старшинство, которого в замысле нет. Читатель
это старшинство считывает, даже когда о нём не сказано ни слова.
**С208. Работа без своего исхода вырождается в пролог.** Разведка, кончавшаяся
переходом к коду, не имела причины писать в документы: её ответ и так уезжал в
`design.md`. Дом для исхода — вот что делает работу работой.
+44
View File
@@ -0,0 +1,44 @@
# 62. Вычитку зовёт тот, кто правил, а не тот, кто синкал (2026-08-11)
Вычитка документов агентом `doc-wording` была привязана к **синку**: «позови его
последним шагом синка», «ничего не правивший синк агента не зовёт». Сценарий
разведки о себе говорит обратное — «правило принуждённого отрицания здесь не
действует, это не синк», — и при буквальном чтении вычитка не доставалась ему
вовсе: документы правились, а звать было некому. Гейт перед коммитом машинный, он
смотрит раскладку и битые ссылки, а не залог и неизвестный термин.
**Р218. Условие вызова теперь — правка, а не обряд, внутри которого она
случилась.** Признак читается буквально: документы правились — зови, ничего не
правил — не зови. Синк остался самым частым вызывающим, но перестал быть
единственным.
**Р219. У разведки вычитка стала своим шагом, а не оговоркой внутри чужого.**
Она стоит между записью и гейтом, потому что раньше пачка не полна: разведка
правит две вещи сразу — документы канона и записи каталога задач, — и собирается
пачка только к концу пятого шага. После коммита вычитка правила бы уже
закоммиченное.
**Р220. Обе пачки судятся своими проходами.** Документы — `doc-wording`, записи
задач — `task-form`, затем `task-wording`; владеет каждым проходом его плагин, и
разведка их не зовёт напрямую, а просит владеющий скилл.
**Р221. Запрет остался, но только на судей канона.** `doc-consistency` и
`doc-code-drift` идут на весь канон разом и стоят дорого — их момент выбирает
человек через `av-dev-docs:healthcheck`. Смешение этого запрета с вычиткой и
было второй половиной поломки: «агентов по документам на отдельной работе не
зовут» читалось как правило про всех троих.
## Что из этого следует
**С209. Правило, привязанное к названию обряда, не срабатывает у того, кто себя
этим обрядом не считает.** Условие вызова формулируется через наблюдаемое
действие — «правил текст», — а не через имя фазы, внутри которой оно обычно
происходит.
**С210. Дорогая проверка и дешёвая проверка не живут под одним запретом.** Довод
«не зови агентов сам» верен для судей на весь канон и обратен для вычитки
названной пачки; общая формулировка отменяет вторую вместе с первой.
**С211. Шаг, собирающий пачку, стоит после последнего, кто в неё кладёт.**
Вычитка на шаге записи проверила бы половину написанного, а после коммита — уже
историю.
+101
View File
@@ -0,0 +1,101 @@
# 63. Обслуживание — третий сценарий: у цикла SDD там нет входа (2026-08-13)
Задача, не меняющая поведения — тулчейн и сборка, зависимости, гит-хуки, перенос,
чистка, — шла полным циклом решения: `propose`, разметка, ревью дизайна,
чекпоинт, `archive`. Все пять шагов стоят на дельта-спеках, а у типа `chore`
дельта-спек **нет по построению**: тип определён через «наблюдаемое поведение не
меняется». Цикл не урезается ради дешевизны — он остаётся без входа, и change,
заведённый под такую задачу, пуст, а разметчик по нему называет не те темы.
**Р222. Признак сценария — связка из двух проверок, и обе обязательны.** Тип
записи предлагает (`chore`, реже `fix`, чьё исправление возвращает поведение к
уже записанному), отсутствие дельт подтверждает. Тип объявляет автор и может
ошибиться; отсутствие дельт — суждение исполнителя, и в одиночку оно
самообслуживающееся. Разошлись — стоп, а не выбор.
**Р223. Размер признаком не стал намеренно.** «Мелкая задача — короткий путь»
это универсальная лазейка: скилл сам называет занижение метки и обход чекпоинта
самым дешёвым способом «ускориться». Однострочная правка, меняющая поведение,
идёт полным циклом; крупная чистка, не меняющая, — обслуживанием.
**Р224. Планового стопа у сценария нет вовсе.** Чекпоинт объясняет человеку
выбор, а выбора здесь нет: что делать, сказано в записи, критерии приёмки
дешёвые и проверяются командой. Объяснение свелось бы к пересказу задачи её же
автору. Правило необратимого при этом действует полностью и срабатывает чаще,
чем в двух других сценариях: выкладка, токены, хуки и чужие данные — обычное
содержимое задач обслуживания.
**Р225. Ревью идёт фиксированным планом, а разметчик не зовётся.** Обе его оси
не определены: размер он выводит из артефактов change, сложность — из формы
решения, а незнакомая форма ушла в разведку ещё на первом шаге. План —
`autotests` (запуск гейта) и `operations` (сверка), плюс `conventions` с
техническим разбором, когда дифф трогает код, а не только оснастку:
`review-code` — единственный проход, который вообще говорит «здесь ошибка в
логике», и чистка без него проверена лишь на то, что она собирается.
`requirements` и `security` не смотрит никто, и это строка границ покрытия, а не
умолчание.
**Р226. Найденная дельта — не поломка задачи, а обнаружение более широкого
типа.** Стоп поэтому устроен как три шага, а не как доклад об отказе: назвать
тип, которым задача оказалась (`fix` — расходится с заявленным, `feature`
снаружи появляется то, чего не было), объяснить человеку простым языком, что
нашлось, и дать **два** решения — переформулировать запись и решать её процессом
того типа следующим прогоном либо прекратить работу. Третьего решения, «доделать
как обслуживание», нет: оно и есть молчаливое изменение поведения. Тип при этом
исполнитель **предлагает**, а меняет `av-dev-tasks:tasks` и только после ответа
— иначе исполнитель назначает себе другой процесс и другую глубину проверки сам.
**Р227. Триггеры ADR у обслуживания работают стоп-признаком, а не поводом
завести запись.** Список источников ADR канон закрыл двумя — архивный
`design.md` и записка разведки, — и обслуживание не производит ни того ни
другого. Значит дорогой откат, намеренный отказ и пересмотр прежнего решения
означают здесь одно: сценарий выбран неверно, работа идёт разведкой, где решение
проходит чекпоинт вариантов и получает законный источник. Третьего источника
заводить не понадобилось.
**Р228. Синк документации — главный шаг сценария, а не остаток.** Обслуживание
не меняет поведения, значит почти всё, что оно меняет, — документация: команды,
шаги гейта, зависимости, пути, имя ветки, место механизации правила. Ровно эти
факты `doc-code-drift` и сверяет с кодом.
**Р229. Состав гейта сверяется отдельно от цвета, а чем именно — решает
проект.** Красный, ставший зелёным, виден; «проверок стало на две меньше, обе
зелёные» не виден ничем, а это единственное место конвейера, где инструмент
проверяет сам себя. Что считается составом, объявляет проект семантикой гейта в
`CLAUDE.md`; не объявил — строка доклада «сверен только цвет», а не догадка.
**Р230. Своей capability тулчейн не получает, и своего документа канона тоже.**
Граница возможностей и сопровождения проходит по тому, кто наблюдает: гейт
наблюдаем мы, а не пользователь сервиса. Всё, что попало бы в
`docs/toolchain.*`, уже расписано по домам — `CLAUDE.md` (команды, семантика
гейта, запреты, пути), `architecture.*` (зависимости, окружение, выкладка),
`conventions.*` (механизированное), `ROADMAP.md` (работы). Проекту, которому
этого мало, канон уже даёт механизм и без новой строки в раскладке: список тем
открытый, и свой документ заводит свою тему. Цена такой темы названа — она
попадает в план каждого прогона и на большинстве задач молчит.
## Что из этого следует
**С212. Короткий путь оправдан отсутствием входа, а не дешевизной.** «Тут можно
проще» — начало любой деградации; «этому шагу нечего обрабатывать» — проверяемое
утверждение, и проверяется оно тем же признаком, что и переход между сценариями.
**С213. Признак, объявляемый автором, и признак, выводимый исполнителем, держат
друг друга.** Первый один — ошибается в постановке; второй один —
самообслуживающийся. Разрешать расхождение в чью-то пользу нельзя: это стоп.
**С214. Сценарий без стопа для человека требует более жёсткого правила
необратимого, а не более мягкого.** Стопа, на котором «ой» заметили бы, там нет.
**С215. Инструмент, проверяющий сам себя, проверяется по составу, а не по
исходу.** Зелёный гейт после правки гейта не значит ничего.
**С216. Место для нового документа ищется не по теме, а по бездомному факту.**
Тема «тулчейн» звучит убедительно, а фактов без дома за ней не оказалось —
значит документ был бы вторым домом четырёх чужих.
**С217. Работа, переросшая свой тип, останавливается предложением, а не
отказом.** «Здесь нужно менять спеки» перекладывает классификацию на человека в
момент, когда весь материал для неё у исполнителя. Стоп обязан принести
названный тип, объяснение и закрытый список решений — иначе выбор делается
вслепую или не делается вовсе, и работа доезжает до коммита не тем процессом.
+65
View File
@@ -0,0 +1,65 @@
# 64. Три плагина слились в один: раскол платили, а не пользовались (2026-08-13)
Плагинов было три — `av-dev-docs`, `av-dev-tasks`, `av-dev-code`, — и разрез
между ними шёл по признаку «ставится порознь» ([тема
51](51-av-dev-pm-split.md)). Признак был выбран верно, но **посылка под ним не
проверялась**: за всё время подмножество не понадобилось ни разу, а платился
раскол постоянно.
Цена измерена, а не оценена: шестьдесят с лишним вызовов между скиллами при цикле
зависимостей `docs → code → docs`, язык проектных текстов четырьмя помеченными
копиями по 213 строк, словарь сопровождения двумя, правило границы семью,
дюжина веток «плагина нет» — и `copies.py`, заведённый ровно затем, чтобы это
не разъезжалось молча.
**Довод «а вдруг понадобится» снят наблюдением владельца, а не спором.** Ждали
случая «документы и задачи без OpenSpec» — например, ansible-репозиторий. Он
уже покрыт: сценарий обслуживания в `resolve` OpenSpec не требует по
построению, а `docs.py` считает отсутствие `openspec/` неприменимостью, а не
отказом. То есть режим, ради которого держали раскол, работает и в слитом
плагине.
**Слияние оказалось дешевле, чем выглядело, потому что граница была сделана
правильно.** Присутствие соседа узнавалось **следом в проекте**
(`.docs.json`, `.tasks.json`, `openspec/config.yaml`), а не перечнем
установленных плагинов. Значит мягкость поведения держалась на состоянии
проекта и пережила слияние без единой правки логики: сменилась упаковка, а не
механика. Дом правила переехал из `plugin-boundary.md` в `absence.md` и стал
говорить о том, чем он и был на деле, — о частях раскладки, которых может не
быть.
**Р231. Версия стала одна и начинается с 1.** Две версии — канон 14 и формат
задач 1 — двигались порознь, потому что порознь ставились плагины; с одним
плагином два числа означали бы только вопрос, по какому журналу повышать.
Прежние журналы закрыты и не переписаны: адрес, верный на день записи, остаётся
свидетельством. Служебный файл один, `.av-dev.toml` в корне репозитория, и
**формат выбран ради комментариев** — файл живёт в чужом репозитории, и
назначение числа должно читаться из него самого, а не из документации плагина.
Отсюда правило записи: скрипт правит строку, а не переписывает файл.
**Возможность расколоть обратно не потеряна.** Понадобится инфраструктурный
плагин — раскол будет переименованием пространства имён, а не переделкой:
граница по-прежнему держится на следе в проекте. Платить за эту возможность
копиями сегодня незачем.
## Что из этого следует
**С218. Разрез, оправданный сценарием, обязан этот сценарий однажды увидеть.**
«Ставится порознь» — проверяемое утверждение, и проверяется оно не рассуждением,
а тем, поставил ли кто-нибудь половину. Пока не поставил, разрез оплачивается
копиями за случай, которого нет.
**С219. Механика, привязанная к состоянию проекта, переживает перестановку
плагинов; привязанная к их составу — нет.** Это и есть практическая разница
между «узнаём следом» и «узнаём перечнем», и обнаруживается она только на
слиянии или расколе.
**С220. Копия дословного текста — плата за неразрешимый путь, а не за важность
правила.** Путь разрешился — копия становится вторым домом без причины. Остаётся
она там, где текст обязан лежать **внутри промпта**: устав агента, `SKILL.md`
скилла и скелет, уезжающий в проект. Разрез проверяемый: файл, который модель
получает целиком, против файла, за которым она идёт отдельным чтением.
**С221. Формат служебного файла выбирается по тому, кто его читает.** Читает
человек в чужом репозитории через полгода — значит комментарии, значит TOML,
значит построчная правка вместо перезаписи.
+56
View File
@@ -0,0 +1,56 @@
# 65. Перечень осей получил дом; две оси жили без владельца (2026-08-13)
Слияние плагинов не тронуло ни одной идеи процесса — типы записей, метки, три
сценария, категории документов, severity находок остались как были. Но оно
сделало дешёвым то, что раньше было дорого: правило, натянутое между задачами и
ревью, теперь имеет достижимый дом, а не помеченную копию через границу.
**Ось — закрытый перечень значений, по которому что-то ветвится.** Признак
проверяемый, и он отсекает похожее: темы ревью и документы проекта — списки
**открытые**, их пополняет проект. Модель прохода — не ось, а цена прогона.
Осей по этому признаку девять, и дом теперь у каждой.
**Р232. Две оси были бездомными, и обе машинные.** Коды выхода объявлялись
«общим словарём» в **одиннадцати** местах, и каждое объявление перечисляло свой
набор соседей: «тот же, что у `tasks.py`», «тот же, что у `tasks.py`, `docs.py`
и `copies.py`». Ни одно не было домом — все списки по памяти, и машина их не
сверяла, потому что `copies.py` смотрит markdown, а перечни лежали в
docstring'ах скриптов. Режим прогона (с меткой · без метки) завёлся накануне
слияния и разошёлся по четырём файлам, ни в одном не будучи назван осью, — при
том что в уставе `review-basics` он задаёт **саму возможность запуска** прохода.
**Р233. Слово «стадия» значило в одном файле две разные вещи.** «Стадия 1 —
Автотесты … Стадия 5 — Triage» — ступени внутри прогона кода, наружу не
выходящие; «метка правит обе стадии ревью» — дизайн и код, то есть членение,
которое видит вызывающий скилл. Разведено: ступени внутри, стадии снаружи.
**Р234. Дом перечня — не дом значений.** `shared/axes.md` держит только сами
оси, их адреса и **чего каждая не решает**. Механика остаётся у владельца:
второй пересказ разошёлся бы с первым, а вот перечень нужен целиком и в одном
месте — вопрос «а не задаёт ли это метку» задают из скилла, который метку не
ведёт. Целиком сюда переехали ровно две оси, у которых владельца нет.
**Карта нашла ошибку в самой себе, и это её главный довод.** Первая редакция
объявила пустой клетку «категория документа × метка»: якобы проект вправе
завести тему, под которую ни одна метка не отряжает прохода. Проверка показала
обратное — `review-basics` приёмник проектных тем при **любой** метке. Пустой
оказалась соседняя клетка: на прогоне **без метки** план фиксирован сценарием, и
своих тем проекта в нём нет вовсе. Найти это можно было только сведя оси в одну
таблицу.
## Что из этого следует
**С222. Словарь, объявленный «общим» в каждом потребителе, — это перечень по
памяти, а не дом.** Признак вырожденности проверяемый: каждое объявление
называет свой набор соседей, и ни одно не называет владельца.
**С223. Копия в docstring'е скрипта машиной не сверяется, потому что `copies.py`
смотрит markdown.** Значит, прозе в коде дом нужнее, чем прозе в документах: там
расхождение ловит гейт, здесь — никто.
**С224. Пустая клетка в таблице осей — находка, а не пробел оформления.** Она
называет случай, для которого процесс не сказал ничего, и до сведения осей в
таблицу такой случай неотличим от продуманного умолчания.
**С225. Слово, занятое дважды в одном файле, дороже неточного слова.** Читатель,
пришедший за термином, получает два ответа и не знает, что их два.
+121
View File
@@ -0,0 +1,121 @@
# Решения по устройству процесса
Журнал согласований: что решено, почему и что из этого следует. Пишется по ходу
разбора тем, **одна тема — один файл**; этот файл — только указатель.
Три сквозные нумерации, и они не пересекаются:
- **Т** — требование: вход, который обязан быть удовлетворён. Живут здесь, ниже.
- **Р** — решение: что согласовано и почему. Р1–Р234 по темам в порядке журнала.
- **С** — следствие: что из решения вытекает. С1–С225, тоже сквозным счётом.
Номер закреплён за записью навсегда: журнал описывает прошлые состояния и задним
числом не переписывается. Отсюда и разнобой формы — ранние темы держат решения
под заголовком «Решено», поздние ведут их прозой.
Незакрытые остатки прошлого захода — [REMAINING.md](../REMAINING.md).
## Требования, зафиксированные по ходу
Не решения — вход, который обязан быть удовлетворён и разбирается в названной
теме.
**Т1. Адаптация и проверка проекта под канон — обязательный скилл.** Нужно уметь
прийти в **любой** старый проект и перевести его на текущие рельсы. Канон при
этом сам будет меняться, поэтому уже приведённые проекты тоже должны повышаться
до новых версий. *Разбирается в [теме 5](05-project-start-lifecycle.md) (старт и
жизненный цикл проекта).*
Следствия, которые из этого уже видны:
- **У канона обязана быть версия, а у проекта — отметка, под какую он
приведён.** Иначе «соответствует канону» не имеет определённого ответа:
сравнение идёт с тем, что модель помнит сейчас, а это и есть дрейф.
- **Журнал изменений канона — как миграции.** Каждое повышение версии несёт
запись «что добавилось, что переехало, что удалено, что сделать проекту». Без
него адаптация переизобретается на каждом проекте.
- **Отметка версии машиночитаема.** `.docs.json` отвергнут как *указатель
путей* (решение [Р6](02-project-doc-canon.md)), но отметка версии — другое:
её читает скрипт, и разбирать прозу `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](07-skills-layout-scripts.md).*
## Темы
| # | Тема | Дата |
|---|------|------|
| 1 | [Статус OpenSpec](01-openspec-status.md) | 2026-08-03 |
| 2 | [Канон документов проекта](02-project-doc-canon.md) | 2026-08-03 |
| 3 | [Брифа ревью нет — бриф это и есть канон](03-review-brief-is-canon.md) | 2026-08-03 |
| 4 | [Границы плагинов](04-plugin-boundaries.md) | 2026-08-03 |
| 5 | [Старт проекта и жизненный цикл под каноном](05-project-start-lifecycle.md) | 2026-08-03 |
| 6 | [Поддержание документов по ходу разработки](06-docs-upkeep.md) | 2026-08-03 |
| 7 | [Раскладка скиллов и доставка скриптов](07-skills-layout-scripts.md) | 2026-08-03 |
| 8 | [Порядок выката](08-rollout-order.md) | 2026-08-03 |
| 9 | [Линтеры скриптов](09-script-linters.md) | 2026-08-03 |
| 10 | [Ревью готовых плагинов двумя проходами](10-two-pass-plugin-review.md) | 2026-08-03 |
| 11 | [Зависимости между плагинами](11-plugin-dependencies.md) | 2026-08-03 |
| 12 | [Механическая проверка копий](12-mechanical-copy-check.md) | 2026-08-03 |
| 13 | [Секции `PLAN.md` переименованы](13-plan-sections-renamed.md) | 2026-08-03 |
| 14 | [Умолчания режимов прогона перевёрнуты](14-run-mode-defaults-flipped.md) | 2026-08-03 |
| 15 | [Порядок проходов ревью — граф зависимостей](15-review-pass-order-graph.md) | 2026-08-03 |
| 16 | [Каталог вместо файла в `docs/` — отложено до переезда healthlog](16-directory-instead-of-file.md) | 2026-08-04 |
| 17 | [Разбор заметок: ступень ревью, род работы, роадмап](17-notes-tier-work-kind-roadmap.md) | 2026-08-04 |
| 18 | [Ступень поднимает проход, а не риск](18-tier-raises-pass-not-risk.md) | 2026-08-04 |
| 19 | [Роадмап — состояние проекта, а не очередь работ](19-roadmap-is-state-not-queue.md) | 2026-08-04 |
| 20 | [Форма записи: заголовок, секции, вычитка](20-record-form-heading-sections.md) | 2026-08-04 |
| 21 | [Язык проектных текстов — информационный стиль](21-project-text-language.md) | 2026-08-04 |
| 22 | [Обкатка агента вычитки: имя, охват и «так везде»](22-wording-agent-trial.md) | 2026-08-04 |
| 23 | [Вычитка разделена на два прохода](23-wording-split-two-passes.md) | 2026-08-04 |
| 24 | [Обкатка двух проходов: два дефекта в собственных правилах](24-two-pass-trial-defects.md) | 2026-08-04 |
| 25 | [Секция `Сопровождение` и общий словарь трёх мест](25-maintenance-section-shared-vocab.md) | 2026-08-04 |
| 26 | [Канон 4: правка задним числом отменена](26-canon-4-retroactive-edit-cancelled.md) | 2026-08-04 |
| 27 | [Тип записи стал единственной осью и задаёт схему](27-record-type-single-axis.md) | 2026-08-05 |
| 28 | [Слаг подкреплён проверкой, обещанный судья заведён](28-slug-check-and-judge.md) | 2026-08-05 |
| 29 | [Обкатка `doc-consistency` на самом dev-skills](29-doc-consistency-trial.md) | 2026-08-05 |
| 30 | [`av-dev-backlog` удалён](30-av-dev-backlog-removed.md) | 2026-08-05 |
| 31 | [Ревизия покрытия `av-dev-pm` продакт-оптикой](31-pm-coverage-product-review.md) | 2026-08-05 |
| 32 | [Сквозной проход по словарю: пять слов сняты, девять закрыты списком](32-vocabulary-sweep.md) | 2026-08-05 |
| 33 | [Стоимость ревью: снят самый дорогой проход и самая дорогая модель](33-review-cost-cut.md) | 2026-08-06 |
| 34 | [Пропускная способность против глубины: тяжёлые проходы уехали в верхнюю ступень](34-throughput-vs-depth.md) | 2026-08-06 |
| 35 | [Ревизия моделей: переведены двое из девяти, и критерий оказался не тот](35-model-revision.md) | 2026-08-06 |
| 36 | [Темы ревью: документ проекта стал направлением проверки](36-review-topics-project-docs.md) | 2026-08-06 |
| 37 | [`gate` и `autotests` сведены к одному имени](37-gate-and-autotests-one-name.md) | 2026-08-07 |
| 38 | [Шов между плагинами: канон не называет имён проходов](38-plugin-seam-no-pass-names.md) | 2026-08-07 |
| 39 | [Спринт без цели — законный случай](39-sprint-without-goal.md) | 2026-08-07 |
| 40 | [Три категории документов: не всякий документ — тема ревью](40-three-doc-categories.md) | 2026-08-07 |
| 41 | [Разметка задачи: одна величина, посчитанная один раз](41-task-sizing-once.md) | 2026-08-07 |
| 42 | [`quick` стал дешевле `standard` тремя способами](42-quick-cheaper-than-standard.md) | 2026-08-07 |
| 43 | [Ревью дизайна тоже растёт ступенями](43-design-review-tiers.md) | 2026-08-07 |
| 44 | [Метка задачи: одно значение, по которому выбираются все ревьюверы](44-task-label-single-value.md) | 2026-08-07 |
| 45 | [Корректор метки, доля `small` и корпус оценки](45-label-corrector-small-share.md) | 2026-08-07 |
| 46 | [Правило выбора метки съехало из скилла в отдельный документ](46-label-rule-own-document.md) | 2026-08-07 |
| 47 | [OpenSpec заводится скиллом, а его конфиг — часть канона](47-openspec-setup-skill.md) | 2026-08-07 |
| 48 | [Форма чужого инструмента держится опросом инструмента, а не памятью](48-foreign-tool-form-by-query.md) | 2026-08-07 |
| 49 | [Дом общего правила вышел из плагина](49-shared-rule-home-outside-plugin.md) | 2026-08-09 |
| 50 | [Вычитка раздвоилась по плагину, а не по правилу](50-wording-split-by-plugin.md) | 2026-08-09 |
| 51 | [av-dev-pm расколот: владение пошло по тому, что ставится порознь](51-av-dev-pm-split.md) | 2026-08-09 |
| 52 | [Валидатор поехал за файлом: у конвейера появился свой скрипт](52-validator-follows-file.md) | 2026-08-09 |
| 53 | [`canon` и `docs` остаются двумя скиллами](53-canon-and-docs-two-skills.md) | 2026-08-09 |
| 54 | [Стык плагинов: правило получило дом, адреса остались у владельцев](54-plugin-seam-rule-home.md) | 2026-08-09 |
| 55 | [`task-pipeline` стал `resolve`: два плановых стопа вместо полной автономии](55-task-pipeline-becomes-resolve.md) | 2026-08-09 |
| 56 | [`av-dev-pipeline` → `av-dev-code`, `review-pipeline` → `review`](56-plugin-and-skill-renames.md) | 2026-08-09 |
| 57 | [Спринты отменены: приоритет стал порядком строк, `session` стал `groom`](57-sprints-cancelled-groom.md) | 2026-08-09 |
| 58 | [Судьи документов получили свой скилл — `healthcheck`](58-doc-judges-healthcheck.md) | 2026-08-09 |
| 59 | [Аудит четырьмя сабагентами: описания отстают от механики молча](59-four-subagent-audit.md) | 2026-08-09 |
| 60 | [Служебный файл зовётся по плагину-владельцу; у задач появилась своя версия формата](60-service-file-named-by-owner.md) | 2026-08-11 |
| 61 | [Разведка и решение — два сценария одного скилла, а не два скилла](61-research-and-solve-two-scenarios.md) | 2026-08-11 |
| 62 | [Вычитку зовёт тот, кто правил, а не тот, кто синкал](62-wording-called-by-editor.md) | 2026-08-11 |
| 63 | [Обслуживание — третий сценарий: у цикла SDD там нет входа](63-maintenance-third-scenario.md) | 2026-08-13 |
| 64 | [Три плагина слились в один: раскол платили, а не пользовались](64-three-plugins-merged.md) | 2026-08-13 |
| 65 | [Перечень осей получил дом; две оси жили без владельца](65-axes-registry-home.md) | 2026-08-13 |
+6 -2
View File
@@ -51,13 +51,15 @@ OWNERS = {
# Журналы: описывают прошлые состояния и задним числом не переписываются.
# Адрес, верный на момент записи, здесь останется навсегда, и это не дрейф.
# Ключ, кончающийся на `/`, — каталог целиком: журнал решений разложен по теме
# на файл, и каждый новый файл в нём — журнал по построению, а не по списку.
JOURNALS = {
"av-dev/skills/doc-canon/references/changelog.md": "журнал версий раскладки",
"av-dev/skills/doc-canon/references/changelog-before-merge.md":
"журнал версий канона до слияния",
"av-dev/skills/doc-canon/references/changelog-tasks-before-merge.md":
"журнал версий формата задач до слияния",
"DECISIONS.md": "журнал решений",
"decisions/": "журнал решений",
"HISTORY.md": "журнал работ",
"NOTES.md": "рабочие заметки",
}
@@ -136,7 +138,9 @@ def walk(root: Path) -> list[Path]:
rel = p.relative_to(root)
if SKIP_DIRS & set(rel.parts):
continue
if rel.as_posix() in JOURNALS:
posix = rel.as_posix()
if any(posix == k or (k.endswith("/") and posix.startswith(k))
for k in JOURNALS):
continue
out.append(p)
return out