«Идентичность, слияние, разбор» пришли из одного проекта, и в общем виде формулировка не читалась: вопрос «как применить это к моему проекту» не имел ответа в тексте. Теперь класс задан тремя условиями, не зависящими ни от домена, ни от языка: вариантов несколько и оба защитимы; спека между ними не выбирает; неверный выбор не падает, а даёт правдоподобный результат и молча меняет смысл данных. Отрицательный тест сильнее трёх положительных: то, что красит гейт, роняет запрос или ломает тест, в класс не входит — это ловят проходы дешевле. Отсюда же и причина, по которой класс достался самому дорогому проходу: независимая реализация выберет другой вариант, и дифф между вариантами и есть находка; там, где вариант один, она совпадёт с существующей. Три слова остались как три места, где такие правила водятся — граница, где данные входят или встречаются: состав ключа и нормализация перед сравнением; победитель конфликта и тай-брейк при равенстве; границы токенов и неоднозначный вход. Проект перечисляет свои места в docs/review.md, и перечень производен от теста, а не заменяет его. Две оговорки, без которых правило вырождается: - триггер — новое или изменённое по существу правило, а не код рядом с ним; иначе проект, чей домен и состоит из таких правил, всегда в deep; - проект, где такого класса нет вовсе, deep не запускает никогда, и это законное состояние, а не недонастройка. review-reimpl получил тот же тест и право сказать первой строкой, что позвали не на его класс, — строкой в границы покрытия, а не отказом работать. DECISIONS 18, XXX и следствия 76–77. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1351 lines
116 KiB
Markdown
1351 lines
116 KiB
Markdown
# Решения по устройству процесса
|
||
|
||
Журнал согласований: что решено, почему и что из этого следует. Пишется по ходу
|
||
разбора тем, одна тема — один раздел. Причина обязательна: через месяц она
|
||
забывается раньше факта.
|
||
|
||
Незакрытые остатки прошлого захода — [REMAINING.md](REMAINING.md).
|
||
|
||
## Требования, зафиксированные по ходу
|
||
|
||
Не решения — вход, который обязан быть удовлетворён и разбирается в названной
|
||
теме.
|
||
|
||
**Т1. Адаптация и проверка проекта под канон — обязательный скилл.** Нужно уметь
|
||
прийти в **любой** старый проект и перевести его на текущие рельсы. Канон при
|
||
этом сам будет меняться, поэтому уже приведённые проекты тоже должны повышаться
|
||
до новых версий. *Разбирается в теме 5 (старт и жизненный цикл проекта).*
|
||
|
||
Следствия, которые из этого уже видны:
|
||
|
||
- **У канона обязана быть версия, а у проекта — отметка, под какую он
|
||
приведён.** Иначе «соответствует канону» не имеет определённого ответа:
|
||
сравнение идёт с тем, что модель помнит сейчас, а это и есть дрейф.
|
||
- **Журнал изменений канона — как миграции.** Каждое повышение версии несёт
|
||
запись «что добавилось, что переехало, что удалено, что сделать проекту». Без
|
||
него адаптация переизобретается на каждом проекте.
|
||
- **Отметка версии машиночитаема.** `.docs.json` отвергнут как *указатель
|
||
путей* (решение F), но отметка версии — другое: её читает скрипт, и разбирать
|
||
прозу `CLAUDE.md` для этого не нужно. Прецедент — `.tasks.json`.
|
||
- **Операций три:** `check` (соответствие текущему канону), `adopt` (перевод
|
||
чужой раскладки), `upgrade` (повышение с версии N до M по журналу). Первая и
|
||
третья — одно сравнение с разными исходами.
|
||
- **Механизируемое и суждение не смешивать.** Скрипт проверяет пути, лишние
|
||
файлы, битые ссылки, версию. Агент судит о смысловых дублях (`docs/specs/
|
||
recognition.md` против capability `recognition`) и об оставшемся поведении в
|
||
`architecture.md`. Скрипт, отчитавшийся «канон соблюдён» на проекте с тремя
|
||
лишними файлами, хуже отсутствующего.
|
||
- **Границы плагинов:** `docs/tasks/` — часть канона документов, но владеет им
|
||
`av-dev-tasks` со своим `tasks.py adopt`. Два плагина сходятся на одном
|
||
каталоге. *Тема 7.*
|
||
|
||
## 1. Статус OpenSpec (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
OpenSpec несёт оба проекта: healthlog — 5 capability, 3530 строк спек, 9
|
||
архивных change за две недели; jellybit — 11 capability, 3895 строк, 43 архивных
|
||
change. При этом в трёх местах плагина написана ветка «проект без OpenSpec»
|
||
(`task-pipeline` предпосылки, `review-pipeline` предпосылки, `task-batch`
|
||
предпосылки) — и **не исполнялась ни разу**.
|
||
|
||
Проектные факты живут в пяти домах: `CLAUDE.md`, `docs/architecture.md`,
|
||
`openspec/specs/`, `openspec/config.yaml` → `context`, и планируется шестой —
|
||
`docs/review-brief.md`.
|
||
|
||
Расхождение измерено: у healthlog раздел «Хранилище» в `docs/architecture.md` —
|
||
950 строк (377–1328) против `openspec/specs/storage/spec.md` на 1337 строк. Два
|
||
описания одного поведения, никем не сверяемые. У jellybit того же нет:
|
||
`docs/specs/architecture.md` — 300 строк обзора, детали в 11 спеках. **Проект с
|
||
43 изменениями держит архитектуру втрое короче проекта с 9.**
|
||
|
||
### Решено
|
||
|
||
**A. OpenSpec — жёсткая предпосылка `av-dev-pipeline`.** Ветки деградации
|
||
удаляются, вместо них объявленная зависимость и проверка на старте. Зависимость
|
||
на уровне **плагина, а не процесса**: `av-dev-tasks`, `av-dev-git` и будущий
|
||
плагин документов от OpenSpec не зависят и работают на python/ansible-проектах.
|
||
|
||
*Причина:* непроверенная ветка деградации хуже честной строки «требуется
|
||
OpenSpec» — она даёт ложную уверенность, что проект без спек поедет.
|
||
|
||
**B. Нормативный дом поведения — `openspec/specs/`.** `architecture.md`
|
||
переопределяется как **обзор**: принципы, компоненты со ссылками на capability,
|
||
внешние форматы данных, раскладка, деплой, открытые вопросы. Поведения он не
|
||
описывает.
|
||
|
||
*Причина:* `opsx:archive` вливает дельты именно в `openspec/specs/` — любой
|
||
другой нормативный дом обязан синхронизироваться руками и разойдётся. Форма
|
||
jellybit это уже подтвердила на 43 изменениях.
|
||
|
||
**C. `openspec/config.yaml` → `context` держит только нужды генерации.** Язык,
|
||
правила именования capability, придирки валидатора RFC 2119 — и ссылки. Правило
|
||
ревью, пересказ конвенций и инварианты оттуда вычищаются: у них есть свои дома.
|
||
|
||
*Причина:* блок «Ревью (процесс, не артефакт)» в обоих `config.yaml` дословно
|
||
повторяет шаги 4 и 7 `task-pipeline`. Это второй дом для правила, которым владеет
|
||
плагин, и он разойдётся на первой же правке.
|
||
|
||
### Что из этого следует
|
||
|
||
Из A:
|
||
|
||
1. Три места с веткой деградации переписываются на объявленную предпосылку плюс
|
||
проверку на старте (есть `openspec/`, разрешаются `opsx:*`) и внятный отказ:
|
||
`task-pipeline` предпосылки, `review-pipeline` предпосылки, `task-batch`
|
||
предпосылки.
|
||
2. Описание `av-dev-pipeline` в маркетплейсе получает строку «требует OpenSpec».
|
||
3. **Факт для темы «объединять ли tasks и pipeline»:** объединение потянуло бы
|
||
зависимость от OpenSpec на управление задачами, которой там сейчас нет.
|
||
|
||
Из B:
|
||
|
||
4. Правило «поведение — в спеку, устройство и границы — в архитектуру» становится
|
||
контрактом плагина документов и правилом шага «синк документации» в
|
||
`task-pipeline`.
|
||
5. healthlog чистится **не разом**: раздел вычищается той задачей, которая его
|
||
касается. Нужен способ не потерять остаток — иначе 950 строк «Хранилища»
|
||
останутся навсегда.
|
||
6. **Дыра, которую решение открывает:** «почему» после архивации. Сегодня
|
||
`CLAUDE.md` healthlog велит писать причину решения в `architecture.md` — а мы
|
||
её оттуда выселяем. Спеки нормативны и «почему» не держат; `design.md` живёт
|
||
внутри change и уезжает в архив. Либо ADR (как у jellybit), либо явное
|
||
правило «почему живёт в архивных change». **Первый вопрос следующей темы.**
|
||
|
||
Из C:
|
||
|
||
7. `av-dev-pipeline` даёт образец `openspec/config.yaml` отдельным reference —
|
||
он владеет связью с OpenSpec. Заполняется при старте проекта и при `adopt`.
|
||
8. У обоих проектов из `config.yaml` вычищается блок «Ревью (процесс, не
|
||
артефакт)», пересказ конвенций и инвариантов.
|
||
|
||
## 2. Канон документов проекта (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Измерено по обоим проектам:
|
||
|
||
- **«Почему» не теряется — оно не находится.** `design.md` пишется почти всегда
|
||
(jellybit 39 из 43 архивных change, healthlog 9 из 9 — ≈285 КБ за две недели)
|
||
и имеет секции `Context` / `Goals / Non-Goals` / `Decisions` /
|
||
`Risks / Trade-offs`, то есть является ADR по структуре. Против этого ADR
|
||
руками: **6 записей у jellybit, четыре из них 13 июня — в день старта**; между
|
||
15 июня и 23 июля прошло ~40 изменений и ноль ADR. У healthlog ADR нет вовсе,
|
||
а настоящее ADR-рассуждение (отказ от DuckDB) лежит в разделе «Открытые
|
||
вопросы» файла `architecture.md`, потому что больше некуда.
|
||
- **Два плана.** `docs/plan.md` healthlog («порядок и его обоснование», 11 шагов)
|
||
и `<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.
|
||
|
||
### Решено
|
||
|
||
**D. «Почему» — ADR как промоут поверх архива.** Обоснование по-прежнему пишет
|
||
`design.md`; ADR — короткая запись, цитирующая решение и ссылающаяся на архивный
|
||
`design.md`. Заводит её **шаг «синк документации» пайплайна по названному
|
||
триггеру** (дорогой откат / намеренный отказ от очевидного / пересмотр прежнего
|
||
решения), а не человек по вдохновению.
|
||
|
||
*Причина:* ручной ритуал эмпирически не выжил — 6 записей на 52 изменения.
|
||
Автоматический (`opsx:propose` пишет `design.md` всегда) работает и производит на
|
||
порядок больше. Чинить надо не дом, а индекс и критерий промоута.
|
||
|
||
**E. `docs/plan.md` растворяется в `<tasks>/PLAN.md`.** Файл удаляется, 11 шагов
|
||
становятся целями в «порядке», ссылки в `CLAUDE.md` и паспорте переводятся.
|
||
|
||
**F. Пути жёсткие, оба проекта приводятся к одному виду.** Плагин знает раскладку
|
||
поимённо; указателя вида `.docs.json` нет.
|
||
|
||
*Причина (словами владельца):* «так проще ориентироваться во множестве проектов,
|
||
а не видеть слегка похожую, но разную структуру в каждом. Все проекты малого и
|
||
среднего размера, проще подогнать их под одну структуру. Кроме того, у OpenSpec
|
||
тоже структура строгая». Цена принята сознательно: плагин перестаёт быть
|
||
переносимым на чужой репозиторий, а `adopt` из «поправь указатели» превращается в
|
||
«перенеси файлы».
|
||
|
||
**G. Конвенции и разведка — каталогами с README-индексом.** `docs/conventions/`
|
||
и `docs/research/`: путь жёсткий, нарезка внутри свободна. Схема хранилища —
|
||
**отдельный** `docs/database.md` (своя каденция: меняется миграцией, а не
|
||
архитектурным решением; гейт healthlog уже сверяет миграции с документацией).
|
||
Конвенции идентификаторов и именования — не схема, они в `conventions/`.
|
||
|
||
**H. Слота для черновиков нет.** Идея → задача `[idea]`; намеренный отказ → ADR;
|
||
порядок работ → `PLAN.md`; незрелое размышление → `opsx:explore` внутри change.
|
||
|
||
### Канон
|
||
|
||
```
|
||
CLAUDE.md памятка агенту: что это, стек, инварианты, команды, слоты
|
||
docs/
|
||
passport.md зачем и для кого; чем НЕ является; сценарии; референсы
|
||
architecture.md как сложено — обзор: принципы, компоненты со ссылками
|
||
на capability, внешние границы, раскладка, деплой
|
||
database.md схема хранилища (там, где есть БД)
|
||
conventions/README.md + <тема>.md как пишем код; README держит правило промоута
|
||
research/README.md + <тема>.md что показала реальность: чужие форматы, живые данные
|
||
adr/README.md + template.md + ADR-*.md почему — промоут поверх архивных design.md
|
||
review-journal.md промахи конвейера ревью ← уточнено в теме 3
|
||
review-brief.md предмет ревью — см. тему 3 ← отменено в теме 3
|
||
tasks/ av-dev-tasks: items/, PLAN.md, BACKLOG.md, SPRINT.md, REJECTED.md
|
||
openspec/
|
||
config.yaml только нужды генерации + ссылки
|
||
specs/<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 (поддержание):** точная формулировка триггера промоута в
|
||
ADR; нужен ли механический `check` раскладки документов, раз пути жёсткие;
|
||
как не потерять остаток при постепенной чистке `architecture.md`.
|
||
|
||
## 3. Брифа ревью нет — бриф это и есть канон (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Контракт брифа — 413 строк, 13 разделов, отдельный файл `docs/review-brief.md`,
|
||
который каждый проход читает как истину. Заполнение на обоих проектах дало 841 и
|
||
734 строки, и `REMAINING.md` уже отметил, что часть разделов вырождается в
|
||
пересказ.
|
||
|
||
Разбор по разделам после решения F (жёсткие пути) показал: **посредник между
|
||
агентом и файлом не нужен, когда путь известен**. Восемь из тринадцати разделов
|
||
дублируют канон или снимаются жёсткими путями.
|
||
|
||
### Решено
|
||
|
||
**I. Отдельного файла-брифа нет.** Проектную конкретику проходам дают документы
|
||
канона напрямую, по жёстким путям. Формулировка владельца: «артефакты в `docs` и
|
||
должны стать частями брифа, а для ревью достаточно дать ссылки на эти артефакты».
|
||
|
||
*Причина:* один факт — один дом. Бриф был вторым домом для паспорта, инвариантов
|
||
и карты, а разошедшийся бриф хуже отсутствующего: он выглядит актуальным.
|
||
|
||
**J. Заводится `docs/security.md`.** Периметр **первой строкой** (целевой и
|
||
сегодняшний, если контур не развёрнут), недоверенный вход и его каналы, из чего
|
||
строятся пути и ключи, что разграничивает доступ, что чувствительнее чего, что
|
||
вне модели. Материал уже есть, но рассыпан: у healthlog — раздел
|
||
«Аутентификация» в `architecture.md` и строка про секреты в `CLAUDE.md`, у
|
||
jellybit — секреты в `conventions/config.md`. **Периметра нет ни у одного**, а
|
||
без него враждебный проход не выбирает между «открыт наружу» и «контур
|
||
доверенный».
|
||
|
||
**K. `review-journal.md` → `docs/review.md`:** журнал дефектов плюс настройка
|
||
конвейера под проект. Туда садится остаток брифа, который фактом о проекте не
|
||
является — типовые узлы, типовые ложноположительные, вопросы к проходам,
|
||
недоступно проверке.
|
||
|
||
*Причина:* все четыре — производные калибровки, и журнал им источник. `##
|
||
Вопросы к проходам` сам называет журнал главным источником; `### Перестали
|
||
проверять сознательно` требует ссылки на его запись.
|
||
|
||
**L. Журнал расширяется до всех воспроизведённых дефектов** с пометкой
|
||
«проскочил / пойман ревью». Эвал-сет для калибровки — выборка по пометке.
|
||
|
||
*Причина:* пойманные дефекты с оракулом (768 МиБ пика, 5.019 с удержания
|
||
блокировки) сегодня не сохраняются нигде, кроме отчётов триажа в архиве change, а
|
||
они и есть лучшая опора для прохода — проектные, воспроизводимые, однажды
|
||
оказавшиеся правдой.
|
||
|
||
**M. Семантика гейта — в `CLAUDE.md`, расширением раздела «Команды».** Чем
|
||
краснеет безусловно и почему, где логи, что означает исход, чего в гейте
|
||
намеренно нет, **кто и когда обязан гонять дорогое вне гейта**, что запускать
|
||
запрещено (с путями). Гейт краснеет не только в ревью — это факт о проекте.
|
||
|
||
**N. Severity инвариантов дописывается в `CLAUDE.md`** рядом с формулировкой.
|
||
Контракт брифа сам называл это лучшим исходом; жёсткие пути делают возможным.
|
||
Оговорка «выведена по обратимости» исчезает вместе с пересказом.
|
||
|
||
### Канон после темы 3
|
||
|
||
```
|
||
CLAUDE.md что это, стек, инварианты с severity, команды,
|
||
семантика гейта, запреты, слоты
|
||
docs/
|
||
passport.md зачем и для кого; чем НЕ является; сценарии; референсы
|
||
architecture.md как сложено — обзор; окружение, внешние зависимости,
|
||
наблюдатель, характер потока
|
||
database.md схема хранилища; представление данных и настройки
|
||
с числовым значением (таймаут занятости, лимит тела,
|
||
режим журналирования, ретеншен)
|
||
security.md периметр первой строкой; недоверенный вход; из чего
|
||
строятся пути и ключи; разграничение; что вне модели
|
||
conventions/README.md + <тема>.md
|
||
research/README.md + <тема>.md наблюдения и измеренные числа с провенансом
|
||
adr/README.md + template.md + ADR-*.md
|
||
review.md настройка конвейера под проект + журнал дефектов
|
||
tasks/ av-dev-tasks
|
||
openspec/
|
||
config.yaml, specs/<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, требование Т1).
|
||
14. **Девять charter'ов переписываются второй раз.** Сейчас каждый читает «из
|
||
раздела `## X` брифа»; станет — из файла канона. **Цена названа вслух:**
|
||
первая переписка (вынос в плагин) осталась незамеренной — `REMAINING.md`,
|
||
пункт 1. Вторая делает замер по четырём реальным находкам healthlog
|
||
**обязательным, а не желательным**: два неизмеренных изменения подряд в том
|
||
самом месте, где присваивается severity.
|
||
15. **Теряется соседство фактов, и charter обязан сшивать.** Контракт настаивал,
|
||
что замер становится находкой только рядом с настройкой: «768 МиБ пика» —
|
||
аномалия, лишь если известно, что запись лежит сжатой и распаковывается
|
||
целиком; «5.019 с удержания блокировки» — отказ соседа, лишь если известен
|
||
таймаут занятости. Теперь это `research/` и `database.md`, и charter'ы `ops`,
|
||
`adversary`, `reimpl` обязаны прямо говорить «собери из этих двух», иначе
|
||
проход снимет верное число и честно понизит находку до гипотезы.
|
||
16. **Деградация становится поразрядной** — и это лучше прежнего «нет брифа →
|
||
деградирует всё». Нет `security.md` — деградирует `adversary`; нет
|
||
`research/` — числа неизвестны `ops`, `adversary` и `reimpl`; нет
|
||
`passport.md` — архитектурный проход теряет границу домена. Каждый проход
|
||
пишет свою строку в границы покрытия.
|
||
17. **Открытый вопрос из `REMAINING.md` закрыт:** раздел `## Триггеры`
|
||
удаляется вместе с брифом. Правило выбора профиля остаётся в скилле
|
||
конвейера; проектная конкретизация, если понадобится, — в `docs/review.md`.
|
||
|
||
## 4. Границы плагинов (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Связь `tasks` ↔ `pipeline` уже сделана **ролями, а не именами**: скиллы говорят
|
||
«пайплайн проекта», «владелец спринта», «тот, кто ведёт задачи». Жёсткая ссылка
|
||
по имени ровно одна — `task-pipeline:112` на канонический текст правила про
|
||
остаток внутри `session`, и рядом обработан случай «плагин не подключён».
|
||
|
||
Слоты `CLAUDE.md` при этом дублировались уже внутри одного плагина: шесть у
|
||
`tasks`, семь у `session`, три пары — одно и то же. Темы 2–3 растворили ещё
|
||
часть: «куда переезжает суть» отвечает канон, «оракулы» — семантика гейта
|
||
(решение M), «где живёт разбор процесса» — `docs/review.md` (решение K). Из
|
||
тринадцати остаётся около четырёх.
|
||
|
||
### Решено
|
||
|
||
**O. Три плагина: `av-dev-pm`, `av-dev-pipeline`, `av-dev-git`.**
|
||
|
||
- **`av-dev-pm`** (бывший `av-dev-tasks`) — управление продуктом: канон
|
||
документов, задачи, цели, спринты, старт и адаптация проекта. Владеет всем
|
||
`docs/`, включая `docs/tasks/`.
|
||
- **`av-dev-pipeline`** — исполнение: SDD-цикл, конвейер ревью, девять агентов.
|
||
- **`av-dev-git`** — стиль коммитов; работает в любом репозитории.
|
||
|
||
*Причина (словами владельца):* «пайплайн можно и переиспользовать в других
|
||
проектах с более простым подходом к управлению». Это подтверждается разбором:
|
||
пайплайн зависит от **файлов канона и от OpenSpec, а не от плагина** `av-dev-pm`.
|
||
В чужом проекте нужных файлов нет — включается поразрядная деградация (следствие
|
||
16), и это штатный режим, а не поломка.
|
||
|
||
*Имя:* `pm` = product management, «объединение всех операций по управлению
|
||
продуктом», и согласуется с `av-dev-git`.
|
||
|
||
**P. Граница «пайплайн не закрывает задачу» снимается.** Закрывает задачу и
|
||
двигает строки между `SPRINT.md` / `BACKLOG.md` / `REJECTED.md` **агент-
|
||
оркестратор** — `task-pipeline` и `task-batch`, а не сабагенты внутри них. Зовёт
|
||
он `tasks.py` через слот «Команда учёта задач» в `CLAUDE.md`.
|
||
|
||
Слот, следовательно, **не исчезает, а становится мостом между плагинами** — и
|
||
заодно тем, чего в чужом проекте нет, отчего пайплайн там работает как прежде:
|
||
докладывает исход, записей учёта не трогает.
|
||
|
||
**Q. `av-dev-backlog` помечается устаревшим и остаётся** до перевода jellybit.
|
||
Описание переписывается так, чтобы не ловить триггер «добавь задачу в беклог» —
|
||
иначе агент выбирает между ним и `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). Разбирается в теме 5.
|
||
22. **Состав `av-dev-pm`:** `tasks`, `session` (есть), `docs` — ведение канона,
|
||
`project` — старт, adopt, check, upgrade (тема 5).
|
||
|
||
## 5. Старт проекта и жизненный цикл под каноном (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Требование Т1: прийти в любой старый проект и перевести на текущие рельсы; канон
|
||
сам меняется, значит уже приведённые проекты тоже повышаются.
|
||
|
||
Существующий `adopt` (уровень задач) даёт готовую форму: **`scan` — только
|
||
чтение, карта → суждение человека → `apply` — запись одним проходом**, с отказом
|
||
до первой записи при неверной карте и с обязательным разделом «не разложилось»
|
||
поимённо. Форма переносится на уровень канона как есть.
|
||
|
||
Четыре операции различаются не поровну: `adopt`, `check` и `upgrade` — одна
|
||
машина сравнения с разными исходами, а `init` — принципиально другой режим,
|
||
разговор, а не сверка.
|
||
|
||
### Решено
|
||
|
||
**R. Два скилла: `av-dev-pm:init` и `av-dev-pm:canon`.** `init` — интервью по
|
||
входному брифу для нового проекта. `canon` — привести к канону: `check`, `adopt`,
|
||
`upgrade` одной машиной.
|
||
|
||
**S. Скелет канона заводится целиком, незаполненное называется пустым.** Все
|
||
файлы канона есть с первого дня, но незаполненный держит **одну честную
|
||
информативную строку**: «наблюдений на живых данных нет — внешний источник один,
|
||
формат документирован», «прецедентов не накоплено», «внешних зависимостей нет,
|
||
смотри на диск и на СУБД».
|
||
|
||
*Причина:* это тот же принцип, что был в контракте брифа, поднятый на уровень
|
||
файлов. Проход читает такую строку **как факт**, а не как пробел, и не тратит
|
||
обязательный вопрос впустую. Отсутствие файла он прочитать не может никак.
|
||
|
||
**Защита от вырождения в заглушки** берётся у `tasks.py`: пока на месте стоит
|
||
плейсхолдер шаблона, `check` о нём напоминает. Строка «TBD» — это не «пустое
|
||
названо пустым», и `check` обязан их различать.
|
||
|
||
**T. Скрипт `docs.py` плюс версия канона в `docs/.pm.json`.** Отдельный скрипт,
|
||
не расширение `tasks.py`: рефакторинг 2421 работающей строки ради удобства вызова
|
||
не окупается. `docs.py check` зовёт `tasks.py check` для своей части.
|
||
|
||
**Граница механизируемого объявляется вслух — иначе `check` соврёт.**
|
||
|
||
| Проверяет `docs.py` | Судит агент |
|
||
| --- | --- |
|
||
| отсутствующие пути канона | смысловой дубль (`docs/specs/recognition.md` против capability) |
|
||
| файлы в `docs/` вне канона | поведение, оставшееся в `architecture.md` |
|
||
| битые относительные ссылки | протухший факт, разошедшийся с кодом |
|
||
| версия канона и её отставание | достаточность честной строки в пустом слоте |
|
||
| нетронутый плейсхолдер шаблона | |
|
||
|
||
`check`, отчитавшийся «канон соблюдён» на проекте, где из шести файлов три
|
||
лишние, хуже отсутствующего.
|
||
|
||
### Порядок интервью `init` — зависимость, а не удобство
|
||
|
||
Цель и потребители → чем это **не** является и мера успеха → периметр и что
|
||
недоверенное → стек, хранилище, необратимое → чем краснеет гейт → первые цели в
|
||
`PLAN.md`. Каждый блок опирается на ответ предыдущего.
|
||
|
||
Вход — свободный текст «что мне нужно и почему» (образец формы: `BRIEF.md`
|
||
jellybit, 6 КБ). После `init` его дом — `passport.md`; отдельным файлом он не
|
||
остаётся.
|
||
|
||
**`init` физически не производит полный канон.** В новом репозитории нет кода, а
|
||
`architecture.md`, `database.md`, `conventions/` и `research/` выводятся из него.
|
||
Они заводятся скелетом с честной строкой («архитектуры пока нет: кода нет,
|
||
заводится первой задачей») и наполняются шагом синка документации.
|
||
|
||
### Что из этого следует
|
||
|
||
23. **`docs/.pm.json` поглощает `<tasks>/.tasks.json`.** Меняется цепочка
|
||
разрешения в `tasks.py` — сегодня он ищет `.tasks.json` вверх от текущего
|
||
каталога. Нужен переходный период либо чтение обоих.
|
||
24. **`tasks.py adopt` становится шагом внутри `canon adopt`**, а не отдельной
|
||
пользовательской операцией: `docs/tasks/` — часть той же раскладки.
|
||
25. **Версия канона — целое число**, не semver: у канона нет обратной
|
||
совместимости, есть только «приведён» и «не приведён».
|
||
26. **Журнал изменений канона** живёт в плагине —
|
||
`av-dev-pm/skills/canon/references/changelog.md`, запись на версию: что
|
||
добавилось, что переехало, что удалено, что сделать проекту.
|
||
27. **Открыто до темы 6:** звать ли `docs.py check` из гейта проекта. У healthlog
|
||
`task gate` уже сверяет миграции с документацией, так что место есть; но гейт
|
||
принадлежит проекту, и плагин может только рекомендовать строкой в отчёте.
|
||
|
||
## 6. Поддержание документов по ходу разработки (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Гейт healthlog **уже изобрёл нужный механизм** для одного документа —
|
||
`scripts/gate.py:177-181`: миграция изменена, а `docs/database.md` нет → `FAIL`.
|
||
Документ канона сверяется с кодом красным гейтом, а не напоминанием.
|
||
|
||
Против этого — прямое доказательство, что́ не работает: у `adr/` был список
|
||
триггеров прозой («выбор технологии, структурные решения, дорогой откат,
|
||
намеренный отказ»), и он дал **6 записей на 43 изменения**. Прозаический триггер,
|
||
который некому проверить, не срабатывает.
|
||
|
||
Механизируемы три документа из десяти: `database.md` (миграция), `architecture.md`
|
||
(capability в `openspec/specs/` без упоминания в обзоре), `tasks/` (`tasks.py
|
||
check`). Плюс `openspec/specs/` вливает `opsx:archive`.
|
||
|
||
### Решено
|
||
|
||
**U. Принуждённое отрицание в докладе шага синка.** Шаг обязан назвать **каждый**
|
||
документ канона: обновлён — чем, либо «не требуется, потому что…». Нетронутые
|
||
группируются одной строкой с общей причиной.
|
||
|
||
*Причина:* это тот же приём, что «границы покрытия» в отчёте ревью и «пустой
|
||
пункт называется пустым» в каноне — и единственный, о котором в этом репозитории
|
||
есть данные, что он работает. Умолчание «не написал» становится неотличимым от
|
||
«написал, что не требуется», только если отрицание обязательно.
|
||
|
||
**Триггер ADR, окончательная формулировка.** Запись заводится, когда верно одно
|
||
из трёх: **дорогой откат** (переделка стоит дороже переписывания одного файла);
|
||
**намеренный отказ** от очевидного подхода; **пересмотр прежнего решения** — тогда
|
||
у старой записи обязателен статус «заменено на». Не заводится для рутины и для
|
||
того, что видно из кода и `git log`. Источник — архивный `design.md`: ADR его
|
||
цитирует и на него ссылается, а не пересказывает.
|
||
|
||
**V. `docs.py check` обязателен в гейте проекта.** Скилл `canon` при адаптации
|
||
добавляет шаг и печатает это в отчёте.
|
||
|
||
*Причина:* гейт — единственный общий станок, который нельзя пропустить. Проверка,
|
||
которую зовёт агент, может быть не позвана; прецедент в самом healthlog уже есть.
|
||
|
||
Сверка «миграция изменена — `database.md` нет» **обобщается**: путь миграций
|
||
проекта записывается в `docs/.pm.json` рядом с версией канона, и `docs.py` делает
|
||
эту проверку сам, а не каждый проект заново.
|
||
|
||
**W. Остаток чистки помечается маркером и считается числом.** Неразобранный
|
||
раздел получает `<!-- канон: поведение → 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`.
|
||
|
||
## 7. Раскладка скиллов и доставка скриптов (2026-08-03)
|
||
|
||
### Решено
|
||
|
||
**X. Пять скиллов в `av-dev-pm`.**
|
||
|
||
```
|
||
av-dev-pm/skills/
|
||
init/ интервью по брифу → канон нового проекта
|
||
canon/ раскладка: check / adopt / upgrade
|
||
docs/ содержимое канона: ADR из архивного design.md, промоут конвенций,
|
||
запись в research/ и review.md, чистка architecture.md
|
||
tasks/ формат и содержимое задач
|
||
session/ ритуал спринта
|
||
```
|
||
|
||
*Причина отдельного `docs`:* правила ведения содержимого канона обязаны жить у
|
||
владельца канона, а не в шаге синка чужого плагина — иначе проект без пайплайна
|
||
документацию вести не может. Это работает потому, что **вызов скилла через
|
||
пространство имён между плагинами возможен**, в отличие от
|
||
`$CLAUDE_PLUGIN_ROOT`: `task-pipeline` уже зовёт `opsx:propose` и
|
||
`av-dev-pipeline:review-pipeline`. Шаг синка зовёт `av-dev-pm:docs`, а в чужом
|
||
проекте деградирует до прозаического списка.
|
||
|
||
Симметрия, по которой резалось: **раскладка и содержимое разделены и для
|
||
документов, и для задач** — `canon` / `docs`, `tasks` / `session`.
|
||
|
||
**Y. Скрипты не копируются — живут вместе со скиллами.** Три вызывающих, три
|
||
способа дотянуться:
|
||
|
||
| Кто зовёт | Как |
|
||
| --- | --- |
|
||
| скиллы `tasks`, `canon`, `docs` | `$CLAUDE_PLUGIN_ROOT` — свой плагин, работает всегда |
|
||
| `task-pipeline`, `task-batch` | **вызов скилла** `av-dev-pm:tasks`, а не путь |
|
||
| гейт проекта | путь переменной с умолчанием на канонический путь маркетплейса; пишет `canon adopt`, внятный красный отказ, если не найден |
|
||
|
||
**Слот «Команда учёта задач» всё равно исчезает** — но снимает его не копия, а
|
||
**вызов скилла через пространство имён**. Тот же приём, которым шаг синка зовёт
|
||
`av-dev-pm:docs` (решение X): чужой плагин зовёт скилл, скилл разрешает свой
|
||
`$CLAUDE_PLUGIN_ROOT` сам. Путь наружу не выносится вовсе.
|
||
|
||
*Первоначально здесь было решено вендорить `scripts/tasks.py` и
|
||
`scripts/docs.py` в проект. Отменено после проверки фактов:*
|
||
|
||
- **CI нет ни в одном проекте** (ни `.github`, ни woodpecker, ни drone).
|
||
Pre-commit есть только у jellybit — `lefthook` с gofmt/vet/lint/test/gitleaks —
|
||
и гоняется на той же машине, где установлен плагин. Довод «не работает в CI и
|
||
у человека без Claude Code» оказался гипотетическим.
|
||
- **Пара «источник — копия» существует и без вендоринга.** Установленный
|
||
маркетплейс — git-клон; на момент разбора он стоял на `092d07c`, на четыре
|
||
коммита позади `master`, и `av-dev-tasks` с `av-dev-pipeline` в нём
|
||
отсутствовали вовсе. Довод «вендоринг создаёт вторую копию» был слабее, чем
|
||
подан.
|
||
- **Обновление маркетплейса — одна точка на все проекты.** При вендоринге каждый
|
||
проект повышается отдельно, и проекты расходятся друг с другом — ровно та
|
||
разнородность, против которой принято решение F.
|
||
|
||
**Z. Имени у процесса нет — процесс это `av-dev`.** Маркетплейс уже
|
||
`av-dev-skills`, плагины `av-dev-*`; в `CLAUDE.md` проекта пишется «процесс
|
||
av-dev, канон версии N». Имя, которое нигде не работает, — украшение.
|
||
|
||
### Что из этого следует
|
||
|
||
32. **Решение P уточняется:** оркестратор закрывает задачи **вызовом скилла**
|
||
`av-dev-pm:tasks`, а не запуском скрипта по пути. Плагина в проекте нет —
|
||
вызов не разрешается, и пайплайн, как прежде, только докладывает исход.
|
||
33. **Слот исчезает из двух скиллов** — `tasks` (слот 6) и `session` (слот 7), —
|
||
и из текстов `task-pipeline` и `task-batch`, которые на него ссылаются.
|
||
34. **`canon upgrade` отвечает за раскладку и версию в `docs/.pm.json`.**
|
||
Скрипты обновляются обновлением маркетплейса, а не проектом.
|
||
35. **Скрипты живут в `av-dev-pm/skills/{tasks,canon}/scripts/`.** `docs.py` — в
|
||
`canon`, потому что раскладку проверяет он.
|
||
36. **`canon check` сверяет версию канона проекта с версией установленного
|
||
плагина** и говорит, кто отстал. Это нужно и без вендоринга: маркетплейс —
|
||
git-клон, обновляется явно, и на момент разбора отставал на четыре коммита.
|
||
37. **Установленный маркетплейс требует обновления перед любой работой** —
|
||
сейчас в нём нет ни `av-dev-tasks`, ни `av-dev-pipeline`. Это первый шаг
|
||
выката (тема 8), иначе проверять будет нечего.
|
||
|
||
## 8. Порядок выката (2026-08-03)
|
||
|
||
### Объём
|
||
|
||
Ссылок на бриф — **168 строк в 19 файлах** `av-dev-pipeline`, из них ~48 уходят
|
||
вместе с удаляемыми `project-brief/SKILL.md`, `references/project-brief.md` и
|
||
`references/brief-template.md`. Остальное переписывается на пути канона.
|
||
|
||
### Решено
|
||
|
||
**AA. Инструмент строится целиком, потом проверяется.** Не пилот руками.
|
||
|
||
*Риск принят сознательно:* если замер покажет деградацию severity, чинить
|
||
придётся канон, зашитый к тому моменту в три скилла, скрипт и мигрированные файлы
|
||
healthlog.
|
||
|
||
*Удешевление, которое обязано быть заложено сразу:* **определение канона живёт в
|
||
единственном reference-файле**, который читают `init`, `canon` и `docs`, а не
|
||
повторяется в каждом. Правка канона — одно место плюс запись в журнал версий.
|
||
|
||
*Страховка порядка:* **замер ставится перед переездом jellybit**, а не после
|
||
всего, — он всё ещё блокирует то, что дороже всего откатывать.
|
||
|
||
**BB. Работа ведётся в `docs/tasks/` самого `dev-skills`.** Скилл `tasks` не
|
||
требует ни OpenSpec, ни языка — задачи для него просто каталог markdown. Цели —
|
||
крупные куски, задачи — следствия. Заодно первая боевая обкатка собственного
|
||
инструмента.
|
||
|
||
**CC. `AGENTIC-TASKS.md` сжимается до истории решений и переезжает в
|
||
`dev-skills`** отдельным `HISTORY.md`: почему не Scrum, числа первого замера, что
|
||
отвергнуто и почему. Он описывает процесс, а процесс живёт здесь, не в healthlog.
|
||
Остальное содержимое уже в плагинах, и второй дом для тех же правил — ровно то,
|
||
против чего документ сам и написан.
|
||
|
||
### Порядок
|
||
|
||
```
|
||
0. обновить установленный маркетплейс предусловие всего
|
||
0.5 завести docs/tasks в dev-skills, разложить 37 следствий по целям
|
||
|
||
1. РЕПОЗИТОРИЙ ПЛАГИНОВ
|
||
1.1 av-dev-tasks → av-dev-pm, пространство имён
|
||
1.2 канон одним reference-файлом — единственный дом определения
|
||
1.3 правки tasks и session: слоты, «Стимулы», .pm.json
|
||
1.4 новые init, canon, docs + docs.py
|
||
1.5 av-dev-pipeline: удалить project-brief, снять ветки деградации OpenSpec,
|
||
переписать шаг 9, девять charter'ов, promote.md, убрать слот
|
||
1.6 av-dev-backlog устаревшим; README; журнал канона v1; HISTORY.md
|
||
1.7 REMAINING.md пересобрать — часть его вопросов закрыта этим разбором
|
||
|
||
2. HEALTHLOG — первая боевая проверка инструмента
|
||
canon adopt, заполнение канона, security.md, review.md, ADR,
|
||
маркеры в architecture.md, docs.py check в гейте
|
||
|
||
3. КАЛИБРОВКА на четырёх находках healthlog БЛОКИРУЕТ шаг 5
|
||
|
||
4. один-два спринта healthlog на новом процессе
|
||
|
||
5. JELLYBIT — переезд, удаление дублей specs, растворение drafts
|
||
```
|
||
|
||
### Что из этого следует
|
||
|
||
38. **`REMAINING.md` частично устарел:** пункт 2 «Завести брифы» отменён темой 3;
|
||
закрыты открытые вопросы про `## Триггеры`, `av-dev-backlog`, имя процесса и
|
||
`AGENTIC-TASKS.md`. Пункт 1 (калибровка) стал обязательным, а не
|
||
желательным. Пересобрать на шаге 1.7.
|
||
39. **Замер — единственный шаг, который нельзя переставить.** Всё остальное в
|
||
порядке 1–5 можно тасовать; шаг 3 стоит перед шагом 5 жёстко.
|
||
|
||
## 9. Линтеры скриптов (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Три скрипта на python, 3600 строк, ни одной проверки. `tasks.py` — 2450 строк,
|
||
которые ходят по файловой системе, переименовывают и удаляют файлы задач.
|
||
Требование к самим скриптам прежнее и не обсуждается: **голый `python3` 3.12,
|
||
ноль внешних зависимостей** — они лежат рядом со скиллами и запускаются в
|
||
чужом проекте, где ничего ставить нельзя.
|
||
|
||
### Решено
|
||
|
||
**DD. `pyproject.toml` в корне `dev-skills`, зависимости через `uv`.** Файл
|
||
живёт только здесь и не уезжает никуда: он держит **линтеры**, а не зависимости
|
||
скриптов. Скрипты остаются запускаемыми любым `python3` — это проверено прогоном
|
||
всех операций через `/usr/bin/python3`, а не через `.venv`.
|
||
|
||
**EE. Ноль зависимостей охраняется двумя способами, и главный — второй.**
|
||
`banned-api` у ruff ловит частые соблазны по имени (`requests`, `yaml`,
|
||
`pydantic`, `click`, `rich`) — список заведомо неполный. Настоящий страж —
|
||
pyrefly: в окружении нет ничего, кроме линтеров, поэтому **любой** сторонний
|
||
импорт у него не разрешается. Первый способ даёт понятное сообщение, второй —
|
||
полноту.
|
||
|
||
**FF. Версии линтеров прибиты точно** (`ruff==0.16.1`, `pyrefly==1.2.0`) плюс
|
||
`uv.lock` в git. Обновление линтера меняет набор находок, а находки правятся
|
||
руками в скриптах, которые уезжают в чужие проекты. Обновление обязано быть
|
||
отдельной осознанной правкой, а не побочным эффектом `uv sync`.
|
||
|
||
**GG. `RUF001`–`RUF003` выключены.** Весь текст скриптов русский: сообщения,
|
||
докстроки, комментарии. «Похожая на латиницу кириллица» здесь норма, а не
|
||
опечатка, и три этих правила давали **311 срабатываний из 338** — шум, в котором
|
||
тонут остальные 27.
|
||
|
||
**HH. `av-dev-backlog` исключён из проверки.** Плагин помечен устаревшим и живёт
|
||
до перевода последнего проекта, после чего удаляется целиком. Шесть его находок
|
||
косметические (`os.replace`, `l` как имя), а правка замороженного кода без
|
||
тестов — риск без выгоды. Исключение уходит вместе с плагином.
|
||
|
||
**II. Голый `except Exception` разрешён только помеченный.** Правило `BLE`
|
||
включено, а два места последнего рубежа (`main` обоих скриптов, код выхода 4 по
|
||
словарю) несут `# noqa: BLE001` с причиной. Так третий такой except не
|
||
появляется молча.
|
||
|
||
### Что из этого следует
|
||
|
||
40. **Найдено и починено 27 находок ruff и 14 pyrefly.** Содержательных две:
|
||
мёртвая переменная `ques` в `check` (вычислялась и не использовалась —
|
||
вопросы проверяет `questions_open`) и два места в `check --fix`, где
|
||
`find_entry_index` может вернуть `None`, а результат идёт прямо в
|
||
`list.pop` и в `range`. Оба сегодня недостижимы, и недостижимость держалась
|
||
на рассуждении о вызывающем коде, а не на проверке.
|
||
*Поправлено по ревью:* там стоит `raise`, а не `continue`. Тихий пропуск
|
||
превратил бы сломанный инвариант в отчёт «индексы согласованы» — то есть в
|
||
враньё; громкий отказ кодом 4 честнее.
|
||
41. **`os` из `tasks.py` ушёл целиком.** `os.replace` → `Path.replace`,
|
||
`os.path.basename` → `Path.name`; импорт стал не нужен.
|
||
42. **`fail()` в `docs.py` объявлен `NoReturn`.** Без этого `read_config`
|
||
выглядел как возвращающий неинициализированное значение — и это ровно то,
|
||
что читатель кода тоже не мог знать наверняка.
|
||
43. **Проверка не входит ни в один гейт.** CI у репозитория нет, хука нет;
|
||
запускается руками командой из README. Заводить хук ради двух скриптов,
|
||
которые правятся раз в месяц, — плата ритуалом без выгоды.
|
||
|
||
## 10. Ревью готовых плагинов двумя проходами (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Два независимых сабагента `fable` — по одному на `av-dev-pm` и `av-dev-pipeline`.
|
||
**20 находок, из них две найдены обоими независимо.** Прошлые три круга ревью
|
||
шли по одному проходу на всё; два прохода с разными предметами дали и больший
|
||
урожай, и перекрёстное подтверждение самого дорогого дефекта.
|
||
|
||
### Что оказалось сломано по существу
|
||
|
||
**JJ. Перестановка закрытия за коммит (решение из темы 8) сломала `reopen` и
|
||
батч — и это нашли оба прохода.** `close --implemented` печатает «дорога назад:
|
||
файл восстанавливается из git», а `reopen` искал **коммит удаления**, которого в
|
||
новом порядке ещё нет: шаг 11 идёт последним, и учёт остаётся незакоммиченным.
|
||
Проверено прогоном: `reopen` отказывал кодом 2 на свежезакрытой задаче — то есть
|
||
в самом вероятном своём применении. Тем же грязным деревом ломался `task-batch`:
|
||
`git rebase` и `git worktree remove` отказывают, и **каждая успешно закрывшая
|
||
задачу ветка** уезжала бы в провалившиеся.
|
||
|
||
Починено с обеих сторон: `reopen` берёт текст из `HEAD`, если коммита удаления
|
||
нет, а шаг 11 обязан **коммитить учёт вторым коммитом** — иначе закрытие не
|
||
доезжает до основной ветки и опора «`SPRINT.md` под git» остаётся словами.
|
||
|
||
**KK. Канонический пример `docs/.pm.json` убивал `tasks.py`.** `canon.md`,
|
||
`skeletons.md`, `tasks/SKILL.md` и `adopt.md` показывали ключ `tasks.sections`,
|
||
которого скрипт не знает: `_validate_config` отвергает неизвестные ключи кодом 3
|
||
на **любой** команде. Проект, заведённый по канону дословно, остался бы без
|
||
работы с задачами целиком — а `docs.py check` при этом печатал «канон соблюдён»,
|
||
потому что чужой код 3 уходит в «не проверялось». Секции живут в заголовках `##`
|
||
индекса и второго дома не получают.
|
||
|
||
### Что из этого следует
|
||
|
||
44. **Класс находок тот же, что и в прошлые три круга: стыки.** Не новый код, а
|
||
место, где один файл ссылается на другой. `sprint.md` в пункте «Сделана» всё
|
||
ещё отсылал к порядку, который сам же тремя экранами ниже отменил; три
|
||
остатка «шаг 9а» несли **предкоммитную** позицию закрытия; путь отчёта
|
||
триажа не переживал `opsx:archive`, хотя по нему сверяют полноту ревью
|
||
четверо.
|
||
45. **Инструкция, которую нельзя выполнить, выглядит как выполненная.** Ответ на
|
||
вопрос по документированной процедуре (снять тег) оставлял задачу
|
||
незабираемой, потому что судит **раздел**, а не тег; `canon adopt` требовал
|
||
гнать `docs.py check` «до отсутствия дрейфа», недостижимого без нарушения
|
||
запрета сочинять цели; урожай спринта, заведённый после `sprint close`,
|
||
терял автотег молча.
|
||
46. **Два прохода по разным предметам дороже одного, но не вдвое.** Перекрытие
|
||
оказалось ровно в одной находке из двадцати — той самой, что подтвердилась
|
||
дважды. Практика остаётся: ревью на плагин, а не одно на репозиторий.
|
||
|
||
## 11. Зависимости между плагинами (2026-08-03)
|
||
|
||
### Целевая картина, которую проверяли
|
||
|
||
`av-dev-git` ни от чего не зависит. `av-dev-pipeline` сам по себе: задача
|
||
приходит **и обычным текстом**, и из `tasks`. `av-dev-pm` оперирует абстрактным
|
||
«сделать задачу» и не знает, чем она выполняется.
|
||
|
||
### Что показала проверка
|
||
|
||
**LL. Первые две цели выполняются, третья в исходной формулировке недостижима —
|
||
и формулировку надо поправить, а не картину.** `av-dev-pm` **владеет
|
||
конфигурационным файлом конвейера**: `docs/review.md` держит «Вопросы к
|
||
проходам» и «Триггеры профиля», то есть перечисляет проходы поимённо, а скелет
|
||
`review.md` несёт форму журнала дефектов. Кто-то этим словарём владеть обязан —
|
||
канон и есть схема данных, которую конвейер читает. Честная формулировка цели:
|
||
**`av-dev-pm` не зовёт пайплайн и не требует его наличия**. Она выполняется.
|
||
|
||
**MM. Настоящая протечка была одна — необъявленная деградация опор приёмки.**
|
||
«Стимулы» в `session` и приёмка в `sprint.md` держались на «сохранённом отчёте
|
||
триажа» по конкретному OpenSpec-пути. В проекте без конвейера ревью защита от
|
||
занижения урожая исчезала **молча**: сверять не с чем, а текст об этом не
|
||
говорил. Теперь опора названа абстрактно («независимый отчёт ревью»), путь
|
||
`av-dev-pipeline` дан как частный случай, а отсутствие конвейера обязано
|
||
попадать строкой в доклад спринта.
|
||
|
||
**NN. Ветка деградации шага 9 была неисполнима — ровно в том случае, ради
|
||
которого написана.** «Плагина нет — открой
|
||
`av-dev-pm/skills/canon/references/canon.md`»: путь в дерево маркетплейса, из
|
||
проекта без установленного плагина не разрешается ниоткуда. Кросс-плагинные
|
||
пути в дерево маркетплейса теперь не используются вообще: пайплайн ходит в
|
||
**свой** `references/project-facts.md`, а ссылки в чужой плагин даются через
|
||
`Skill <плагин>:<скилл>`.
|
||
|
||
### Что из этого следует
|
||
|
||
47. **Знаниевый цикл есть и он законен, но каждый его контракт обязан иметь
|
||
единственный дом.** Пайплайн описывает раскладку `pm`, `pm` описывает
|
||
артефакты пайплайна — пять симметричных контрактов, из них два уже
|
||
разошлись: форма журнала дефектов (шесть полей против пяти, «Причина»
|
||
потеряна) и список читателей `docs/research/` (`specs` выпал). Дома
|
||
назначены: форма журнала — у конвейера, список читателей — у канона; в обеих
|
||
копиях стоит явное указание на дом.
|
||
48. **Пайплайн больше не называет внутренние имена файлов `pm`.** `items/<slug>.md`
|
||
и `SPRINT.md` в его тексте были вторым домом для раскладки, которую проект
|
||
вправе переименовать через `docs/.pm.json`.
|
||
49. **Описания плагинов в манифестах врали умолчанием.** Ни `marketplace.json`,
|
||
ни `plugin.json` не говорили, что `av-dev-pm` для конвейера **опционален**, а
|
||
задача принимается текстом. Теперь говорят — это первое, что читает человек,
|
||
выбирая, что подключать.
|
||
|
||
## 12. Механическая проверка копий (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Разделение плагинов оставлено (тема 11), но цена его названа: пять симметричных
|
||
контрактов в двух домах, два уже разошлись — форма журнала дефектов потеряла в
|
||
копии поле «Причина», список читателей `docs/research/` потерял `specs`. Оба раза
|
||
копия выглядела актуальной, и оба раза расхождение прошло мимо трёх ревью подряд.
|
||
|
||
### Решено
|
||
|
||
**OO. Копия допустима, но обязана быть дословной и помеченной.** Разметка —
|
||
HTML-комментарии, невидимые в отрендеренном markdown: `<!-- дом: <id> -->` …
|
||
`<!-- /дом: <id> -->` и `<!-- копия: <id> из <путь> -->` … `<!-- /копия: <id> -->`.
|
||
`scripts/copies.py` требует побайтового совпадения текста между маркерами.
|
||
|
||
*Почему комментарии, а не манифест копий отдельным файлом:* маркер уезжает в
|
||
репозиторий проекта вместе со скелетом, и там он **полезен** — говорит читателю,
|
||
что у текста есть дом и правится он там. Манифест остался бы в маркетплейсе и
|
||
проекту ничего не сказал.
|
||
|
||
**PP. Идентификатор строгий — буквы, цифры, дефис — и повторяется в закрывающем
|
||
маркере.** Иначе документация о самом механизме объявляет дом и роняет проверку:
|
||
это случилось на первом же прогоне, `README.md` объявил дом примером. Теперь
|
||
пример пишется `<id>`, угловые скобки под шаблон не подходят.
|
||
|
||
**QQ. Ограда блока кода в сверку не входит.** В доме текст обрамлён своей ```,
|
||
а в скелете тот же текст лежит внутри чужой, объемлющей ограды. Сверяется
|
||
содержимое, а не разметка вокруг него.
|
||
|
||
**RR. Коды выхода — общий словарь** (0 сошлось, 1 расхождение, 2 разметка,
|
||
3 не тот каталог, 4 сбой). Третий скрипт репозитория, и третий по тем же кодам.
|
||
|
||
### Что из этого следует
|
||
|
||
50. **Помечены два контракта:** форма записи журнала дефектов (дом — конвейер
|
||
ревью, копия — скелет канона; это кросс-плагинная пара) и «когда заводить
|
||
ADR» (дом — канон, копия — его же скелет). Второй пришлось сперва **сделать**
|
||
дословным: копия говорила «обязателен статус», дом — «обязателен статус
|
||
„заменено на"», и это ровно тот класс, который и ищется.
|
||
51. **Чего проверка не ловит — копию, которую забыли пометить.** Помечать
|
||
остаётся решением человека, и это названо в `README.md` вслух: иначе зелёный
|
||
прогон читался бы как «копий больше нет».
|
||
52. **Дом без копий — расхождение, а не замечание.** Маркер, обещающий
|
||
дисциплину, за которой не за чем следить, — такая же ложная запись, как
|
||
разошедшаяся копия.
|
||
53. **Запись в журнал версий канона проверка не заменяет.** Она видит, что копия
|
||
отстала, но не видит, что проект уже унёс старую версию к себе. Это остаётся
|
||
на человеке и сказано в обоих домах.
|
||
|
||
## 13. Секции `PLAN.md` переименованы (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Секции назывались **«линия»** и **«кусты»** — метафора, требующая расшифровки
|
||
при каждом употреблении. В текстах она и расшифровывалась: «звено упорядоченной
|
||
линии продукта», «тематический куст — цель, в последовательность не встающая».
|
||
Если название приходится объяснять рядом с каждым употреблением, объясняет не
|
||
название.
|
||
|
||
### Решено
|
||
|
||
**SS. «порядок» и «темы».** Заголовок называет ровно то свойство, которым секции
|
||
различаются: в первой очередь значима и обоснована прозой, во второй порядка нет
|
||
вовсе. Расшифровывать нечего — правило написано в самом имени.
|
||
|
||
**TT. Записи в журнал версий канона не требуется — канон этих имён не знает.**
|
||
`canon.md` называет файл `docs/tasks/PLAN.md` и ничего не говорит о его секциях:
|
||
их дом — заголовки `##` индекса, а умолчание живёт в `tasks.py`. Версия канона
|
||
поэтому не меняется, и проект вправе называть секции по-своему. Причина названа
|
||
вслух, потому что соблазн повысить версию «на всякий случай» здесь сильный, а
|
||
повышение обязало бы каждый проект что-то делать — при том что делать нечего.
|
||
|
||
### Что из этого следует
|
||
|
||
54. **Умолчание одно и живёт в `DEFAULT_PLAN_SECTIONS`.** Имена секций
|
||
по-прежнему настраиваются `--plan-sections`, а домом остаются заголовки `##`
|
||
индекса — переименование не трогает механику, только умолчание и тексты.
|
||
55. **Метафора — плохое имя для секции индекса.** Секция читается человеком без
|
||
контекста, часто из вывода `list`, и второго шанса объяснить себя у неё нет.
|
||
|
||
## 14. Умолчания режимов прогона перевёрнуты (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Оба скилла держали одно и то же умолчание — «по очереди», — хотя цена очереди у
|
||
них разная. `review-pipeline` гнал проходы последовательно и требовал для
|
||
параллельности **двух** условий (явная просьба **и** поимённо названный набор).
|
||
`task-batch`, наоборот, планировал волны параллельных задач с потолком 2–3 и
|
||
считал параллельность нормой прогона.
|
||
|
||
Перепутаны оказались уровни. Проход ревью — чтение и рассуждение: он ничего не
|
||
поднимает, ни за что не дерётся и по построению не видит выводов соседа. Задача
|
||
батча — полный цикл пайплайна: гейт, поднятие сервиса вживую, вложенное ревью,
|
||
общие порты и рабочие каталоги. Дешёвое стояло в очереди, дорогое гонялось разом.
|
||
|
||
### Решено
|
||
|
||
**UU. В ревью умолчание — параллельно.** Стадии по-прежнему идут по порядку,
|
||
параллельность касается только проходов внутри стадии. Последовательно гоняем по
|
||
трём особым причинам, и каждая называется в отчёте: сказал оператор; проходы
|
||
меряют; машина занята — причём занятость видит вызывающий, а не конвейер. Просьба
|
||
«гони последовательно» **набора не требует**: очередь ничего не портит, она
|
||
только дольше, и домысливать тут нечего — в отличие от прежнего правила, где
|
||
неназванный набор блокировал отступление.
|
||
|
||
**VV. Меряющая пара — правило стадии, а не решение прогона.** `adversary` и `ops`
|
||
идут по очереди всегда: оба доказывают находки числами и оба меряют одно железо, а
|
||
испорченный оракул хуже отсутствующего. Общее «гони параллельно» этого не
|
||
отменяет; отменяет только прямое слово оператора **про эту пару**, и тогда в
|
||
границы покрытия идёт строка про замеры под соседней нагрузкой.
|
||
|
||
**WW. В батче умолчание — по одной задаче, параллельность — по графу
|
||
зависимостей.** План собирается как граф (рёбра — жёсткие зависимости и
|
||
сериализуемые пересечения) и в умолчании линеаризуется в один порядок. Просьба
|
||
«гони параллельно» разрешает использовать **ширину графа**, а не гнать всё разом:
|
||
потолок 2–3, замеряющая задача — волной по одной. Прежние правила волн сохранены
|
||
целиком, они просто перестали быть умолчанием.
|
||
|
||
### Что из этого следует
|
||
|
||
56. **Режим батча задаёт режим ревью внутри задачи, и его называет charter.**
|
||
Батч идёт по одной — машина свободна, сабагент гонит проходы параллельно;
|
||
батч идёт волнами — сабагенту предписан последовательный режим с этой самой
|
||
причиной. Сабагент своего соседа не видит, поэтому решать это ему нельзя.
|
||
57. **Ранний выход из ревью переехал на границу стадии.** Стадии идут по порядку
|
||
в любом режиме, так что остановиться между ними можно всегда; остановка
|
||
**внутри** стадии осталась побочной выгодой последовательного режима — но не
|
||
поводом его выбирать.
|
||
58. **Цена параллельного батча проверяется до первой волны.** Тесты, делящие
|
||
фиксированный порт или файл БД, и проект, умеющий поднимать один экземпляр, —
|
||
основание гнать по одной даже после просьбы, сказанное строкой: просьба была
|
||
про параллельность, а не про сломанные тесты.
|
||
|
||
## 15. Порядок проходов ревью — граф зависимостей (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Решение 14 перевернуло умолчание, но оставило порядок в прежней форме: «стадии
|
||
идут по порядку номеров, параллельность — только внутри стадии». Номер стадии при
|
||
этом ничего не означает: между стадиями 1–4 ни один проход не читает вывод
|
||
другого, так что очередь между ними была платой ни за что. А правило про замеры
|
||
держалось на **двух именах** — `adversary` и `ops`, — и рассыпалось бы в тот
|
||
день, когда мерить начнёт третий проход или проект добавит свой.
|
||
|
||
### Решено
|
||
|
||
**XX. Порядок задаёт граф; стадии остаются единицей состава.** Профиль
|
||
по-прежнему набирается стадиями, но запускается всё, у чего закрыты входящие
|
||
рёбра. Рёбер три вида, и смешивать их нельзя: **зависимость** (гейт → все
|
||
опиниативные, все проходы → триаж), **конфликт за ресурс** (ненаправленный, между
|
||
теми, кто держит машину), **барьер стоимости** (только `deep`).
|
||
|
||
**YY. Сериализует ресурс, а не имена.** Пометка «держит машину» — таблицей в
|
||
скилле: `gate`, `adversary`, `ops`, `triage`; читают и рассуждают — `specs`,
|
||
`code`, `reimpl`, `architecture`, `rubric`. Проект вправе пометить свой проход в
|
||
`docs/review.md`; снимать пометку с перечисленных нельзя. Правило теперь
|
||
самораспространяется: начнёт проход мерить — попадёт в цепочку по факту, а не по
|
||
поправке.
|
||
|
||
**ZZ. Ранний выход заменён барьером стоимости.** Он стоит там, где ранний выход
|
||
зарабатывал: перед `reimpl` (пишет реализацию целиком) и `architecture`. В
|
||
`quick`/`standard` барьера нет — стадий 3–4 там не бывает; в `design` нет по
|
||
другой причине — предметом там и является форма, защищать нечего.
|
||
|
||
**AAA. Ребро — это порядок, никогда не данные.** В обычном графе задач ребро
|
||
тянет за собой вывод предшественника; здесь это запрещено: проход, увидевший
|
||
чужие находки, соглашается с ними, и декорреляция — вся ценность конвейера —
|
||
обнуляется. Сказано в самом правиле, потому что графовый словарь провоцирует
|
||
ровно эту ошибку. Исключение одно и оно же сток: триаж.
|
||
|
||
**BBB. Диаграммы в скиллах — `mermaid`.** Граф, описанный прозой, читается как
|
||
инструкция и теряет форму; диаграмма показывает её целиком. В конвейере четыре:
|
||
общий граф прогона, граф профиля `design`, пример графа задач батча, веер
|
||
финальной сверки.
|
||
|
||
**Критерий, где диаграмма уместна: структура — граф или автомат, и проза
|
||
вынуждена его пересказывать.** По этому критерию диаграммы заведены ещё в шести
|
||
местах: жизненный цикл записи по индексам (`tasks`), четыре шага сессии с
|
||
причинами на рёбрах (`session`), исходы задачи в спринте (`sprint.md`), одиннадцать
|
||
шагов пайплайна с развилкой «тривиальная» (`task-pipeline`), храповик промоута с
|
||
обратным ребром (`promote.md`), счётчик `retune` до `drop` (`calibration.md`) и
|
||
граф вызовов между плагинами (`README.md`). Где структура — таблица соответствий
|
||
(чек-лист синка в `docs`, профили ревью, коды выхода), диаграмма не заводится:
|
||
она бы дублировала таблицу и разошлась с ней. Все диаграммы прогоняются через
|
||
`mermaid-cli` перед коммитом — синтаксическая ошибка в блоке не видна при чтении
|
||
и молча ломает рендер.
|
||
|
||
### Что из этого следует
|
||
|
||
59. **Триаж — сток по определению, а не «стадия 5».** Отсюда без отдельного
|
||
обоснования следует правило, которое раньше приходилось защищать: на неполном
|
||
графе триаж не запускается, потому что агрегировал бы половину и выглядел бы
|
||
полным.
|
||
60. **Словарь рёбер общий у ревью и батча.** «Жёсткая зависимость» и
|
||
«сериализуемое пересечение» в `task-batch` — те же два вида рёбер;
|
||
формулировки сведены, и в обоих скиллах стоит ссылка на другой.
|
||
61. **Значения режима стали `по графу` и `линейно`.** Прежние «параллельно» и
|
||
«последовательно» описывали способ запуска, а не структуру; линеаризация
|
||
осталась отступлением с тремя причинами (оператор, занятая машина, разбор
|
||
самого конвейера).
|
||
62. **Проход, держащий машину, знает об этом из своего charter'а.** `adversary` и
|
||
`ops` получили по абзацу: цепочка гарантирует им чистое железо, значит их
|
||
число — оракул, и шум в нём объясняется замером, а не соседом.
|
||
63. **У каждой диаграммы объявлено старшинство — это цена второго дома.** Схема
|
||
и проза вокруг неё описывают один факт, и разойтись они могут молча: то
|
||
самое, против чего написан `copies.py`. Механической сверки здесь нет —
|
||
дословного соответствия между текстом и графом не существует, — поэтому
|
||
работает объявление: **в `review-pipeline` старший граф** (он и есть алгоритм
|
||
планировщика, проза объясняет рёбра), **в остальных местах старшая проза**
|
||
(диаграмма там сводка). Для агента это не философия: без объявления он идёт
|
||
за тем, что конкретнее, то есть чаще за схемой.
|
||
64. **Рендер диаграмм проверяется скриптом, а не памятью автора.**
|
||
`scripts/diagrams.py` вынимает все блоки `mermaid` и гонит их через
|
||
`mmdc` или `npx @mermaid-js/mermaid-cli`; коды выхода — общий словарь, нет
|
||
рендерера — код 3, а не молчаливый успех. Причина та же, что у остальных
|
||
проверок репозитория: **ошибка в блоке не видна при чтении** — текст
|
||
правдоподобен, дифф разумен, падает только рендер. Расхождение с прозой
|
||
скрипт не ловит и не притворяется, что ловит: это работа правила 63.
|
||
|
||
## 16. Каталог вместо файла в `docs/` — отложено до переезда healthlog (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Вопрос: разрешить документам в корне `docs/` быть не только файлом, но и
|
||
каталогом — когда документ описывает несколько принципиальных решений или
|
||
перерастает 400–500 строк. Паспорт остаётся файлом в любом случае: компактность
|
||
и есть его функция.
|
||
|
||
Механизм в каноне уже работает — `conventions/`, `research/`, `adr/` каталоги с
|
||
обязательным `README.md`-индексом, — так что вопрос не «можно ли», а «от чего
|
||
лечим».
|
||
|
||
### Решено
|
||
|
||
**CCC. Порог в строках триггером не становится.** Замер по проектам: у порога
|
||
ровно один документ — `healthlog/docs/architecture.md`, 1662 строки. В нём
|
||
десять маркеров долга, а разделы — «Слои гранулярности» (211 строк), «Тренировки
|
||
и прочие секции» (222), «Условный запрос», «Свёртка и размер ответа», «Форма
|
||
ответа». Это **поведение**, чей нормативный дом `openspec/specs/`, где у проекта
|
||
уже лежат пять capability. Остальные документы 108–438 строк, `jellybit` — 169.
|
||
Порог сработал бы ровно там, где надо не разносить, а доводить переезд, и дал бы
|
||
долгу постоянное жильё: разложить 1662 строки по файлам дешевле, чем вынести их
|
||
в спеки, а после раскладки давление исчезнет и второй дом поведения останется
|
||
навсегда.
|
||
|
||
**DDD. Шов выноса — другой читатель или другой срок жизни, а не размер.** По
|
||
этому критерию кандидатов два. `review.md` — сильнее прочих: у него уже записаны
|
||
два раздела с разными сроками жизни, настройка конвейера стабильна и читается
|
||
проходами, а журнал дефектов растёт неограниченно. `architecture.md` — по шву
|
||
«окружение, деплой, наблюдатель», у которого отдельный читатель `ops`. А вот
|
||
расщепление архитектуры **по принципиальным решениям отвергнуто**: у факта
|
||
«почему решено так» дом `adr/`, и вынесенные разделы немедленно станут его
|
||
вторым домом.
|
||
|
||
**EEE. `security.md` и `passport.md` каталогом не становятся.** У `security.md`
|
||
ценность именно в цельности: периметр первой строкой и «что вне модели» читаются
|
||
враждебным проходом за один раз, а разнесённые — расходятся первыми. У
|
||
`database.md` механизм заводить не под что: 241 и 211 строк.
|
||
|
||
**FFF. Если вводить — точка входа остаётся одна.** `docs/architecture.md`
|
||
упомянут в репозитории 66 раз: девять charter'ов, карта `project-facts.md`,
|
||
`docs.py`, скелеты. Развилка «файл или каталог» размножится на девять «прочитай
|
||
либо обойди». Поэтому форма жёсткая: каталог легален только при
|
||
`<имя>/README.md`, и он **и есть** прежний документ — обзор целиком со ссылками
|
||
на вынесенное, а не оглавление к нему. Каждый файл каталога обязан быть достижим
|
||
ссылкой из `README.md`; это проверяется сегодняшним механизмом ссылок `docs.py`
|
||
и ловит файл-сироту. Вынеся раздел, `README.md` на него **ссылается, а не
|
||
пересказывает** — тот же приём, которым в архитектуре уже описаны компоненты со
|
||
ссылкой на capability.
|
||
|
||
**GGG. Решение отложено до конца переезда `healthlog` (шаг 2 TODO).** Порядок:
|
||
довести поведение в спеки, замерить остаток. Жмёт после этого — вводить каноном
|
||
версии 3, и сразу для `review.md` и `architecture.md`, а не для всех документов
|
||
корня скопом.
|
||
|
||
### Что из этого следует
|
||
|
||
65. **Цена изменения — версия канона, а не правка одного файла.** Обратной
|
||
совместимости у канона нет, поэтому в счёт входят: `docs.py` (`check_stray` с
|
||
его `ALLOWED_FILES`/`ALLOWED_DIRS`, `check_required` — обязательный путь
|
||
становится развилкой, `check_capabilities` — сегодня читает ровно один файл),
|
||
`skeletons.md`, `project-facts.md`, девять charter'ов, запись в
|
||
`changelog.md` канона и ветка `upgrade` в скилле `canon`.
|
||
66. **Раздутый документ канона — сначала подозреваемый, потом кандидат на
|
||
вынос.** Диагностика перед раскладкой — счёт маркеров долга
|
||
(`grep -c "<!-- канон:"`) и вопрос, не поведение ли это. Разложить дрейф по
|
||
файлам значит перестать его видеть.
|
||
67. **Материал для решения даёт `healthlog`, а не `jellybit`.** У второго 169
|
||
строк архитектуры — там вопрос не стоит вовсе, и принимать по нему решение
|
||
значит принимать его без предмета.
|
||
|
||
## 17. Разбор заметок: ступень ревью, род работы, роадмап (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Семь заметок из `NOTES.md`, накопленных по ходу работы: переименование
|
||
`PLAN.md`, тип у каждой задачи, цвета сабагентов по модели, кавычки во
|
||
фронтматтерах, уровни ревью для проекта, задачи в терминах функций и границ,
|
||
язык задач без англицизмов. Разного размера и из разных мест, но три из них
|
||
оказались об одном — **о том, можно ли оценить задачу, не открывая код**.
|
||
|
||
### Решено
|
||
|
||
**HHH. Цвет charter'а кодирует модель, а не роль прохода.** Раскладка
|
||
`sonnet` → green, `opus` → yellow, `fable` → red. Роль прохода видна из имени, а
|
||
стоимость прогона — ниоткуда; цвет, розданный по ролям, не отвечает ни на один
|
||
вопрос, который задают во время прогона. Дом раскладки — таблица «Модель по
|
||
проходу» в `review-pipeline/SKILL.md`.
|
||
|
||
**III. Фронтматтеры проверяются машиной, а не вниманием.** Три описания из
|
||
четырнадцати содержали `: ` в незакавыченном значении — для YAML это вложенное
|
||
отображение, то есть синтаксическая ошибка, которую **нельзя увидеть чтением**:
|
||
текст читается правильно. Тот же класс, что у mermaid-диаграмм, и лечится тем же
|
||
способом — `scripts/frontmatter.py`. Он же держит раскладку цветов (HHH) и
|
||
сверку `name` с именем каталога.
|
||
|
||
**JJJ. Между `standard` и `deep` заведена ступень `wide`.** *(содержание
|
||
триггеров пересмотрено темой 18, TTT: миграция схемы и публичный контракт ступень
|
||
не поднимают.)* Прыжок стоил самого
|
||
дорогого прохода конвейера, а платить приходилось за одну архитектурную находку:
|
||
изменений, которые трогают публичный контракт, но не вводят нового правила
|
||
слияния, — большинство. `wide` — это `standard` плюс `architecture` (вход шире
|
||
диффа, отсюда имя), семь проходов против восьми у `deep`.
|
||
|
||
**KKK. Триггер независимой реализации стал триггером профиля.** Раньше условие
|
||
«изменение вводит новое правило идентичности, слияния или разбора» стояло
|
||
**внутри** `deep`, и профиль означал то семь проходов, то восемь. Реестр состава,
|
||
который «сверяется взглядом до коммита», проверять было нечем: у профиля не было
|
||
одного правильного ответа. Теперь условие выбирает профиль, а `reimpl` в `deep`
|
||
безусловен — и он единственное, чем `deep` отличается от `wide`.
|
||
|
||
**LLL. Барьер стоимости остался только в `deep`.** В `wide` за ним стоял бы один
|
||
дешёвый проход с потолком в 3 находки, а барьер не бесплатен — он сериализует то,
|
||
что могло идти разом. Вторая причина помельче: барьер спрашивает «выживает ли
|
||
форма изменения», а `architecture` — как раз тот, кто на этот вопрос отвечает.
|
||
|
||
**MMM. Род работы — вторая ось типа, и живёт тегом.** Тип записи
|
||
(`goal`/`idea`/`epic`/`task`) отвечает «что это за запись», род
|
||
(`feature`/`fix`/`chore`/`research`) — «какого рода работа». В один префикс их не
|
||
свести: идея бывает *про* функцию, эпик функцией *и является*. Дом — тег
|
||
`kind:<род>`, потому что теги здесь и есть единственный механизм разметки, а
|
||
`list --kind` работает даром. Принятая цена: в строку индекса род не попадает
|
||
(индексы производны), и состав набора по роду виден командой, а не глазами.
|
||
Словарь **закрыт** — открытый разъехался бы на синонимах `bug`/`bugfix`/`fix`.
|
||
|
||
**NNN. У `chore` тест готовности ослаблен честно.** Вопрос «что станет наблюдаемо
|
||
иначе» для обслуживания отвечается разработчику, а не пользователю. Пока рода не
|
||
было, такие задачи либо не заводились, либо придумывали себе пользовательскую
|
||
пользу — и это второе хуже: оно проходит проверку.
|
||
|
||
**OOO. Задача называет границы, а не намерения.** Раздел «Затрагивает» —
|
||
эндпоинт, таблица и миграция, формат на диске, публичный тип пакета. Без него
|
||
задача оценивается по объёму текста, а не по объёму поверхности, и оценка
|
||
систематически занижена ровно там, где текст короткий, а границ много. Механизм
|
||
проверяет **наличие** непустого раздела: полноту перечня машина не видит, и
|
||
делать вид, что видит, хуже, чем не проверять.
|
||
|
||
**PPP. Род и границы требуются к взятию в спринт, а не к заведению.** Тот же
|
||
приём, что уже работает для критериев приёмки, и по той же причине: беклог
|
||
пополняется чаще, чем разбирается, а требование на входе выгоняет в заметки то,
|
||
что должно лежать задачей. `check` о пропаже напоминает замечанием — иначе два
|
||
живых проекта покраснели бы на 98 задачах, заведённых до этого решения.
|
||
|
||
**QQQ. `PLAN.md` → `ROADMAP.md`, вместе с ключом конфига и токенами команд.**
|
||
Слово «план» в репозитории значит три разных вещи — оглавление целей, план
|
||
реализации внутри задачи и `PLAN.json` разовой адаптации. Переименовано всё:
|
||
`tasks.plan` → `tasks.roadmap`, `--index plan` → `--index roadmap`,
|
||
`--plan-sections` → `--roadmap-sections`. Старый ключ в `docs/.pm.json` не
|
||
игнорируется молча — скрипт останавливается и называет переименование.
|
||
|
||
### Что из этого следует
|
||
|
||
68. **Версия канона 3 занята этим изменением.** Отложенное решение темы 16
|
||
(каталог вместо файла в `docs/`) вводится теперь версией **4**, а не 3.
|
||
69. **Род работы ничего не предписывает конвейеру.** Профиль ревью выбирается по
|
||
факту изменения: `chore` бывает миграцией схемы, `fix` — правкой публичного
|
||
контракта. Правило «предписание процесса в теле задачи снимается» родом не
|
||
отменяется, а подтверждается.
|
||
70. **Проверка фронтматтеров — третья проверка репозитория того же класса.**
|
||
Копии, диаграммы, фронтматтеры: всё это ошибки, невидимые при чтении. Класс
|
||
опознаётся по признаку «диff выглядит разумно, а результат ломается», и
|
||
каждый его представитель получает скрипт, а не пункт чек-листа.
|
||
71. **Ступеней профиля четыре, и правило выбора читается сверху вниз.** Первое
|
||
сработавшее условие и есть ответ: правило слияния → `deep`, контракт или
|
||
схема → `wide`, видимое снаружи поведение → `standard`, иначе `quick`.
|
||
|
||
## 18. Ступень поднимает проход, а не риск (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Наблюдение с живых проектов: полный набор проходов гоняется чаще, чем оправдано —
|
||
архитектура и независимая реализация нужны заметно реже, чем запускаются. Развилка
|
||
названа сразу: крупные задачи с частым полным ревью либо мелкие и средние задачи
|
||
со средним ревью. Выбран второй путь.
|
||
|
||
Разбор показал, что размер задач — только половина причины, и не главная.
|
||
|
||
### Решено
|
||
|
||
**RRR. Профиль — максимум по поверхности, а не средневзвешенное.** Условия
|
||
читаются сверху вниз, первое подошедшее отвечает за весь дифф. Значит цена ревью
|
||
растёт быстрее размера задачи: на крупной задаче верхний профиль оплачивается в
|
||
том числе за ту её часть, которая сама по себе была бы `quick`. Это и есть
|
||
механизм, ради которого выбран путь мелких задач.
|
||
|
||
**SSS. Ступень поднимает то, что даёт работу новому проходу, а не то, что кажется
|
||
рискованным.** Правило вывода, по которому спорные случаи решаются без нового
|
||
списка. Проверка нынешних триггеров этим правилом:
|
||
|
||
| Триггер | Кто закрывает | Где этот проход |
|
||
| --- | --- | --- |
|
||
| миграция схемы | `gate` (шаг миграций), `ops` (миграция под потоком, откат при двух версиях) | уже в `standard` |
|
||
| публичный контракт | `specs`, направление `code → spec` | во всех профилях |
|
||
| инвариант проекта | основание для `critical` у любого прохода | во всех |
|
||
| новый пакет, новое понятие | `architecture` | только `wide` |
|
||
| новое правило слияния | `reimpl` | только `deep` |
|
||
|
||
Три верхних триггера не добавляли ни одного прохода — они поднимали ступень «на
|
||
всякий случай». На проекте с базой и эндпоинтами это делало верхнюю ступень
|
||
умолчанием, то есть правило объявляло исключением то, что происходит всегда.
|
||
|
||
**TTT. Миграция схемы, публичный контракт и инвариант уехали в `standard`.**
|
||
`wide` теперь означает ровно одно: изменение вводит **новое понятие или
|
||
структурную единицу** — новый пакет или слой, новая точка входа, второй способ
|
||
делать то, что уже делается, перенос ответственности между узлами. Добавленное
|
||
поле в существующем ответе концептом не является. Это **отменяет часть JJJ темы
|
||
17**: ступень `wide` остаётся, её содержание меняется. Проект, где изменение
|
||
контракта и правда архитектурное (публичный SDK, чужие потребители), поднимает
|
||
его сам в `docs/review.md` — уточнением, а не возвратом прежнего умолчания.
|
||
|
||
**UUU. Чекпоинт `design` получил то же условие.** `review-specs` в режиме «дизайн
|
||
ДО кода» идёт всегда — это самый дешёвый чекпоинт конвейера. `review-rubric` и
|
||
`review-architecture` — только при новом понятии. Причина арифметическая: чекпоинт
|
||
стоит на **каждой** задаче, поэтому при мелкой нарезке три прохода умножаются на
|
||
число задач и становятся самой большой статьёй. Причина по существу та же, что в
|
||
SSS: рубрика на узел без нового понятия порождает свойства уже существующего
|
||
рода, записанные конвенциями и спеками.
|
||
|
||
**VVV. Шов нарезки — граница, за которой падает ступень.** Тест декомпозиции
|
||
отвечает, **допустим** ли разрез; шов отвечает, **где** его провести. Раздел
|
||
«Затрагивает» перечисляет границы; строка, поднимающая ступень выше остальных, и
|
||
есть кандидат на отдельную задачу.
|
||
|
||
**WWW. Костяк из четырёх проходов платится за каждую задачу.** Гейт, спеки, код,
|
||
триаж несокращаемы, поэтому разрез, после которого обе половины остаются в одной
|
||
ступени, делает ревью **дороже**: тот же объём тем же составом, но костяк оплачен
|
||
дважды. Резать — когда разрез снимает дорогой проход с большей части диффа.
|
||
|
||
**XXX. Верхняя ступень задана тестом, а не списком.** «Идентичность, слияние,
|
||
разбор» — формулировка, пришедшая из одного проекта, и в общем виде она не
|
||
читалась: вопрос «как это применить к моему проекту» не имел ответа в тексте.
|
||
Теперь класс задан тремя условиями, независимыми от домена и языка: вариантов
|
||
несколько и оба защитимы; спека между ними не выбирает; неверный выбор не падает,
|
||
а молча меняет смысл данных. Отрицательный тест сильнее положительных — то, что
|
||
красит гейт или роняет запрос, в класс не входит. Три слова остались как **три
|
||
места**, где такие правила водятся (граница входа данных и место их встречи), а
|
||
проект перечисляет свои места в `docs/review.md` — перечень производен от теста и
|
||
не расширяет класс.
|
||
|
||
Оговорка, без которой правило вырождается: триггер — **новое или изменённое по
|
||
существу правило**, а не код рядом с ним. Проект, чей домен и состоит из таких
|
||
правил, иначе оказывался бы в `deep` всегда — та же болезнь, от которой лечилась
|
||
ступень `wide`.
|
||
|
||
### Что из этого следует
|
||
|
||
72. **Порога в числе границ не заводится.** Тот же принцип, что в теме 16 (CCC):
|
||
размер не триггер. Шов проходит по скачку ступени, а не по длине перечня.
|
||
73. **Ступень — признак для планирования, но не запись в задаче.** Строка «делать
|
||
профилем standard» в теле — тот самый второй дом правила выбора, который
|
||
снимает гигиена полей. Профиль выбирает тот, кто видит изменение.
|
||
74. **Дешёвое место заметить разнородную задачу — показ набора спринта.** Там
|
||
«Затрагивает» уже написан, а предложение об изменении ещё не заведено: разрез
|
||
стоит одного `edit` вместо выброшенного предложения.
|
||
75. **Замер остаётся за обкаткой.** Правило выведено из состава проходов, а не из
|
||
статистики прогонов: считать, какая доля задач попадает в каждую ступень,
|
||
можно только на спринтах нового процесса (TODO шаг 4).
|
||
76. **Отсутствие верхней ступени — законное состояние проекта.** Бывают проекты,
|
||
где данные приходят нормализованными, ничего ни с чем не сливается, а внешних
|
||
форматов нет: `deep` там не срабатывает никогда, и придумывать ему повод не
|
||
надо. Раньше это читалось как недонастройка.
|
||
77. **Ступень определяет класс правила, а не вид работы.** Миграция схемы —
|
||
`standard`, но миграция, переносящая данные по правилу («сложить дубли»,
|
||
«привести к одному виду перед сравнением»), несёт правило идентичности и
|
||
потому `deep`. Одно слово в описании задачи попадает в разные ступени — это не
|
||
противоречие, смотрят не на слово.
|