Compare commits

...
10 Commits
Author SHA1 Message Date
av 6251157d8d журнал решений: тема 67 дополнена по итогам ревью
- снята выдуманная ссылка: фраза «цель и приоритет — независимые оси»
  приписывалась теме 19, а стояла в правиле 4 самого скилла;
- перечень отменяемого доведён до полного: тема 19 целиком кроме Р81,
  половина Р83, плюс Р69, Р84, С85, Р101, Р103, Р105, Р106, Р107, Р110,
  Р128 из тем 17, 20, 25, 26, 27, 31;
- Р242 приведён к тому, что команда делает на самом деле; заведены Р244
  (объявление стадии и её смена — разные операции) и С234–С236.
2026-08-13 15:08:43 +03:00
av ed83ec7dc0 задачи: починена смена стадии, разобраны находки ревью плагина
Команда stage была дефектна по шести пунктам, и все шесть подтверждены
прогоном: не звала raw_last (переход оставлял каталог красным), не
переписывала шапку беклога (индекс продолжал объявлять прежнюю стадию),
шла в обход write_config, молча пропускала файлы с непересобираемой метой,
ломалась на беклоге без заголовков и схлопывала полки при первом
объявлении стадии.

Объявление и смена разведены: объявление беклога не трогает вовсе, смена
трогает состав секций только по явному --sections, а слить полки скрипт
не берётся ни в одном случае. Абзац шапки размечен парой «стадия», и
расхождение с конфигом стало обычным дрейфом.

Отказ по недостающей строке индекса запирал запись, пережившую упразднение
роадмапа: edit, close и reopen теперь заводят или пропускают строку сами.
Прочее: регистр stage нормализуется при чтении; --fix снимает мёртвые теги
и у неразобранных записей; move отказывает переставлять сырьё; adopt
держит место сырья; docs.py bump двигает одну запись журнала за раз;
tasks.py получил перечень упразднённых адресов, и гейт наконец видит
собственное упразднение ROADMAP.md.

Запись «Версия 3» переписана по прогону на игрушечном проекте: прежний
порядок шагов был неисполним. Закрыты дыры модели стадий (пересмотр плана
стройки стал сценарием, приёмка отвязана от груминга, from-review,
research и adopt получили развилку по стадии, перечень осей пересчитан) и
находки, старшие этой сессии: review-triage получил режим без метки, три
списка проектных копий сведены к дому с проверяемыми копиями, пять
пересказов правил стали помеченными копиями или ссылками, language.md
перестал объявлять юрисдикцию над чужим плагином.
2026-08-13 15:08:29 +03:00
av 8d8c1656e5 журнал решений: тема 67 — цель упразднена, у проекта появилась стадия
Р237–Р243 и С228–С233: почему цель была зонтиком над параллельными
направлениями и почему у линейного списка работ его нет; почему «Готово»
удалена, а не перенесена; почему стадия объявляется явно и почему её
русское имя — «доработка», а не занятая «поддержка».
2026-08-13 14:26:32 +03:00
av 3849f084be задачи: цель упразднена, у проекта появилась стадия
Тип goal и индекс ROADMAP.md убраны: цель — зонтик над параллельными
направлениями, а у проекта на одного человека список работ линеен. Роадмап
при этом наполовину дублировал беклог, а «что уже умеет» отвечают спеки и
git log индекса. Секция «Готово» удалена, а не перенесена.

Вместо цели — ось «стадия проекта»: build (беклог это план стройки, порядок
строк значит зависимость, секция одна) и support (очередь правок, порядок
значит важность, секции — полки домена). Стадия объявляется ключом
[tasks] stage, меняется командой stage, без неё check отказывает: порядок
строк нечем прочитать.

Ушли теги goal:/decomposed, поле «Секция», раздел «Завершение», флаги
--goal и edit --section. Версия раскладки 2 → 3, перевод проекта расписан
записью журнала.
2026-08-13 14:26:21 +03:00
av 0627199a1a журнал решений: тема 66 — скилл формы вышел из семейства документов
- Р235: префикс называет материал, а `canon` занят формой, общей у всех частей
  проекта; версия стала общей ещё на слиянии, и с того дня `doc-` спорил с
  механикой;
- Р236: переименование поехало записью журнала версий, хотя в проекте ничего
  не переехало — сломались бы путь в гейте и имя вызова;
- С226 и С227: префикс — проверяемое утверждение о владении материалом;
  переименование скилла есть изменение раскладки, если проект держит его адрес
  у себя.
2026-08-13 12:53:27 +03:00
av dff05ad097 скиллы: doc-canon стал canon, версия раскладки поднята до 2
- каталог скилла и все вызовы переименованы: префикс `doc-` называл материал,
  а скилл занят формой — раскладкой всех частей проекта и общим повышением
  версии, включая каталог задач;
- README перестроен: `canon` вынесен из семейства документов отдельным блоком
  и отдельным узлом графа, правило префиксов переформулировано, у документов
  уточнено владение — содержимым, а не раскладкой;
- заведена запись 2 журнала версий: в проекте ничего не переехало, но путь к
  `docs.py` и имя вызова живут в гейте и в `CLAUDE.md` проекта и сломаются
  молча;
- прежние адреса в записи 1 и в журнале решений оставлены как есть: журнал
  описывает состояния, которые были, и задним числом не переписывается.
2026-08-13 12:53:12 +03:00
av 3529cd8425 удалены TODO.md, REMAINING.md и HISTORY.md
- указатель в README сведён к журналу решений; из decisions/README.md убрана
  строка про остатки, из addresses.py — HISTORY.md в перечне журналов;
- упоминания этих файлов внутри журнала оставлены как есть: он описывает
  прошлые состояния и задним числом не переписывается.
2026-08-13 12:43:09 +03:00
av eae734f5cc гейт: добавлена проверка ссылок и номеров журнала решений
- `decisions.py` судит четыре вещи: уникальность номеров Т/Р/С, раскладку тем,
  указатель и ссылки — цель существует, подпись называет именно её;
- проверка идёт без glob, как адреса: файл темы и ссылки на него лежат порознь,
  и переименование темы трогает только одну сторону, а ломает обе;
- ссылка внутри блока кода ссылкой не считается — в скелетах канона она
  адресована дереву проекта.
2026-08-13 12:41:13 +03:00
av bf6a173115 журнал решений: разложен по теме на файл, метки решений стали номерами
- DECISIONS.md (4040 строк, 65 тем) → decisions/, файл на тему плюс указатель;
- буквенные метки решений заменены сквозными Р1–Р234, следствия получили
  префикс С при прежних номерах: схема букв выродилась до пятибуквенных и
  сломалась — `АЕАКЛ` была занята и темой 53, и темой 65;
- 42 перекрёстные ссылки переписаны под новые номера и стали живыми; где номер
  означал тему, а слово стояло «решение», формулировка исправлена.
2026-08-13 12:40:56 +03:00
av b411d4edb8 оси: перечень получил дом, две бездомные оси переехали в shared
Слияние ничего из идей не тронуло, но сделало дешёвым дом для правила, натянутого
между скиллами. Заведён shared/axes.md — дом перечня, а не значений: девять осей,
их адреса и чего каждая не решает. Механика остаётся у владельца.

Целиком сюда переехали две оси, у которых владельца не было. Коды выхода
объявлялись общим словарём в одиннадцати местах, и каждое объявление называло
свой набор соседей; машина их не сверяла, потому что copies.py смотрит markdown,
а перечни лежали в docstring'ах. Теперь дом один, скрипты держат указатель, а три
SKILL.md — помеченную копию, потому что на кодах они ветвятся. Режим прогона
(с меткой, без метки) был размазан по четырём файлам и осью назван не был, хотя
в уставе review-basics задаёт саму возможность запуска.

Разведены два значения слова «стадия»: ступени 1-5 внутри прогона кода, стадии
дизайна и кода снаружи.

Карта нашла ошибку в себе: клетка «категория документа × метка» пустой не была —
review-basics приёмник проектных тем при любой метке. Пустой оказалась соседняя:
на прогоне без метки план фиксирован, и своих тем проекта в нём нет вовсе.
Обе оставшиеся пустоты названы вслух, а не заполнены наугад.
2026-08-13 12:16:38 +03:00
129 changed files with 6918 additions and 6236 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
{ {
"name": "av-dev", "name": "av-dev",
"source": "./av-dev", "source": "./av-dev",
"description": "Личный процесс разработки одним плагином: документы проекта, учёт работ и работа по задачам. Документы — канон раскладки (CLAUDE.md плюс docs/: паспорт, архитектура, схема БД, безопасность, конвенции, разведка, ADR, журнал ревью), три операции одной машиной сравнения в doc-canon (check, adopt, upgrade) со скриптом docs.py, заведение нового проекта интервью в doc-init, ведение содержимого по ходу разработки в doc-sync, суд о смысловом здоровье в doc-healthcheck двумя агентами разом. Учёт — задачи и цели каталогом markdown-файлов в task-track, у записи тип (goal, feature, fix, chore, research), и тип решает её схему; приоритет расставляет интерактивный task-groom порядком строк. Работа — одна задача от постановки до закрытия скиллом code-resolve: точка входа одна, сценария три (решение циклом SDD с чекпоинтом, разведка без кода, обслуживание без change), и выбирает сценарий сам скилл, прочитав постановку; плюс конвейер ревью code-review с детерминированным гейтом, сверкой со спеками и обязательным триажем, плюс code-openspec, который заводит и проверяет openspec/. Версия раскладки и настройки живут в .av-dev.toml в корне репозитория. Части включаются следом в проекте, а не установкой: нет docs/ — документы не ведутся, нет каталога задач — учёт остаётся владельцу, нет openspec/ — его заводит сценарий решения. Коммиты — отдельный плагин av-dev-git." "description": "Личный процесс разработки одним плагином: форма раскладки, документы проекта, учёт работ и работа по задачам. Форму держит скилл canon — три операции одной машиной сравнения (check, adopt, upgrade) со скриптом docs.py и общим журналом версий, по которому повышается вся раскладка, включая каталог задач; имя без префикса, потому что форма общая у всех частей. Документы — канон раскладки (CLAUDE.md плюс docs/: паспорт, архитектура, схема БД, безопасность, конвенции, разведка, ADR, журнал ревью), заведение нового проекта интервью в doc-init, ведение содержимого по ходу разработки в doc-sync, суд о смысловом здоровье в doc-healthcheck двумя агентами разом. Учёт — задачи каталогом markdown-файлов в task-track: у задачи тип (feature, fix, chore, research), задающий её схему, а у проекта — стадия (build — беклог это план стройки от базы к деталям, порядок строк значит зависимость; support — очередь правок, порядок значит важность); очередь расставляет интерактивный task-groom порядком строк. Работа — одна задача от постановки до закрытия скиллом code-resolve: точка входа одна, сценария три (решение циклом SDD с чекпоинтом, разведка без кода, обслуживание без change), и выбирает сценарий сам скилл, прочитав постановку; плюс конвейер ревью code-review с детерминированным гейтом, сверкой со спеками и обязательным триажем, плюс code-openspec, который заводит и проверяет openspec/. Версия раскладки и настройки живут в .av-dev.toml в корне репозитория. Части включаются следом в проекте, а не установкой: нет docs/ — документы не ведутся, нет каталога задач — учёт остаётся владельцу, нет openspec/ — его заводит сценарий решения. Коммиты — отдельный плагин av-dev-git."
}, },
{ {
"name": "av-dev-git", "name": "av-dev-git",
-3985
View File
File diff suppressed because it is too large Load Diff
-85
View File
@@ -1,85 +0,0 @@
# Как процесс дошёл до текущей формы
Сжатие черновика `AGENTIC-TASKS.md` (497 строк), лежавшего незакоммиченным в
корне healthlog. Правила процесса из него переехали в плагины и здесь **не
повторяются** — второй дом для тех же правил ровно то, против чего документ и
был написан. Остаётся то, чего в плагинах нет и быть не должно: **что отвергнуто
и почему, и числа первого замера**.
Решения текущего круга разбора — [DECISIONS.md](DECISIONS.md).
## Что отвергнуто и почему
### Scrum целиком
Терминология близка — спринт, груминг, определение готовности, ретроспектива, —
и она удобна: не нужно изобретать слова. Но добрая половина Scrum существует ради
синхронизации людей, которых здесь нет: исполнителей двое, человек и агент.
**Не взято:** тайм-бокс (спринт ограничен объёмом, а не временем), velocity и
оценки в очках, ежедневный стендап (стендап — это и есть диалог), планирование
отдельно от груминга (владелец беклога один), роль скрам-мастера.
**Взято:** цель спринта, заморозка набора, определение готовности, груминг —
каждое потому, что снимает решение, которое иначе принимается заново каждый раз.
**Ретроспектива взята содержанием, но не отдельным ритуалом**: она шаг той же
сессии. Отдельная встреча ради трёх вопросов — плата ритуалом без выгоды.
### Приоритеты у задач
Заменены целью. Ни секциями, ни списком: «что делать дальше» отвечает набор
спринта, а между спринтами порядок не нужен никому — брать задачи вне спринта
запрещает заморозка. Отсюда нет ни «повысить», ни «встать раньше»: вместо
повышения — смена цели или включение в набор.
### Секция «блокеры» в беклоге
Блокер — **состояние** (спринт не может продолжаться ни одной задачей), а не
полка: он живёт ровно до ответа человека, и записи в такой секции не успевают
жить. Основание измерено: **два «блокера» из двух ничего не блокировали** — в
обоих файлах записано «что заблокировано: ничего». Отсюда разделение вопроса и
блокера.
### Запись о сделанной задаче
У сделанной задачи записи не остаётся: файл и строка удаляются. Ей хватает
коммита и документации; вторая запись была бы вторым домом для того же факта.
Вопрос «что было в спринте N» отвечается даром — `SPRINT.md` лежит под git.
## Числа первого замера
Одна сессия, шесть закрытых задач. **Выборка нетипичная, статус — первый
замер.** Приведены не как константы, а чтобы следующий замер было с чем
сравнить.
- **Беклог вырос с 29 до 38**: заведено 15, закрыто 6 (две родились и умерли
внутри сессии). Прирост **2,5 задачи на одну закрытую** — ревью и
эксплуатационные проходы производят работу быстрее, чем мы её потребляем.
- **Одна из шести задач была внеплановой** — дозакрытие находок, вставленное в
ход работы, потому что дефект затирал маршрут тренировки необратимо, а
пересборка журнала повторяла то же поражение. Отсюда класс «необратимый
ущерб» как единственное, что врывается в замороженный спринт: правило не
придумано, оно уже применялось.
- **15 часов на шесть задач**: пять заняли от 1 ч 16 мин до 2 ч 14 мин (медиана
≈ 1 ч 55 мин), шестая — 5 ч 42 мин в два захода. Мерилось **до** сужения
конвейера ревью; замер устарел и подлежит повторению.
- **Шесть задач за сессию** — предел одного контекста, а не спринта. Спринт
сессией не ограничен, перенос числа условен.
Умолчание «5–8 задач в спринте» выведено отсюда и остаётся **ориентиром, а не
законом**. Пересматривается на разборе прошедшего спринта — шаг 2 сессии, и ничей
другой.
## Что из черновика было не решено и решено позже
| Вопрос черновика | Где решён |
| --- | --- |
| название процесса | решение Z: имени нет, процесс это `av-dev` |
| «Ближайшая цель» прозой в `docs/plan.md` как второй дом цели спринта | решение E: `plan.md` растворяется в `PLAN.md` целей |
## Судьба самого черновика
Документ описывал процесс, а процесс живёт в плагинах этого репозитория, не в
healthlog. Содержимое разошлось: правила — в `av-dev-pm:tasks` и
`av-dev-pm:session`, обоснования и числа — сюда. Оригинал в git не коммитился и
удаляется при переезде healthlog на канон.
+64 -34
View File
@@ -3,45 +3,52 @@
Личный маркетплейс плагинов для разработки. Процесс имени не имеет — он и есть Личный маркетплейс плагинов для разработки. Процесс имени не имеет — он и есть
`av-dev`. `av-dev`.
Что решено и почему — [DECISIONS.md](DECISIONS.md). Что осталось сделать — Что решено и почему — [журнал решений](decisions/README.md).
[TODO.md](TODO.md) и [REMAINING.md](REMAINING.md). Как процесс дошёл до текущей
формы — [HISTORY.md](HISTORY.md).
## Плагины ## Плагины
Плагина два: **av-dev** — весь процесс, и **av-dev-git** — сообщения коммитов. Плагина два: **av-dev** — весь процесс, и **av-dev-git** — сообщения коммитов.
Второй работает в любом репозитории, в том числе не приведённом к канону. До 13 августа 2026 процесс жил тремя плагинами Второй работает в любом репозитории, в том числе не приведённом к канону. До 13 августа 2026 процесс жил тремя плагинами
(`av-dev-docs`, `av-dev-tasks`, `av-dev-code`); раскол делался под раздельную (`av-dev-docs`, `av-dev-tasks`, `av-dev-code`); раскол делался под раздельную
установку, она не понадобилась ни разу, и плагины слились — тема 64 установку, она не понадобилась ни разу, и плагины слились —
[DECISIONS.md](DECISIONS.md). [тема 64](decisions/64-three-plugins-merged.md) журнала решений.
Имя **скилла** несёт префикс прежнего плагина: `doc-`, `task-`, `code-`. Вызов Имя **скилла** несёт префикс материала, с которым он работает: `doc-`, `task-`,
выходит вида `/av-dev:<скилл>`. `code-`. Вызов выходит вида `/av-dev:<скилл>`. Префикса нет ровно у одного —
`canon`: он работает не с материалом, а с **формой**, общей у всех частей
проекта.
### av-dev — документы, учёт, работа ### av-dev — форма, документы, учёт, работа
**Документы проекта.** Владеют `docs/` и `CLAUDE.md`. **Форма раскладки.** Одна на весь проект, и держит её один скилл.
- `canon` — раскладка проекта и её обновление: `check` / `adopt` / `upgrade`,
плюс скрипт `docs.py`. `check` сверяет раскладку документов, `adopt` заводит
все части сразу и зовёт владельцев каталога задач и `openspec/`, `upgrade`
повышает **всю** раскладку по журналу версий — общему, и на документы, и на
каталог задач. Содержимого он не ведёт: это соседние скиллы.
**Документы проекта.** Владеют **содержимым** `docs/` и `CLAUDE.md`; раскладка —
у `canon`.
- `doc-init` — новый проект: интервью по свободному описанию замысла → - `doc-init` — новый проект: интервью по свободному описанию замысла →
первичная документация; первичная документация;
- `doc-canon` — привести проект к канону документов: `check` / `adopt` /
`upgrade`, плюс скрипт `docs.py`. Он же ведёт журнал версий раскладки —
общий, и на документы, и на каталог задач;
- `doc-healthcheck` — здоровье документации **судом, а не машиной**: не - `doc-healthcheck` — здоровье документации **судом, а не машиной**: не
разошлись ли документы между собой и с кодом. Зовёт двух агентов на весь канон разошлись ли документы между собой и с кодом. Зовёт двух агентов на весь канон
разом — `doc-consistency` (документы между собой и с openspec) и разом — `doc-consistency` (документы между собой и с openspec) и
`doc-code-drift` (факты против кода) — и разбирает урожай порциями. Дорого, `doc-code-drift` (факты против кода) — и разбирает урожай порциями. Дорого,
поэтому не на каждой задаче. Язык документов вычитывает отдельный агент поэтому не на каждой задаче. Язык документов вычитывает отдельный агент
`doc-wording`, и зовут его не отсюда, а те, кто только что писал текст: `doc-wording`, и зовут его не отсюда, а те, кто только что писал текст:
`doc-sync`, `doc-init` и `doc-canon`; `doc-sync`, `doc-init` и `canon`;
- `doc-sync` — содержимое канона по ходу разработки: ADR из архивного - `doc-sync` — содержимое канона по ходу разработки: ADR из архивного
`design.md`, промоут конвенций, запись в разведку и журнал ревью, чистка `design.md`, промоут конвенций, запись в разведку и журнал ревью, чистка
архитектуры. архитектуры.
**Учёт работ.** Владеет каталогом задач. **Учёт работ.** Владеет каталогом задач.
- `task-track` — задачи и цели каталогом markdown-файлов, у каждой записи тип - `task-track` — задачи каталогом markdown-файлов: у каждой тип (`feature`,
(`goal`, `feature`, `fix`, `chore`, `research`), и тип задаёт её схему; `fix`, `chore`, `research`), задающий её схему, а у проекта — стадия (`build`
или `support`), решающая, что значит порядок строк беклога;
вычитывают их два отдельных прохода: `task-form` (форма записи) и вычитывают их два отдельных прохода: `task-form` (форма записи) и
`task-wording` (язык записей); `task-wording` (язык записей);
- `task-groom` — груминг беклога: что сейчас самое важное и что перестало быть - `task-groom` — груминг беклога: что сейчас самое важное и что перестало быть
@@ -114,10 +121,10 @@ flowchart TB
tp["code-resolve<br/>3 сценария: разведка,<br/>решение, обслуживание"] --> rp["code-review<br/>10 агентов-проходов"] tp["code-resolve<br/>3 сценария: разведка,<br/>решение, обслуживание"] --> rp["code-review<br/>10 агентов-проходов"]
osp["code-openspec<br/>заводит и проверяет openspec/"] osp["code-openspec<br/>заводит и проверяет openspec/"]
end end
subgraph docsp["документы, владеют docs/"] canon["canon<br/>форма раскладки всего проекта"]
subgraph docsp["документы, владеют содержимым docs/"]
direction LR direction LR
init["doc-init"] init["doc-init"]
canon["doc-canon"]
docs["doc-sync"] docs["doc-sync"]
hc["doc-healthcheck"] hc["doc-healthcheck"]
end end
@@ -152,18 +159,20 @@ flowchart TB
`docs/`, каталог задач, `openspec/`. Тогда вызывающий называет строкой, чего теперь не делает `docs/`, каталог задач, `openspec/`. Тогда вызывающий называет строкой, чего теперь не делает
никто, и работу не останавливает. Правило целиком — никто, и работу не останавливает. Правило целиком —
[shared/absence.md](av-dev/shared/absence.md): оно нужно почти каждому скиллу, и [shared/absence.md](av-dev/shared/absence.md): оно нужно почти каждому скиллу, и
ни один им не владеет. Там же, в `shared/`, живут язык проектных текстов и ни один им не владеет. Там же, в `shared/`, живут язык проектных текстов,
словарь сопровождения; скиллы читают их по ссылке, а дословной копией они словарь сопровождения и **перечень осей процесса**
уезжают только в уставы вычитки — туда, где текст обязан лежать внутри промпта. [axes.md](av-dev/shared/axes.md) — какие закрытые словари правят ходом работы,
где дом каждого и чего он **не** решает. Скиллы читают эти дома по ссылке, а
дословной копией оттуда уезжает лишь то, что обязано лежать внутри промпта.
## Канон документов проекта ## Канон раскладки проекта
Все проекты приводятся к одной раскладке — так проще ориентироваться, когда Все проекты приводятся к одной раскладке — так проще ориентироваться, когда
проектов много, и рядом OpenSpec тоже держит строгую структуру. `CLAUDE.md` плюс проектов много, и рядом OpenSpec тоже держит строгую структуру. `CLAUDE.md` плюс
`docs/` (паспорт, архитектура, база, безопасность, конвенции, разведка, ADR, `docs/` (паспорт, архитектура, база, безопасность, конвенции, разведка, ADR,
ревью, задачи) и `openspec/` — **раскладка целиком, роли документов и правило ревью, задачи) и `openspec/` — **раскладка целиком, роли документов и правило
единственного дома живут одним домом**: единственного дома живут одним домом**:
[canon.md](av-dev/skills/doc-canon/references/canon.md). Здесь она не [canon.md](av-dev/skills/canon/references/canon.md). Здесь она не
пересказывается: копия перечня путей уже расходилась с домом, и как раз в пересказывается: копия перечня путей уже расходилась с домом, и как раз в
обязательных — в ней не хватало путей, чьё отсутствие `docs.py check` считает обязательных — в ней не хватало путей, чьё отсутствие `docs.py check` считает
нарушением. нарушением.
@@ -181,9 +190,9 @@ flowchart TB
«тема → её дом → что оттуда берётся» — «тема → её дом → что оттуда берётся» —
[project-facts.md](av-dev/skills/code-review/references/project-facts.md). [project-facts.md](av-dev/skills/code-review/references/project-facts.md).
Прийти в старый проект и перевести его на канон — `/av-dev:doc-canon`. Прийти в старый проект и перевести его на канон — `/av-dev:canon`.
Раскладка версионируется, и проекты повышаются по [журналу Раскладка версионируется, и проекты повышаются по [журналу
версий](av-dev/skills/doc-canon/references/changelog.md). версий](av-dev/skills/canon/references/changelog.md).
**Версия одна, и живёт она в `.av-dev.toml` в корне репозитория** — вместе с **Версия одна, и живёт она в `.av-dev.toml` в корне репозитория** — вместе с
настройками: `[docs] migrations` и секция `[tasks]`, которая говорит, где лежит настройками: `[docs] migrations` и секция `[tasks]`, которая говорит, где лежит
@@ -232,11 +241,22 @@ claude plugin install av-dev-git@av-dev-skills --scope project
} }
``` ```
**При установке в проект, где лежали проектные копии** скиллов и агентов **При установке в проект, где лежали проектные копии** скиллов и агентов
(`.claude/skills/` — голые `task-pipeline`, `review-pipeline` снеси их. Перечень полный, и он же дом: скиллы носят его помеченной копией,
и с префиксом проекта `<проект>-task-pipeline`, потому что предупреждают о том же в момент работы.
`.claude/agents/<проект>-review-*.md`) — снеси их. Две копии одного скилла
расходятся, и побеждает та, что короче названа. <!-- дом: проектные-копии -->
Голые имена в `.claude/skills/`: `resolve`, `review`, а у проектов прошлого
поколения ещё `task-pipeline`, `review-pipeline`, `task-batch`. С префиксом
проекта: `<проект>-task-pipeline`, `<проект>-review-pipeline`. Агенты:
`.claude/agents/<проект>-review-*.md`.
Две копии одного скилла расходятся, и побеждает та, что **короче названа**:
короткое имя разрешится в устаревшую проектную копию молча и без признаков
подмены.
<!-- /дом: проектные-копии -->
## Обновление ## Обновление
@@ -369,7 +389,7 @@ python3 scripts/frontmatter.py # 0 в порядке, 1 расхождени
простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт, простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт,
сверка…» — это не текст с двоеточием, а синтаксическая ошибка. Так было сверка…» — это не текст с двоеточием, а синтаксическая ошибка. Так было
написано часть описаний плагинов, и читались они правильно — замер и разбор написано часть описаний плагинов, и читались они правильно — замер и разбор
в [DECISIONS.md](DECISIONS.md), решение III; в [решении Р61](decisions/17-notes-tier-work-kind-roadmap.md) журнала;
- **`name`, разошедшееся с именем каталога скилла или файла charter'а.** Вызов - **`name`, разошедшееся с именем каталога скилла или файла charter'а.** Вызов
разрешается по каталогу, а сообщение о промахе говорит «нет такого скилла», разрешается по каталогу, а сообщение о промахе говорит «нет такого скилла»,
а не «имя не то»; а не «имя не то»;
@@ -426,7 +446,7 @@ python3 scripts/copies.py # 0 сошлось, 1 расхождение, 2 р
когда он решает; и в скелетах, уезжающих в репозиторий проекта. когда он решает; и в скелетах, уезжающих в репозиторий проекта.
**Дом ставится в `shared/` только тогда, когда владельца нет.** У адресов **Дом ставится в `shared/` только тогда, когда владельца нет.** У адресов
владелец есть: раскладку `docs/` держит `doc-canon`, каталог задач — владелец есть: раскладку `docs/` держит `canon`, каталог задач —
`task-track`, и переносить их наружу значило бы отобрать у владельца его же `task-track`, и переносить их наружу значило бы отобрать у владельца его же
предмет. Общее без владельца живёт в `shared/`; чужое с владельцем остаётся предмет. Общее без владельца живёт в `shared/`; чужое с владельцем остаётся
дома, а потребитель на него ссылается. дома, а потребитель на него ссылается.
@@ -464,7 +484,7 @@ python3 scripts/resync.py # переписать тела всех разо
## Проверка адресов документов ## Проверка адресов документов
Адрес документа принадлежит одному плагину, а называют его все: `docs/*` стоит Адрес документа принадлежит одному плагину, а называют его все: `docs/*` стоит
примерно в сорока местах конвейера, `tasks/ROADMAP.md` — в четырёх местах канона. примерно в сорока местах конвейера, `tasks/BACKLOG.md` — в нескольких местах канона.
Переименование в каноне до этих мест само не доходит. Переименование в каноне до этих мест само не доходит.
``` ```
@@ -524,7 +544,7 @@ python3 scripts/diagrams.py A.md B.md # только названные фай
## Гейт коммита ## Гейт коммита
Все шесть проверок стоят в `pre-commit` через [lefthook](https://lefthook.dev) — Все семь проверок стоят в `pre-commit` через [lefthook](https://lefthook.dev) —
конфиг в [lefthook.yml](lefthook.yml), ставится один раз на клон: конфиг в [lefthook.yml](lefthook.yml), ставится один раз на клон:
``` ```
@@ -537,6 +557,7 @@ lefthook run pre-commit # прогнать руками, не коммитя
| фронтматтеры | правка `*.md` или `*.json` | весь репозиторий | миллисекунды | | фронтматтеры | правка `*.md` или `*.json` | весь репозиторий | миллисекунды |
| копии правил | правка `*.md` | весь репозиторий | миллисекунды | | копии правил | правка `*.md` | весь репозиторий | миллисекунды |
| адреса документов | **каждый коммит** | весь репозиторий | ~0.07 с | | адреса документов | **каждый коммит** | весь репозиторий | ~0.07 с |
| журнал решений | **каждый коммит** | весь репозиторий | ~0.09 с |
| диаграммы | правка `*.md` | staged-файлы | ~1 с на файл | | диаграммы | правка `*.md` | staged-файлы | ~1 с на файл |
| `ruff check --fix` | правка `*.py` | staged-файлы | доли секунды | | `ruff check --fix` | правка `*.py` | staged-файлы | доли секунды |
| `pyrefly check` | правка `*.py` | staged-файлы | доли секунды | | `pyrefly check` | правка `*.py` | staged-файлы | доли секунды |
@@ -552,7 +573,16 @@ Glob разводит две половины: коммит, трогающий
обходит весь репозиторий за сотые доли секунды — экономить тут нечего; обходит весь репозиторий за сотые доли секунды — экономить тут нечего;
`addresses.py` идёт **без glob вовсе**, потому что сводит две стороны: перечень `addresses.py` идёт **без glob вовсе**, потому что сводит две стороны: перечень
адресов лежит в `*.py` владельца, а упоминания — в `*.md` соседей, и коммит с адресов лежит в `*.py` владельца, а упоминания — в `*.md` соседей, и коммит с
переименованием документа трогает только первую. переименованием документа трогает только первую; `decisions.py` — по той же
причине: файл темы и ссылки на него лежат порознь, и переименование темы трогает
только одну сторону.
**Что судит `decisions.py`.** Номер записи журнала обязан быть один: буквенные
метки решений выродились до пятибуквенных и разъехались молча — `АЕАКЛ` означала
и тему 53, и тему 65. И подпись ссылки обязана называть свою цель: в
`[тема 5](decisions/05-project-start-lifecycle.md)` номер записан дважды, словом и путём, —
дубль неизбежный (иначе ссылку не прочитать, не открыв) и потому сверяемый, как
всякая копия.
**`ruff` чинит безопасное сам, и починка доносится до этого же коммита** **`ruff` чинит безопасное сам, и починка доносится до этого же коммита**
(`stage_fixed: true`). Иначе исправленный файл остался бы в рабочем дереве, а в (`stage_fixed: true`). Иначе исправленный файл остался бы в рабочем дереве, а в
-140
View File
@@ -1,140 +0,0 @@
# Остатки, открытые вопросы и принятые пределы
Состояние пересобирается по ходу работы; счётчика тем и коммитов здесь нет
намеренно — он протухает молча, а двигать его некому. Что и когда решено —
[DECISIONS.md](DECISIONS.md), записи датированы.
План работ — [TODO.md](TODO.md). Решения с причинами — [DECISIONS.md](DECISIONS.md).
Здесь то, что **не** является работой из плана: незакрытые риски, честно принятые
пределы и вопросы, у которых пока нет ответа.
## Главный незакрытый риск
**Калибровка не сделана, а уставы проходов с тех пор переписывались не раз.**
Правки шли волнами: вынос в плагин (предмет проверки заменён ссылкой на раздел
брифа), переход на пути документов канона, две правки по находкам ревью, граф
порядка, ступень `wide`, пересмотр триггеров ступени.
`references/calibration.md` требует при каждой такой правке замерить, помогла ли
она, — **ни одного замера не было**. Числа правок здесь нет намеренно: счётчик
пришлось бы двигать вручную, и он уже однажды отстал.
**Неизмеренные изменения копятся** в том самом месте, где присваивается
severity. Пробы готовы и синтетических не нужно — четыре реальные находки
прошедшей сессии healthlog:
- скелет из `null` затирает маршрут тренировки молча и необратимо;
- откат бинаря поверх новой схемы стартует без единого слова;
- канонизация внутри транзакции — 768 МиБ пика, 5.019 с удержания блокировки;
- `-1 >= -1` читается как «журнал разобран целиком».
Ожидаемый исход известен и его стоит проверить первым: метод переносится, а
**severity деградирует**. Третья находка без слота под представление данных и
настройки хранилища превращалась из `critical` с прогнанным оракулом в условное
наблюдение. Ровно ради этого случая канон развёл числа (`docs/research/`) и
настройки (`docs/database.md`) по разным домам и **обязал проход их сшивать**
но работает ли обязанность, не проверено. Оркестратор реагирует на severity,
поэтому цена — не «не найдём», а **«найдём и не починим»**.
Сама работа — [TODO.md](TODO.md), раздел «Калибровка»; здесь только цена: замер
стоит перед переездом jellybit и блокирует его (решение 39), а ожидаемый исход
уже назван выше.
**«Главный» здесь про цену, а не про очередь.** Первой идёт адаптация healthlog
(TODO, раздел «Живые проекты»): без неё нет проекта под каноном, на котором
работают остальные скиллы. Калибровка блокирует один шаг — переезд jellybit, — а
не всё подряд.
## Что ещё не сделано
Список работ — в [TODO.md](TODO.md). Здесь только то, что стоит держать в голове
отдельно:
- **Ни один скилл не прогонялся на живом проекте.** `docs.py` прогнан на
healthlog и jellybit в режиме `check` и находит осмысленный дрейф; `init`,
`canon adopt`, `canon upgrade`, скиллы `docs`, `openspec` и `resolve` не
исполнялись ни разу. `openspec.py`, раскол плагинов и оба чекпоинта `resolve`
проверены только на фикстурах и на установке каждого плагина в одиночку.
- **Проектные копии в healthlog и jellybit.** Два `.claude/skills/` и одиннадцать
`.claude/agents/` старого поколения — их надо снести при установке.
**Совпадение имён при этом больше не грозит:** скиллы jellybit названы
`task-pipeline`, `review-pipeline`, `task-batch`, а плагин теперь даёт
`resolve`, `review`, `openspec` — ни одно имя не пересекается. Риск снят
переименованием, а не устранён по существу: заведись у проекта свой `review`,
Claude Code держал бы обе пары, и короткое имя увело бы в копию молча.
## Открытые вопросы
**`doc-consistency` не различает «про нас» и «про то, что мы производим».**
Первый прогон на самом dev-skills предъявил репозиторию правило из
`av-dev-git/skills/commit/SKILL.md` — а это продукт, уезжающий в чужие проекты,
а не правило, которому подчиняется маркетплейс. На проекте под каноном такой
путаницы нет (там документы описывают сам проект), поэтому в устав это пока не
дописано: сперва посмотреть, встретится ли класс ещё раз.
**Как проверять, что канон не разошёлся с проектами после `upgrade`.** `canon
check` сверяет версию, но не то, что миграционные записи journal'а применены
верно. Проект может нести `"canon": 2` и не иметь того, что версия 2 требовала.
Ответ выбран: шагом 6 `upgrade` зовутся оба судьи документов — проверка не
механическая, но других у существа записей нет. Останется открытым, пока не
прогнано на живом проекте: неизвестно, ловят ли они недоделанную миграцию или
только её последствия.
**Не выродились ли «границы покрытия» в шаблон.** Строка «что смотрели и чего не
смотрели» обязательна в докладе `check`, `adopt`, интейка, штурма и всех пяти
агентов канона и задач (`doc-consistency`, `doc-code-drift`, `doc-wording`,
`task-form`, `task-wording`) — девять мест, и проверить её исполнение некому:
приёмщик и исполнитель одно лицо (`task-groom/SKILL.md`, «Стимулы»). Выродившаяся
строка **хуже отсутствия**: доклад выглядит проверенным.
Приём не правится: это гипотеза об износе, а не находка, и менять работающее по
догадке дороже. **Наблюдение к первой обкатке на живом проекте:** если в трёх
докладах подряд границы покрытия совпали дословно или называют не то, чего
проверка действительно не касалась, — приём выродился, и вот тогда решать.
**Форма ADR при пересмотре решения.** Парный статус («старая запись получает
`заменено на`») судит агент `doc-consistency` — правило 6 его устава. Охват был
открытым вопросом, пока агент звался пачкой, отобранной работой; переезд вызова
в `av-dev:doc-healthcheck` с пачкой «весь канон» его снял. Остаётся зазор до
ближайшего прогона `healthcheck` и отсутствие механической проверки — то есть
пересмотр, сделанный сегодня, судится тогда, когда позовут сверку, а не в момент
правки.
## Известные пределы — приняты, чинить не планируется
**Транзакций на несколько файлов нет.** POSIX её не даёт без журнала. Окно сжато
до цепочки `rename` без ввода-вывода, а всё, что в окне может разъехаться,
сделано производным и восстанавливается `check --fix` без потерь.
**Оракул в критериях приёмки проверяется эвристикой.** Число пунктов проверяется
жёстко, наличие оракула — по слову, и это **только замечание**. В тексте прямо
сказано, что проверено меньше, чем требуется.
**Recall прохода по конвенциям равен качеству конвенций проекта.** Своего списка
у него нет: критерий берётся из `docs/conventions/`. На проекте с тонкими
конвенциями проход почти пуст, и charter это признаёт вслух.
**Доменного словаря в каноне нет.** Проходы получают факты, но не термины;
словарь строится каждый раз заново из спек и архитектуры. Цена не измерена.
**Смысловые дубли ловит только агент.** `docs.py` видит раскладку, но не то, что
раздел `docs/architecture.md` описывает поведение, уже записанное capability
`recognition`.
Граница объявляется вслух в каждом отчёте — это единственная защита от
«соблюдено» на проекте с тремя лишними файлами.
**Приёмщик и исполнитель совпали, и опор стало меньше.** Граница «пайплайн не
закрывает задачу» снята сознательно (решение P); защиты держатся текстом, а не
механикой. Реальных опор было три, осталось две: сохранённый отчёт триажа и
`reopen` (индексы под git показывают закрытие, потому что оно коммитится
отдельным коммитом учёта). Третья — приёмка шагом сессии — ушла вместе со
спринтами: у неё больше **нет момента**, и происходит она только тогда, когда
что-то бросилось в глаза на груминге. Это записано в самих скиллах, а не
спрятано.
**Копия правила в шаблонах проекта.** `adr/README.md` и `review.md` уезжают в
репозиторий и обязаны там что-то говорить, поэтому правило канона в них
копируется намеренно. Расхождение копии с домом ловит `scripts/copies.py`
но только у **помеченной** копии, и только внутри маркетплейса. Остаётся на
человеке двое: пометить копию и завести запись в журнал версий, когда правка
уже уехала в проект.
-133
View File
@@ -1,133 +0,0 @@
# Что осталось сделать
**Здесь только работы и их порядок.** Чего здесь нет намеренно:
- **риски, открытые вопросы и принятые пределы** — [REMAINING.md](REMAINING.md);
- **почему решено так** — [DECISIONS.md](DECISIONS.md), записи датированы;
- **шаги повышения проекта с версии канона на версию** — журнал версий
([changelog.md](av-dev/skills/doc-canon/references/changelog.md)). Пересказ их
сюда был бы вторым домом, и прежний план на этом уже разъезжался: он повторял
записи версий 3, 4 и 5 построчно, и половина повторов протухла молча.
Сделанное отсюда **удаляется, а не помечается галочкой**. След остаётся в
коммитах и в `DECISIONS.md`; список из двух сотен `[x]` перестают читать целиком,
и живые пункты в нём теряются — прежний план умер именно так.
## Где мы сейчас
Плагина два: `av-dev` — весь процесс девятью скиллами (`doc-*` — документы,
`task-*` — учёт работ, `code-*` — работа по задачам), и `av-dev-git`
сообщения коммитов. Прежние три (`av-dev-docs`, `av-dev-tasks`, `av-dev-code`)
слились 13 августа 2026, тема 64 DECISIONS. Общее, что нужно нескольким скиллам,
живёт домом в `av-dev/shared/`.
Раскладка — **версия 1**, одна на документы и на каталог задач, в
`.av-dev.toml` в корне проекта. Живые проекты стоят на каноне 2–3 и на плагине
`av-dev-pm`, которого больше нет: им идти сперва по закрытому журналу канона до
14, потом по записи 1 действующего.
Бумажная часть закрыта аудитом четырёх плагинов и четырьмя пропусками правок
(DECISIONS, запись 59). Всё, что ниже, проверяется **только на живом коде**.
## 1. Живые проекты — вернуть в рабочее состояние
Блокирует всё остальное: под текущим каноном не стоит ни один проект, и ни один
скилл, кроме `docs.py check`, не исполнялся на живом коде ни разу
(см. REMAINING, «Что ещё не сделано»).
### healthlog — первым
- [ ] переустановить плагины: снять `av-dev-pm` и `av-dev-pipeline`, поставить
`av-dev` и `av-dev-git`. Прежние имена мертвы, и `plugin update` их не
переименует — только снять и поставить. `marketplace update`, затем `plugin update` — одного шага мало
(README, «Обновление»)
- [ ] удалить проектные копии: `.claude/skills/healthlog-{task,review}-pipeline`
и девять `.claude/agents/healthlog-review-*.md`. Они прошлого поколения и
после переезда указывают на документы, которых уже не будет
- [ ] `av-dev:doc-canon` в режиме `adopt` — он приведёт проект к раскладке 1
сразу, картой и с подтверждением. Файл-в-файл здесь не расписан: раскладку
знает скилл, и второй перечень разошёлся бы с ним
- [ ] каталог задач — в `tasks/` корня (канон 11), не в `docs/tasks/`, без
`SPRINT.md` (канон 12); версия и настройки — в `.av-dev.toml` корня, там
же секция `[tasks]`. Скилл задач зовётся из `adopt` сам
- [ ] гейт проекта: три шага вместо одного — `docs.py check`, `tasks.py check
--dir tasks`, `openspec.py check`. **Второй и третий раньше не были
нужны:** согласованность задач тянул за собой `docs.py`, форму `config.yaml`
он же. Теперь оба молчат, и без своих шагов дрейф перестанет ловиться
- [ ] разобрать урожай `doc-consistency` и `doc-code-drift` порциями — правило
единственного дома на живом проекте не проверял никто
### jellybit — после калибровки
Порядок не произволен: замер (раздел 3) блокирует переезд jellybit, и только его.
- [ ] то же, что у healthlog: плагины, проектные копии, `adopt`, каталог задач,
гейт
- [ ] проектные копии здесь опаснее: скиллы названы `task-pipeline`,
`review-pipeline` — **ровно как в плагине**, и короткое имя
может увести в устаревшую копию молча (REMAINING)
## 2. Учёт работ без спринтов — что осталось
Сделано: спринт снят со скрипта и текстов, приоритет стал порядком строк в
беклоге, гейт готовности переехал в `tasks.py ready`, `session` стал скиллом
`groom`, запись 12 в журнал версий канона написана.
- [ ] прогнать груминг на живом беклоге — на фикстуре проверялись команды, а не
сам разбор. Наблюдение к первому прогону: **не выродился ли шаг 4 в
«оставить как есть»** — признак тот, что доклад не называет ни одного
движения с доводом
## 3. Калибровка — блокирует переезд jellybit
- [ ] замер на четырёх находках healthlog: скелет из `null`, откат бинаря,
канонизация в транзакции, `-1 >= -1`. Цена и ожидаемый исход — REMAINING,
«Главный незакрытый риск»
## 4. Конвейер: что осталось после `resolve`
Сам скилл написан (`av-dev:code-resolve`, три сценария — разведка, решение и
обслуживание; чекпоинт есть у первых двух, у обслуживания планового стопа нет),
`task-batch` удалён. Осталось то, что на бумаге не проверяется:
- [ ] прогнать сценарий разведки на живой задаче: он написан целиком на бумаге и
не исполнялся ни разу. Самое неизвестное — выбор сценария на входе (не
уедет ли всё в решение, потому что «способ вроде понятен») и объём того,
что разведка пишет в документы
- [ ] прогнать сценарий обслуживания на живой задаче `chore`. Неизвестных три:
**держится ли связка признаков** (не уедет ли в обслуживание то, что меняет
поведение, и наоборот — не заведут ли пустой change по привычке); **работает
ли ревью без change** — конвейер написан вокруг него, и прогон с
фиксированным планом не запускался ни разу; **есть ли чем сверить состав
гейта** — на живых проектах семантика гейта в `CLAUDE.md` может не называть
шагов поимённо, и тогда сверка вырождается в цвет
- [ ] перемерить скилл `review` тем же вопросом, что и проект целиком:
сколько из его стадий реально смотрятся глазами. Тысяча строк, и весь
автоматический участок между чекпоинтами держится на них
- [ ] чекпоинт «объяснение» собирается из `proposal.md` и `design.md`, а
требования к их форме уехали в `openspec/config.yaml` (`rules.proposal`,
`rules.design`). **На живом проекте это ни разу не работало:** неизвестно,
хватает ли двух артефактов, чтобы объяснение не пришлось дописывать руками
## 5. Мелочь, оставленная аудитом сознательно
Одной пачкой, когда будет повод открыть эти файлы, — не раньше:
- [ ] «чекпоинт» несёт третий смысл — точка наблюдаемости в коде
(`finding-contract.md`, `promote.md`). Слово занято дважды по своему же
правилу, но домены разные, и переименование здесь может выйти дороже
путаницы
- [ ] закрытый словарь `shared/language.md` не содержит ни «конвейера», ни
«чекпоинта», ни «груминга» — трёх рабочих терминов репозитория. Список
объявлен закрытым, и пополнять его на ходу нельзя
- [ ] `move <слаг>` без флагов теперь легален и значит «в конец своей секции» —
осмысленная операция, но в прозе не описана нигде
- [ ] `reopen` печатает «позиция это приоритет» и для целей роадмапа, где секции
очередью не являются
## 6. Обкатка
- [ ] один-два цикла healthlog на новом процессе. Наблюдения к первой обкатке
два: не выродились ли «границы покрытия» в шаблон (REMAINING, «Открытые
вопросы») и **не превратился ли чекпоинт в ритуал одобрения** — признак
тот же, дословно повторяющийся текст и согласие без единой правки
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "av-dev", "name": "av-dev",
"description": "Личный процесс разработки одним плагином: документы проекта, учёт работ и работа по задачам. Документы — канон раскладки (CLAUDE.md плюс docs/: паспорт, архитектура, схема БД, безопасность, конвенции, разведка, ADR, журнал ревью), три операции одной машиной сравнения в doc-canon (check, adopt, upgrade) со скриптом docs.py, заведение нового проекта интервью в doc-init, ведение содержимого по ходу разработки в doc-sync, суд о смысловом здоровье в doc-healthcheck двумя агентами разом. Учёт — задачи и цели каталогом markdown-файлов в task-track, у записи тип (goal, feature, fix, chore, research), и тип решает её схему; приоритет расставляет интерактивный task-groom порядком строк. Работа — одна задача от постановки до закрытия скиллом code-resolve: точка входа одна, сценария три (решение циклом SDD с чекпоинтом, разведка без кода, обслуживание без change), и выбирает сценарий сам скилл, прочитав постановку; плюс конвейер ревью code-review с детерминированным гейтом, сверкой со спеками и обязательным триажем, плюс code-openspec, который заводит и проверяет openspec/. Версия раскладки и настройки живут в .av-dev.toml в корне репозитория. Части включаются следом в проекте, а не установкой: нет docs/ — документы не ведутся, нет каталога задач — учёт остаётся владельцу, нет openspec/ — его заводит сценарий решения. Коммиты — отдельный плагин av-dev-git.", "description": "Личный процесс разработки одним плагином: форма раскладки, документы проекта, учёт работ и работа по задачам. Форму держит скилл canon — три операции одной машиной сравнения (check, adopt, upgrade) со скриптом docs.py и общим журналом версий, по которому повышается вся раскладка, включая каталог задач; имя без префикса, потому что форма общая у всех частей. Документы — канон раскладки (CLAUDE.md плюс docs/: паспорт, архитектура, схема БД, безопасность, конвенции, разведка, ADR, журнал ревью), заведение нового проекта интервью в doc-init, ведение содержимого по ходу разработки в doc-sync, суд о смысловом здоровье в doc-healthcheck двумя агентами разом. Учёт — задачи каталогом markdown-файлов в task-track: у задачи тип (feature, fix, chore, research), задающий её схему, а у проекта — стадия (build — беклог это план стройки от базы к деталям, порядок строк значит зависимость; support — очередь правок, порядок значит важность); очередь расставляет интерактивный task-groom порядком строк. Работа — одна задача от постановки до закрытия скиллом code-resolve: точка входа одна, сценария три (решение циклом SDD с чекпоинтом, разведка без кода, обслуживание без change), и выбирает сценарий сам скилл, прочитав постановку; плюс конвейер ревью code-review с детерминированным гейтом, сверкой со спеками и обязательным триажем, плюс code-openspec, который заводит и проверяет openspec/. Версия раскладки и настройки живут в .av-dev.toml в корне репозитория. Части включаются следом в проекте, а не установкой: нет docs/ — документы не ведутся, нет каталога задач — учёт остаётся владельцу, нет openspec/ — его заводит сценарий решения. Коммиты — отдельный плагин av-dev-git.",
"author": { "author": {
"name": "Anton Vakhrushev", "name": "Anton Vakhrushev",
"email": "anwinged@gmail.com" "email": "anwinged@gmail.com"
+3 -3
View File
@@ -15,19 +15,19 @@ color: yellow
машина, а что человек», и её правая колонка — твой устав дословно. машина, а что человек», и её правая колонка — твой устав дословно.
Карта домов, по которой ты судишь о правиле 1, — дословная копия канона; дом её Карта домов, по которой ты судишь о правиле 1, — дословная копия канона; дом её
`av-dev/skills/doc-canon/references/canon.md`, раздел «Правило единственного `av-dev/skills/canon/references/canon.md`, раздел «Правило единственного
дома», и правится она там. Здесь она стоит потому, что устав — это твой промпт дома», и правится она там. Здесь она стоит потому, что устав — это твой промпт
целиком: за ссылкой ты пошёл бы отдельным чтением, а карта нужна тебе в тот целиком: за ссылкой ты пошёл бы отдельным чтением, а карта нужна тебе в тот
момент, когда ты судишь. момент, когда ты судишь.
<!-- копия: карта-домов из av-dev/skills/doc-canon/references/canon.md --> <!-- копия: карта-домов из av-dev/skills/canon/references/canon.md -->
| Факт | Дом | | Факт | Дом |
| --- | --- | | --- | --- |
| поведение системы | `openspec/specs/<capability>/spec.md` | | поведение системы | `openspec/specs/<capability>/spec.md` |
| почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки | | почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки |
| граница домена, «чем не является» | `passport.md` | | граница домена, «чем не является» | `passport.md` |
| инвариант и его severity | `CLAUDE.md` | | инвариант и его severity | `CLAUDE.md` |
| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` | | что осталось сделать и в каком порядке | `tasks/BACKLOG.md` |
| измеренное число | `research/` | | измеренное число | `research/` |
| настройка с числовым значением | `database.md` | | настройка с числовым значением | `database.md` |
| периметр и модель угроз | `security.md` | | периметр и модель угроз | `security.md` |
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: doc-wording name: doc-wording
description: "Вычитка языка документов проекта по информационному стилю — паспорт, архитектура, конвенции, безопасность, решения ADR, записки разведки, CLAUDE.md. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в имени файла. Отдаёт готовые формулировки на замену и ничего не правит сам. Записи каталога задач вычитывает отдельный агент task-wording, их форму — task-form. Зовётся по названной пачке правленных документов, а не на весь канон: последним шагом синка документации (av-dev:doc-sync), шагом заведения проекта (av-dev:doc-init), шагами adopt и upgrade скилла av-dev:doc-canon. Скилл healthcheck его не зовёт — там сверка утверждений, а не языка. Только чтение." description: "Вычитка языка документов проекта по информационному стилю — паспорт, архитектура, конвенции, безопасность, решения ADR, записки разведки, CLAUDE.md. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в имени файла. Отдаёт готовые формулировки на замену и ничего не правит сам. Записи каталога задач вычитывает отдельный агент task-wording, их форму — task-form. Зовётся по названной пачке правленных документов, а не на весь канон: последним шагом синка документации (av-dev:doc-sync), шагом заведения проекта (av-dev:doc-init), сценарием разведки (av-dev:code-resolve), шагами adopt и upgrade скилла av-dev:canon. Скилл healthcheck его не зовёт — там сверка утверждений, а не языка. Только чтение."
tools: Read, Grep, Glob tools: Read, Grep, Glob
model: sonnet model: sonnet
color: green color: green
+12 -4
View File
@@ -213,11 +213,15 @@ color: green
- **незнакомое** — форму предстоит нащупать по ходу. Признак один и - **незнакомое** — форму предстоит нащупать по ходу. Признак один и
проверяемый: **перед работой нельзя назвать, какие узлы будут тронуты**. проверяемый: **перед работой нельзя назвать, какие узлы будут тронуты**.
| | знакомое | незнакомое | <!-- копия: матрица-метки из av-dev/skills/code-review/references/review-levels.md -->
| | **знакомое** — форму решения можно назвать до начала | **незнакомое** — форму предстоит нащупать по ходу |
|---|---|---| |---|---|---|
| **малое** | `small` | `large` | | **малое** — один узел | `small` | `large` |
| **среднее** | `medium` | `large` | | **среднее** — несколько узлов одного слоя | `medium` | `large` |
| **крупное** | `large` | `large` | | **крупное** — несколько слоёв, перенос ответственности, большой рефакторинг | `large` | `large` |
<!-- /копия: матрица-метки -->
**Метка — не синоним размера, и это главная ловушка таблицы.** Размер `малое` и **Метка — не синоним размера, и это главная ловушка таблицы.** Размер `малое` и
метка `small` совпадают только в левом верхнем углу: малое **незнакомое** метка `small` совпадают только в левом верхнем углу: малое **незнакомое**
@@ -272,6 +276,8 @@ color: green
**Ревью кода — раздача тем.** Кто закрывает тему, зависит от метки. Раскладка **Ревью кода — раздача тем.** Кто закрывает тему, зависит от метки. Раскладка
жёсткая, выдумывать её не надо: жёсткая, выдумывать её не надо:
<!-- копия: тема-метка-глубина из av-dev/skills/code-review/SKILL.md -->
| Тема | `small` | `medium` | `large` | | Тема | `small` | `medium` | `large` |
|---|---|---|---| |---|---|---|---|
| `requirements` | `specs`, сверка | `specs`, разбор | `specs`, разбор | | `requirements` | `specs`, сверка | `specs`, разбор | `specs`, разбор |
@@ -282,6 +288,8 @@ color: green
| `operations` | `code`, сверка по инвариантам | `basics`, разбор | `ops`, доказательство | | `operations` | `code`, сверка по инвариантам | `basics`, разбор | `ops`, доказательство |
| тема проекта | `basics`, сверка | `basics`, разбор | `basics`, разбор | | тема проекта | `basics`, сверка | `basics`, разбор | `basics`, разбор |
<!-- /копия: тема-метка-глубина -->
Две глубины, которые ты назначаешь: Две глубины, которые ты назначаешь:
- **сверка** — открыть дом, открыть дифф, сравнить. Один-два вопроса на тему, - **сверка** — открыть дом, открыть дифф, сравнить. Один-два вопроса на тему,
+28 -15
View File
@@ -1,6 +1,6 @@
--- ---
name: review-triage name: review-triage
description: "Обязательный финальный проход конвейера ревью — единственный, кто агрегирует. Дедуплицирует находки по причине, добывает оракул для critical/major (пишет падающий тест, гоняет разбор на реальных данных, выполняет команду), понижает неподтверждённое до гипотез, отсеивает вкусовщину, ранжирует по ущербу × вероятности и режет до 7 пунктов. Помечает каждую находку «инлайн» или «развилка» для оркестратора. Сверяет план разметки задачи с пришедшими отчётами: тема, размеченная и оставшаяся без отчёта, — находка о самом прогоне. Формирует итоговый отчёт с планом, перечнем проходов и обязательной секцией границ покрытия." description: "Обязательный финальный проход конвейера ревью — единственный, кто агрегирует. Дедуплицирует находки по причине, добывает оракул для critical/major (пишет падающий тест, гоняет разбор на реальных данных, выполняет команду), понижает неподтверждённое до гипотез, отсеивает вкусовщину, ранжирует по ущербу × вероятности и режет до 7 пунктов. Помечает каждую находку «инлайн» или «развилка» для оркестратора. Сверяет план с пришедшими отчётами: тема, стоявшая в плане и оставшаяся без отчёта, — находка о самом прогоне; на прогоне без метки план даёт сценарий обслуживания, а не разметчик. Формирует итоговый отчёт с планом, перечнем проходов и обязательной секцией границ покрытия."
tools: Read, Grep, Glob, Bash, Write tools: Read, Grep, Glob, Bash, Write
model: opus model: opus
color: yellow color: yellow
@@ -21,18 +21,26 @@ color: yellow
## Вход ## Вход
Сырые выводы всех запущенных проходов, `git diff <база>..HEAD`, **план разметки Сырые выводы всех запущенных проходов, `git diff <база>..HEAD`, **план прогона**
задачи** (агент `review-scope`, один запуск после `propose`) и режим прогона. и режим. Дельта-спеки — по мере надобности.
Дельта-спеки — по мере надобности.
План — это таблица «тема → дом → глубина → кто закрывает» плюс размер, сложность План — таблица «тема → дом → глубина → кто закрывает». Он твой главный инструмент
и метка с обоснованием. Он твой главный инструмент сверки: ты единственный, кто сверки: ты единственный, кто видит и то, что заявлено, и то, что пришло.
видит и то, что размечено, и то, что пришло.
**Плана нет — ты не запускаешься, и исключений нет.** Сверка размеченного с **Откуда план приходит, зависит от режима, и режимов два.**
пришедшим — твоя единственная защита от молчащего пропуска, и без плана она не
выполняется вовсе. Отчёт, собранный без неё, выглядит полным ровно настолько же, - **С меткой** — план собрал `review-scope` (один запуск после `propose`), и к
насколько и неполный. таблице прилагаются размер, сложность и метка с обоснованием.
- **Без метки** — так идёт прогон сценария обслуживания: изменение не меняет
поведения, размечать нечего, и разметчик не запускается вовсе. План
**фиксирован сценарием** (`av-dev:code-resolve`, `references/maintain.md`), а
размера, сложности и метки не существует. Не ищи их и не подставляй: в отчёте
на их месте — строка «прогон без метки, план сценария».
**Плана нет ни от разметчика, ни от сценария — ты не запускаешься, и исключений
нет.** Сверка заявленного с пришедшим — твоя единственная защита от молчащего
пропуска, и без плана она не выполняется вовсе. Отчёт, собранный без неё,
выглядит полным ровно настолько же, насколько и неполный.
Из документов проекта тебе нужны: Из документов проекта тебе нужны:
@@ -174,7 +182,9 @@ severity:
**Сигнала нет — тоже скажи строкой.** «Корректор метки отработал, возражений **Сигнала нет — тоже скажи строкой.** «Корректор метки отработал, возражений
нет» и «корректор не запускался» — разные вещи, и отличить их по молчанию нет» и «корректор не запускался» — разные вещи, и отличить их по молчанию
нельзя. нельзя. **На прогоне без метки корректору нечего поднимать**, и это третье
состояние: пиши «метки нет, корректор неприменим», а не «не запускался» —
последнее читается как пропуск.
## Границы покрытия — не сокращаются ## Границы покрытия — не сокращаются
@@ -238,9 +248,12 @@ severity:
Строго секциями из контракта: `Блокирует мердж` (≤3) / `Стоит исправить сейчас` Строго секциями из контракта: `Блокирует мердж` (≤3) / `Стоит исправить сейчас`
(≤4) / `Гипотезы без доказательства` / `Promote candidates` / `Границы покрытия`. (≤4) / `Гипотезы без доказательства` / `Promote candidates` / `Границы покрытия`.
Перед секциями — сводка: размер, сложность и метка с обоснованием разметки и режим прогона, Перед секциями — сводка: режим прогона, состояние гейта, **план с исходом по
состояние гейта, **план с исходом по каждой теме**, сколько находок пришло на каждой теме**, сколько находок пришло на вход и сколько осталось. На прогоне
вход и сколько осталось. **с меткой** к этому добавляются размер, сложность и метка с обоснованием
разметки; на прогоне **без метки** их место занимает строка «прогон без метки,
план сценария обслуживания» — выдумывать метку задним числом нельзя, её никто
не снимал.
## Ограничения ## Ограничения
+19 -44
View File
@@ -1,6 +1,6 @@
--- ---
name: task-form name: task-form
description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (цель — что приложение будет уметь, задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле, и связь задачи со строкой «Завершения» её цели. Читает файл цели, на которую ссылается задача. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, при проверке готовности перед взятием в работу (tasks.py ready) и на груминге. Только чтение." description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, при проверке готовности перед взятием в работу (tasks.py ready) и на груминге. Только чтение."
tools: Read, Grep, Glob tools: Read, Grep, Glob
model: sonnet model: sonnet
color: green color: green
@@ -11,8 +11,8 @@ color: green
открывая код. открывая код.
Оптика — смысл записи в её собственных рамках. Ты **не** судишь, нужна ли Оптика — смысл записи в её собственных рамках. Ты **не** судишь, нужна ли
задача, верно ли выбрана цель и не крупна ли она: это разбор, и его ведёт задача и не крупна ли она: это разбор, и его ведёт человек со скиллом
человек со скиллом `task-track`. `task-track`.
Границу с языком держи твёрдо. **Залог, оценки, стоп-слова, англицизмы, жаргон** Границу с языком держи твёрдо. **Залог, оценки, стоп-слова, англицизмы, жаргон**
у агента `task-wording`, и тебе они не поручены даже там, где бросаются в у агента `task-wording`, и тебе они не поручены даже там, где бросаются в
@@ -28,8 +28,6 @@ color: green
## Что тебе дают ## Что тебе дают
Список файлов записей (`tasks/items/<slug>.md`) или каталог задач целиком. Список файлов записей (`tasks/items/<slug>.md`) или каталог задач целиком.
Каталог тебе нужен и сам по себе: задача несёт тег `goal:<слаг>`, и **файл цели
ты открываешь**, иначе седьмое правило не проверить.
Документы проекта — паспорт, архитектура, конвенции — если зовущий их назвал. Документы проекта — паспорт, архитектура, конвенции — если зовущий их назвал.
По ним видно, названа ли граница именем, которое в проекте существует. По ним видно, названа ли граница именем, которое в проекте существует.
@@ -43,20 +41,15 @@ color: green
| Тип | Отвечает на | Форма | | Тип | Отвечает на | Форма |
| --- | --- | --- | | --- | --- | --- |
| 🎯 `goal` | что приложение будет уметь | утверждение о возможности: «Соперником может быть компьютер» |
| ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | глагол в неопределённой форме, допускается «не» перед ним: «Печатать поле одним куском кода» | | ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | глагол в неопределённой форме, допускается «не» перед ним: «Печатать поле одним куском кода» |
| 🔬 `research` | о чём разведка | назывное, без обещания: «Подсказка следующего хода» | | 🔬 `research` | о чём разведка | назывное, без обещания: «Подсказка следующего хода» |
Описательный заголовок задачи («Лишние символы молча отбрасываются») называет Описательный заголовок задачи («Лишние символы молча отбрасываются») называет
**состояние** и одинаково читается как жалоба и как задание. Заголовок цели в **состояние** и одинаково читается как жалоба и как задание.
форме действия («Сделать соперника-компьютер») превращает роадмап в список
работ — а он список возможностей.
**Область работ — не цель.** «Работа со слиянием», «Рефакторинг вывода» не **Область работ — не задача.** «Работа со слиянием», «Рефакторинг вывода» не
отвечают ни на один из трёх вопросов; предложи возможность, которую эта работа отвечают ни на один из двух вопросов; предложи формулировку, называющую, что
создаёт, и скажи, если из текста её не видно. **Свойство поведения — нужно сделать, и скажи, если из текста этого не видно.
законная возможность**: «исход слияния не зависит от порядка доставки» — цель,
а не абстракция.
2. **Тип сходится с тем, что в записи написано.** Тип — первое поле меты, и он 2. **Тип сходится с тем, что в записи написано.** Тип — первое поле меты, и он
решает, каких разделов запись требует; разошедшийся тип врёт ровно там, где решает, каких разделов запись требует; разошедшийся тип врёт ровно там, где
@@ -68,7 +61,7 @@ color: green
- **`feature`, после которой снаружи ничего не меняется**, — это `chore`, и - **`feature`, после которой снаружи ничего не меняется**, — это `chore`, и
сказать это честно дешевле, чем выдумывать пользовательскую пользу; сказать это честно дешевле, чем выдумывать пользовательскую пользу;
- **`chore`, меняющий наблюдаемое поведение**, — это `feature` или `fix`, и у - **`chore`, меняющий наблюдаемое поведение**, — это `feature` или `fix`, и у
них другие требования (цель, воспроизведение); последнего другие требования (воспроизведение);
- **`research`, у которого «Вопрос» — это тема, а не вопрос.** «Разобраться с - **`research`, у которого «Вопрос» — это тема, а не вопрос.** «Разобраться с
выводом в терминалах» вопросом не является: на него нельзя ответить. Пока выводом в терминалах» вопросом не является: на него нельзя ответить. Пока
вопроса нет, запись остаётся сырьём — и это законное состояние, но назови вопроса нет, запись остаётся сырьём — и это законное состояние, но назови
@@ -105,34 +98,19 @@ color: green
постановке. Он же путь понизить требования решением, принятым до постановке. Он же путь понизить требования решением, принятым до
проектирования. проектирования.
7. **Задача называет, какую строку «Завершения» своей цели она двигает.**
Открой файл цели из тега `goal:<слаг>` и сверь. Три исхода, и все три —
разные находки:
- **строка не названа** — допиши предложение, какая это строка, если из текста
задачи видно; не видно — так и скажи;
- **строки с таким смыслом в «Завершении» нет** — либо задача не про эту цель,
либо у цели неполное «Завершение». Назови оба варианта, выбирать не тебе;
- **строка «Завершения», к которой не относится ни одна поданная задача**, —
это незакрытая часть возможности. Скажи о ней отдельно, вне списка находок
по файлам: это про набор, а не про запись.
У задачи **без цели** (`fix`, `chore`, `research`) правило не применяется
вовсе — они служат работоспособности, а не направлению.
## Чего ты не проверяешь ## Чего ты не проверяешь
Не своё бывает двух разных родов, и поступают с ними по-разному. Не своё бывает двух разных родов, и поступают с ними по-разному.
**Чужому подрядчику — строкой в границах покрытия.** Язык у `task-wording`; **Чужому подрядчику — строкой в границах покрытия.** Язык у `task-wording`;
согласованность документов канона между собой у `doc-consistency`, их согласованность документов канона между собой у `doc-consistency`, их
соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе, но соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе.
если ты открыл цель и увидел расхождение в самом документе, оно их. Увидел — Увидел не своё — назови в конце одной строкой, чтобы находка не пропала, но
назови в конце одной строкой, чтобы находка не пропала, но находкой не оформляй. находкой не оформляй.
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и **Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и
написание секций, теги, тег `question` при непустом разделе «Вопросы», написание секций, теги, тег `question` при непустом разделе «Вопросы»,
согласованность индексов, битые ссылки, форма заголовка как строки), **не пиши согласованность индекса, битые ссылки, форма заголовка как строки), **не пиши
даже строкой**: это не потерянная находка, а уже проверенное. Повторять машинную даже строкой**: это не потерянная находка, а уже проверенное. Повторять машинную
проверку словами — заводить второй дом для одного правила. проверку словами — заводить второй дом для одного правила.
@@ -142,9 +120,9 @@ color: green
твоя — раздел, который **есть и лжёт**: границы вместо замысла, критерий с твоя — раздел, который **есть и лжёт**: границы вместо замысла, критерий с
оракулом только на словах. оракулом только на словах.
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она, **Содержание работы**: нужна ли задача, не крупна ли она, достаточна ли
достаточна ли декомпозиция. Седьмое правило подходит к этому близко и декомпозиция. **И место в списке**: порядок строк значит зависимость на стройке и
останавливается там, где кончается сверка с текстом цели. Об этом молчи. важность на доработке, а ты записи видишь поштучно, вне списка. Об этом молчи.
## Порог вмешательства ## Порог вмешательства
@@ -169,9 +147,9 @@ color: green
## Доклад ## Доклад
Находки по одной, в порядке важности: заголовок → «зачем» → границы → критерии Находки по одной, в порядке важности: заголовок → «зачем» → границы → критерии.
связь с целью. Порядок такой, потому что заголовок и «зачем» — это всё, что Порядок такой, потому что заголовок и «зачем» — это всё, что видно в индексе, а
видно в индексе, а по индексу и выбирают. по индексу и выбирают.
``` ```
<файл> <файл>
@@ -181,11 +159,8 @@ color: green
почему: <одна фраза> почему: <одна фраза>
``` ```
Отдельным блоком после находок — **строки «Завершения» без задач**, если такие
нашлись: цель, строка, и что это значит.
В конце — **границы покрытия**: сколько записей просмотрено из скольких, какие В конце — **границы покрытия**: сколько записей просмотрено из скольких, какие
цели открыты, какие не смотрел и почему. Отчёт без этой строки читается как не смотрел и почему. Отчёт без этой строки читается как
«беклог проверен», не сообщая, какая его часть осталась нетронутой. Туда же — «беклог проверен», не сообщая, какая его часть осталась нетронутой. Туда же —
строка «замечено не по моей части», если бросился в глаза язык; машинно строка «замечено не по моей части», если бросился в глаза язык; машинно
проверяемое в неё **не идёт**. проверяемое в неё **не идёт**.
+10 -11
View File
@@ -1,18 +1,18 @@
--- ---
name: task-wording name: task-wording
description: "Вычитка языка записей каталога задач по информационному стилю — задачи, цели, строки индексов и причины отказа. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в слаге. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form, документы проекта вычитывает doc-wording. Использовать после заведения или разбора пачки записей, до взятия в работу и на переоценке беклога. Только чтение." description: "Вычитка языка записей каталога задач по информационному стилю — задачи, строки индекса и причины отказа. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в слаге. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form, документы проекта вычитывает doc-wording. Использовать после заведения или разбора пачки записей, до взятия в работу и на переоценке беклога. Только чтение."
tools: Read, Grep, Glob tools: Read, Grep, Glob
model: sonnet model: sonnet
color: green color: green
--- ---
Ты — **вычитка языка записей каталога задач**: задач, целей, строк индексов и Ты — **вычитка языка записей каталога задач**: задач, строк индекса и причин
причин отказа. Оптика — слова и фразы, а не то, что запись описывает: ты не отказа. Оптика — слова и фразы, а не то, что запись описывает: ты не судишь,
судишь, нужна ли задача, верно ли выбрана цель и правильно ли запись оформлена. нужна ли задача и правильно ли она оформлена.
Границу держи твёрдо, и она у тебя одна. **Форму записи** — тип, заголовок по Границу держи твёрдо, и она у тебя одна. **Форму записи** — тип, заголовок по
типу, «зачем» вместо пересказа, раздел «Затрагивает», годность оракулов, связь типу, «зачем» вместо пересказа, раздел «Затрагивает», годность оракулов
со строкой «Завершения» цели — смотрит `task-form`, и тебе она не поручена даже смотрит `task-form`, и тебе она не поручена даже
там, где бросается в глаза. Отсюда же исключение, и оно **против** тебя: там, где бросается в глаза. Отсюда же исключение, и оно **против** тебя:
неудачное слово **в заголовке** судит `task-form`, потому что заголовок целиком неудачное слово **в заголовке** судит `task-form`, потому что заголовок целиком
его. Увидел не по своей части — скажи одной строкой в конце доклада, не его. Увидел не по своей части — скажи одной строкой в конце доклада, не
@@ -27,9 +27,8 @@ color: green
## Что тебе дают ## Что тебе дают
Список записей или каталог задач: файлы `items/<slug>.md`, а с ними — индексы Список записей или каталог задач: файлы `items/<slug>.md`, а с ними —
(`BACKLOG.md`, `ROADMAP.md`, `REJECTED.md`), где та же запись представлена `BACKLOG.md` и `REJECTED.md`, где та же запись представлена строкой. **Строка индекса вычитывается наравне с файлом**: по ней запись
строкой. **Строка индекса вычитывается наравне с файлом**: по ней запись
выбирают, не открывая тела, и «зачем» в ней повторяется дословно. выбирают, не открывая тела, и «зачем» в ней повторяется дословно.
Плюс, если зовущий их назвал, документы проекта — паспорт, архитектура, Плюс, если зовущий их назвал, документы проекта — паспорт, архитектура,
@@ -200,8 +199,8 @@ color: green
критериев `check` только считает — поимённо их судит `tasks.py ready`, и это критериев `check` только считает — поимённо их судит `tasks.py ready`, и это
тоже не твоя находка: твоя — язык того, что уже написано. тоже не твоя находка: твоя — язык того, что уже написано.
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она, **Содержание работы**: нужна ли задача, не крупна ли она, достаточна ли
достаточна ли декомпозиция. Это разбор, и его ведёт человек со скиллом `task-track`. декомпозиция. Это разбор, и его ведёт человек со скиллом `task-track`.
**Полезное действие, параллельность и работающий заголовок** — тоже не твои. **Полезное действие, параллельность и работающий заголовок** — тоже не твои.
Они в доктрине языка, судит их человек: находка по ним требует увидеть текст Они в доктрине языка, судит их человек: находка по ним требует увидеть текст
+1 -1
View File
@@ -31,7 +31,7 @@
| учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе |
| источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять |
**Свой скилл зовётся полным именем**`av-dev:doc-canon`, `av-dev:task-track`, **Свой скилл зовётся полным именем**`av-dev:canon`, `av-dev:task-track`,
`av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную
копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в
поведении. поведении.
+129
View File
@@ -0,0 +1,129 @@
# Оси процесса
**Это дом перечня, а не значений.** Что означает каждое значение и как оно
работает, знает владелец оси — здесь только сама ось, её дом и **чего она не
решает**. Второй пересказ механики разошёлся бы с первым; перечень же нужен
целиком и в одном месте, потому что вопрос «а не задаёт ли это метку» задают из
скилла, который метку не ведёт.
**Ось — это закрытый перечень значений, по которому что-то ветвится.** Признак
проверяемый, и он отсекает похожее: темы ревью и документы проекта — списки
**открытые**, их пополняет проект, и перечень в плагине протух бы на первом же
своём документе. Модель прохода — не ось, а цена прогона; её дом — «Модель по
проходу» в `code-review`, механизация — `frontmatter.py`.
## Перечень
| Ось | Значения | Дом |
| --- | --- | --- |
| стадия проекта | `build` `support` | `task-track/SKILL.md`, «Две стадии» |
| тип записи | `feature` `fix` `chore` `research` | `task-track/SKILL.md`, «Тип записи» |
| сценарий | решение · обслуживание · разведка | `code-resolve/SKILL.md`, «Развилка» |
| метка | `small` `medium` `large` | `code-review/SKILL.md`, «Метки» |
| режим прогона | с меткой · без метки | здесь, ниже |
| стадия ревью | дизайн · код | `code-review/SKILL.md`, «Ревью дизайна» |
| категория документа | тема · источник темы · процессный | `canon/references/canon.md` |
| severity находки | `critical` `major` `minor` `nit` | `code-review/references/finding-contract.md` |
| коды выхода | 0 1 2 3 4 | здесь, ниже |
Две оси стоят домом **здесь**, и обе по одной причине: владельца у них нет.
Коды выхода делят восемь скриптов и три скилла, режим прогона — конвейер, сценарий
обслуживания и два устава.
## Что на что влияет
Клетка называет **место**, где связка описана; сама связка живёт там.
| Влияет | На что | Где описано |
| --- | --- | --- |
| стадия проекта | что значит порядок строк беклога: зависимость или важность | `task-track/SKILL.md`, «Две стадии» |
| стадия проекта | сколько у беклога секций, как его пополняют, применим ли груминг | там же и `task-groom/SKILL.md`, «Груминг — операция доработки» |
| стадия проекта | метку и глубину — **не влияет, и это записано явно** | `task-track/SKILL.md`, «Тип записи» |
| стадия проекта | тип записи — **не влияет, и это записано явно** | `task-track/SKILL.md`, «Две стадии» |
| стадия проекта | как серьёзность находки ложится в список | `task-track/references/from-review.md` |
| стадия проекта | где сценарии кладут свой исход и чем закрывают переходное состояние | `code-resolve/references/research.md`, `task-track/references/adopt.md` |
| тип записи | сценарий — **предлагает**, подтверждает предмет работы | `task-track/SKILL.md`, «Тип записи» |
| тип записи | метку и глубину — **не влияет, и это записано явно** | там же |
| сценарий | режим прогона: обслуживание идёт без метки | `code-resolve/references/maintain.md` |
| метка | состав проходов обеих стадий | `code-review/SKILL.md`, «Метки» |
| метка | глубину темы: против чего смотрят и как | там же |
| режим прогона | состав проходов и саму возможность запуска прохода | `code-review/SKILL.md`, «Прогон без change» |
| категория документа | заводит ли документ направление проверки | `canon.md`, «Три категории» |
| severity | что с находкой делают дальше | `code-review/SKILL.md`, «Что происходит с находками» |
**Пять клеток пусты, и это сказано намеренно, а не забыто.**
**Категория документа × режим прогона.** На прогоне **с меткой** своя тема
проекта закрыта при любом значении: `review-basics` — приёмник проектных тем и
при `small`, и при `large`, и при `medium`. На прогоне **без метки** план
фиксирован сценарием — `autotests`, `operations`, `conventions`, — и своих тем
проекта в нём нет. Значит, документ, заведённый проектом как тема, на
обслуживании не смотрит никто, и строкой это нигде не называется.
**Стадия проекта × метка.** Изменение на стройке ничем не проще того же
изменения на доработке: метку назначает разметка по факту изменения, и стадия в
неё не входит. Заманчивая мысль «на стройке всё `small`, потому что приложения
ещё нет» разбивается о первый же шаг, кладущий схему хранилища.
**Стадия проекта × режим прогона и × стадия ревью.** Не влияет ни на одну:
режим выбирает сценарий, стадию ревью — наличие дизайна. Прогон обслуживания на
стройке — обычное дело (первые шаги плана заводят гейт и сборку), и идёт он там
так же, как на доработке.
**Стадия проекта × категория документа и × коды выхода.** Не влияет: категория —
свойство документа, коды — общий словарь скриптов. Названо потому, что перечень
объявлен полным, и клетка без ответа читается как забытая.
**Режим прогона × severity.** Триаж обязателен всегда, в том числе без метки. Но
часть оснований `critical` — построенный путь к отказу, замер — добывается
проходами, которые без метки не запускаются. Значит ли это, что `critical` на
прогоне обслуживания не бывает, или что его основания там другие, не сказано.
## Режим прогона
<!-- дом: режим-прогона -->
**Прогон ревью идёт в одном из двух режимов, и режим — не глубина.**
- **С меткой** — обычный прогон по change: разметку сделал `review-scope`, состав
обеих стадий выведен из метки.
- **Без метки** — прогон сценария обслуживания: change нет, размечать нечего,
план фиксирован и назван сценарием. Разметчик не запускается вовсе.
**Без метки — не то же самое, что `small`.** `small` — это суждение о размере и
сложности, снятое с изменения; отсутствие метки — утверждение, что снимать её
не с чего. Проход, подставивший себе `small` там, где метки нет, вывел бы
глубину из ничего.
**Режим правит не только состав, но и саму возможность запуска.** Проход, у
которого запуск задан меткой, без метки не имеет ответа на вопрос «запускаться
ли» — и ответ ему даёт план сценария, а не умолчание.
<!-- /дом: режим-прогона -->
## Коды выхода
<!-- дом: коды-выхода -->
**Коды выхода — общий словарь всех скриптов `av-dev`. Ветвись на коде, а не на
тексте вывода.**
| Код | Что случилось |
| --- | --- |
| 0 | сошлось |
| 1 | дрейф: рабочая ситуация, чинится |
| 2 | ошибка употребления: аргументы или нарушенное правило |
| 3 | окружение: не тот каталог, битый конфиг, нет инструмента |
| 4 | внутренний сбой — дефект скрипта, доложить |
**Различать 1 и 3 обязательно.** «Дрейф» — рабочая ситуация, и чинится она
правкой предмета; «окружение» — нерабочая, и повтор той же командой не поможет.
Одинаковая реакция на них неверна в обоих случаях.
<!-- /дом: коды-выхода -->
Словарь был объявлен «общим» в одиннадцати местах, и каждое объявление
перечисляло **свой** набор соседей: «тот же, что у `tasks.py`», «тот же, что у
`tasks.py`, `docs.py` и `copies.py`», «общий словарь скриптов av-dev». Ни одно из
них не было домом, все — списки по памяти. Отсюда дом здесь: у словаря восемь
скриптов-потребителей и ни одного владельца.
+36 -4
View File
@@ -26,9 +26,9 @@
[tasks] [tasks]
dir = "tasks" # каталог задач от корня репозитория dir = "tasks" # каталог задач от корня репозитория
stage = "build" # стадия проекта: build | support
items = "items" # имена частей каталога — необязательны items = "items" # имена частей каталога — необязательны
backlog = "BACKLOG.md" backlog = "BACKLOG.md"
roadmap = "ROADMAP.md"
Коды выхода зовущих скриптов — общий словарь av-dev; отсюда возвращается Коды выхода зовущих скриптов — общий словарь av-dev; отсюда возвращается
исключение `ConfigError`, а решает по нему вызывающий. исключение `ConfigError`, а решает по нему вызывающий.
@@ -55,8 +55,8 @@ LEGACY = ("docs/.docs.json", "docs/.pm.json")
LEGACY_TASKS = ".tasks.json" LEGACY_TASKS = ".tasks.json"
# Версия раскладки — одна на плагин. Журнал версий — references/changelog.md # Версия раскладки — одна на плагин. Журнал версий — references/changelog.md
# скилла `doc-canon`, повышает его операция `upgrade`. # скилла `canon`, повышает его операция `upgrade`.
VERSION = 1 VERSION = 3
VERSION_KEY = "version" VERSION_KEY = "version"
@@ -280,6 +280,33 @@ def merge_section(root: Path, name: str, values: dict) -> list[str]:
return added return added
def set_section_key(root: Path, name: str, key: str, value: str) -> None:
"""Заменить значение ключа секции, не тронув остального.
Отличается от `merge_section` ровно тем, ради чего и заведена: та **не
трогает** ключ, который уже есть, потому что дописывает умолчания в чужой
файл. Здесь же значение меняет команда, которую позвал человек, и не
переписать его значило бы промолчать о выполненном действии. Ключа нет —
он дописывается, секции нет — заводится: и то и другое законное состояние
файла, который правят руками.
"""
path = root / CONFIG_NAME
lines = path.read_text(encoding="utf-8").splitlines() if path.is_file() else []
start = next((i for i, ln in enumerate(lines) if _is_header(ln, name)), None)
if start is None:
merge_section(root, name, {key: value})
return
end = next((i for i in range(start + 1, len(lines)) if _is_header(lines[i])),
len(lines))
pattern = re.compile(rf"^(\s*{re.escape(key)}\s*=\s*)([^#]*?)(\s*(?:#.*)?)$")
for i in range(start + 1, end):
if (match := pattern.match(lines[i])):
lines[i] = f"{match.group(1)}{quote(value)}{match.group(3)}"
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
return
merge_section(root, name, {key: value})
def missing_keys(root: Path, name: str, values: dict) -> dict: def missing_keys(root: Path, name: str, values: dict) -> dict:
"""Ключи секции, которые уже есть и разошлись с тем, что мы собирались дать. """Ключи секции, которые уже есть и разошлись с тем, что мы собирались дать.
@@ -317,7 +344,12 @@ def skeleton(number: int, docs: dict | None = None, tasks: dict | None = None) -
out += ["", "[tasks]", out += ["", "[tasks]",
"# каталог задач от корня репозитория; имена частей — умолчания скрипта", "# каталог задач от корня репозитория; имена частей — умолчания скрипта",
f"dir = {quote(tasks.get('dir', 'tasks'))}"] f"dir = {quote(tasks.get('dir', 'tasks'))}"]
for key in ("items", "backlog", "roadmap"): if tasks.get("stage"):
out += ["# стадия проекта: build — беклог это план стройки, порядок строк"
" значит зависимость;",
"# support — беклог это очередь правок, порядок значит важность",
f"stage = {quote(tasks['stage'])}"]
for key in ("items", "backlog", "rejected"):
if tasks.get(key): if tasks.get(key):
out.append(f"{key} = {quote(tasks[key])}") out.append(f"{key} = {quote(tasks[key])}")
return "\n".join(out) + "\n" return "\n".join(out) + "\n"
+14 -5
View File
@@ -21,9 +21,18 @@
проверяет вовсе, — а вложенных блоков разметка не знает, и внутри `язык-правила` проверяет вовсе, — а вложенных блоков разметка не знает, и внутри `язык-правила`
его было бы не забрать отдельно. его было бы не забрать отдельно.
Правила — для всего, что пишется словами: задачи и цели, документы канона, Правила — для всего, что пишется словами **в этом плагине**: задачи, документы
решения ADR, записки разведки, сообщения коммитов. Не для кода и не для канона, решения ADR, записки разведки. Не для кода и не для сообщений программы
сообщений программы пользователю — там свои конвенции проекта. пользователю — там свои конвенции проекта.
**Сообщения коммитов сюда не входят, и это названо намеренно.** Их форму держит
отдельный плагин `av-dev-git`, скилл `commit`, и она с этими правилами
расходится по существу: там предписан результат страдательным залогом
(«добавлены», «обновлено»), здесь — действие активным. Расхождение осознанное:
строка коммита отвечает на «что стало», а не на «что я сделал», и читают её в
`git log` подряд сотнями. Объявлять юрисдикцию над чужим плагином, до которого
отсюда нет и ссылки, значило бы завести правило, нарушение которого никто не
увидит.
Основа — **информационный стиль** Максима Ильяхова ([учебник Основа — **информационный стиль** Максима Ильяхова ([учебник
бюро](https://bureau.ru/projects/book-text/), книга «Пиши, сокращай»). Он бюро](https://bureau.ru/projects/book-text/), книга «Пиши, сокращай»). Он
@@ -228,8 +237,8 @@
## Доклад вычитки ## Доклад вычитки
Не правило языка, а **контракт прохода**: форма, в которой находка приходит к Не правило языка, а **контракт прохода**: форма, в которой находка приходит к
человеку. Живёт здесь потому, что проходов вычитки несколько — по одному на человеку. Живёт здесь потому, что проходов вычитки два — `doc-wording` по документам и
плагин, — и разойтись формой они не должны. `task-wording` по записям задач, — и разойтись формой они не должны.
<!-- дом: вычитка-доклад --> <!-- дом: вычитка-доклад -->
+9 -7
View File
@@ -1,8 +1,8 @@
# Сопровождение и эксплуатация # Сопровождение и эксплуатация
**Это дом.** Словарь «чем держат проект» назван в трёх местах трёх разных **Это дом.** Словарь «чем держат проект» назван в трёх местах трёх разных
скиллов: секция `Сопровождение` в роадмапе (`task-track`), раздел «Эксплуатация» скиллов: задачи типа `chore` (`task-track`), раздел «Эксплуатация»
в `architecture.md` (`doc-canon`) и тема ревью `operations` (`code-review`). Ни в `architecture.md` (`canon`) и тема ревью `operations` (`code-review`). Ни
один из трёх им не владеет, поэтому дом стоит в `shared/`. один из трёх им не владеет, поэтому дом стоит в `shared/`.
Три перечня «чем держат проект» уже разъезжались — на «метриках и логах» против Три перечня «чем держат проект» уже разъезжались — на «метриках и логах» против
@@ -18,16 +18,18 @@
| Место | Уровень | Что там | | Место | Уровень | Что там |
| --- | --- | --- | | --- | --- | --- |
| `ROADMAP.md`, секция `Сопровождение` | план | **работы**, которые собираемся делать: цели и их задачи | | `BACKLOG.md`, задачи `chore` | план | **работы**, которые собираемся делать |
| `architecture.md`, раздел «Эксплуатация» | состояние | **как устроено сейчас**: где работает, что рядом, кто перезапускает | | `architecture.md`, раздел «Эксплуатация» | состояние | **как устроено сейчас**: где работает, что рядом, кто перезапускает |
| тема ревью `operations` | оптика | **чем проверяем**: «это упало через неделю на проде» | | тема ревью `operations` | оптика | **чем проверяем**: «это упало через неделю на проде» |
Слово **«поддержка» не употребляется вовсе** — в нём слышится помощь Слово **«поддержка» не употребляется вовсе** — в нём слышится помощь
пользователю, а это другая работа. пользователю, а это другая работа. По той же причине им не названа и **стадия
проекта**: у неё имя «доработка» (`support`), словарь — `task-track`, «Две
стадии».
**Граница с возможностями проходит по тому, кто наблюдает.** «Приложение **Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
сообщает о своём состоянии» — возможность приложения, её место среди прочих сообщает о своём состоянии» — возможность приложения, её место среди прочих
целей: наблюдает пользователь сервиса. «Дежурный видит состояние на одном задач: наблюдает пользователь сервиса. «Дежурный видит состояние на одном
экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в разные экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в задачи
секции роадмапа, и это верно — секции отвечают на разные вопросы. разных типов, и это верно — типы отвечают на разные вопросы.
@@ -1,9 +1,9 @@
--- ---
name: doc-canon name: canon
description: Привести проект к канону документов av-dev и держать его в соответствии — три операции одной машиной сравнения. check — что разошлось с текущей версией канона; adopt — перевод проекта из любой прежней раскладки (docs/specs, drafts, backlog, BRIEF.md, review-brief) в канон с переносом файлов; upgrade — повышение проекта с версии канона N до текущей по журналу версий. Использовать, когда просят проверить документацию проекта, перевести проект на канон, обновить его под новую версию канона или когда пришли в старый проект и надо понять, что в нём не так. Заведение нового проекта с нуля — скилл av-dev:doc-init. description: Форма раскладки проекта под av-dev и её обновление — три операции одной машиной сравнения. check — что разошлось с текущей версией раскладки; adopt — перевод проекта из любой прежней раскладки (docs/specs, drafts, backlog, BRIEF.md, review-brief) в канон с переносом файлов и вызовом владельцев каталога задач и openspec/; upgrade — повышение проекта с версии N до текущей по журналу версий, и повышается им вся раскладка, включая каталог задач. Использовать, когда просят проверить документацию проекта, перевести проект на канон, обновить его под новую версию раскладки или когда пришли в старый проект и надо понять, что в нём не так. Имя без префикса намеренно — скилл держит форму всех артефактов проекта, а не один их вид. Содержимое документов ведёт av-dev:doc-sync, форму записей задач — av-dev:task-track, заведение проекта с нуля — av-dev:doc-init.
--- ---
# Приведение проекта к канону # Форма раскладки проекта
Три операции, одна машина сравнения с разными исходами: Три операции, одна машина сравнения с разными исходами:
@@ -13,6 +13,14 @@ description: Привести проект к канону документов
| `adopt` | проект в чужой раскладке | перенос в канон | | `adopt` | проект в чужой раскладке | перенос в канон |
| `upgrade` | канон вырос, проект отстал | по журналу версий | | `upgrade` | канон вырос, проект отстал | по журналу версий |
**Имя без префикса, и это не случайность.** Остальные скиллы названы по
материалу, с которым работают, — `doc-`, `task-`, `code-`; этот работает не с
материалом, а с **формой**, и она у всех частей проекта одна. `check` сверяет
раскладку документов, `adopt` заводит все части сразу и зовёт владельцев каталога
задач и `openspec/`, `upgrade` повышает **всю** раскладку одним журналом версий —
и документы, и каталог задач. Содержимое при этом не его: документы ведёт
`av-dev:doc-sync`, записи задач — `av-dev:task-track`.
**Определение канона — [references/canon.md](references/canon.md).** Здесь оно не **Определение канона — [references/canon.md](references/canon.md).** Здесь оно не
пересказывается: два описания одной раскладки разъедутся, и работать будет то, пересказывается: два описания одной раскладки разъедутся, и работать будет то,
которое прочитали последним. Прочитай его **до** первой правки. которое прочитали последним. Прочитай его **до** первой правки.
@@ -45,7 +53,7 @@ description: Привести проект к канону документов
## Инструмент ## Инструмент
``` ```
ds="$CLAUDE_PLUGIN_ROOT/skills/doc-canon/scripts/docs.py" ds="$CLAUDE_PLUGIN_ROOT/skills/canon/scripts/docs.py"
python3 $ds check --dir <корень> [--base <rev>] # раскладка, ссылки, версия, сверки python3 $ds check --dir <корень> [--base <rev>] # раскладка, ссылки, версия, сверки
python3 $ds version --dir <корень> # версия раскладки: скрипта и проекта python3 $ds version --dir <корень> # версия раскладки: скрипта и проекта
@@ -58,11 +66,30 @@ python3 $ds bump --dir <корень> # поднять вер
не проверяет никто, и это надо сказать строкой доклада, а не считать, что она не проверяет никто, и это надо сказать строкой доклада, а не считать, что она
верна. верна.
**Коды выхода — тот же словарь, что у `tasks.py`:** 0 сошлось, 1 дрейф, 2 ошибка **Копия.** Дом словаря — `shared/axes.md` в репозитории плагина: словарь общий
употребления, 3 окружение, 4 внутренний сбой. Ветвись на коде, а не на тексте. для всех скриптов, и ни один скилл им не владеет. Правится дом, а не этот файл.
Различать 1 и 3 обязательно: «дрейф раскладки» — рабочая ситуация, «это не <!-- копия: коды-выхода из av-dev/shared/axes.md -->
корень проекта» — нерабочая.
**Коды выхода — общий словарь всех скриптов `av-dev`. Ветвись на коде, а не на
тексте вывода.**
| Код | Что случилось |
| --- | --- |
| 0 | сошлось |
| 1 | дрейф: рабочая ситуация, чинится |
| 2 | ошибка употребления: аргументы или нарушенное правило |
| 3 | окружение: не тот каталог, битый конфиг, нет инструмента |
| 4 | внутренний сбой — дефект скрипта, доложить |
**Различать 1 и 3 обязательно.** «Дрейф» — рабочая ситуация, и чинится она
правкой предмета; «окружение» — нерабочая, и повтор той же командой не поможет.
Одинаковая реакция на них неверна в обоих случаях.
<!-- /копия: коды-выхода -->
Здесь это значит: «дрейф раскладки» — рабочая ситуация, «это не корень проекта» —
нерабочая.
### Граница механизируемого — объявляется вслух ### Граница механизируемого — объявляется вслух
@@ -109,7 +136,7 @@ capability: незаполненный канон это переходное с
| учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе |
| источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять |
**Свой скилл зовётся полным именем** — `av-dev:doc-canon`, `av-dev:task-track`, **Свой скилл зовётся полным именем** — `av-dev:canon`, `av-dev:task-track`,
`av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную
копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в
поведении. поведении.
@@ -228,10 +255,11 @@ capability), `openspec/config.yaml`.
**Задачи `docs.py` не проверяет** — их ведёт другой скилл, и согласованность **Задачи `docs.py` не проверяет** — их ведёт другой скилл, и согласованность
каталога показывает только `tasks.py check`. Позвал на шаге 5 скилл задач — каталога показывает только `tasks.py check`. Позвал на шаге 5 скилл задач —
его отчёт идёт в доклад отдельной строкой, и пункт «задачи без цели» в нём его отчёт идёт в доклад отдельной строкой, и зелёным он сразу не станет:
зелёным не станет: цели не сочиняются адаптацией (запрет записан у того, кто у перенесённых записей нет критериев приёмки, а `check` без объявленной
ведёт задачи), их проставляет человек порциями переоценки на первом груминге — **стадии** отказывает вовсе. Стадию называет человек (`tasks.py stage
скилл `av-dev:task-groom`. build|support`) — машина её не выводит: список пунктов одинаково выглядит и
планом стройки, и очередью правок.
### 5. Объяви переходное состояние ### 5. Объяви переходное состояние
@@ -6,7 +6,7 @@
[журнала](changelog.md). Литерал в шапке был третьим и отстал на первом же [журнала](changelog.md). Литерал в шапке был третьим и отстал на первом же
повышении — версию 13 он пережил, объявляя канон двенадцатым. повышении — версию 13 он пережил, объявляя канон двенадцатым.
Это **единственный дом определения канона**. Скиллы `doc-init`, `doc-canon` и `doc-sync` Это **единственный дом определения канона**. Скиллы `doc-init`, `canon` и `doc-sync`
читают его, а не пересказывают: три описания одной раскладки разъедутся, и читают его, а не пересказывают: три описания одной раскладки разъедутся, и
работать будет то, которое прочитали последним. Меняется канон — меняется этот работать будет то, которое прочитали последним. Меняется канон — меняется этот
файл и появляется запись в [changelog.md](changelog.md). файл и появляется запись в [changelog.md](changelog.md).
@@ -19,7 +19,7 @@
Рядом лежит OpenSpec, у которого структура тоже строгая. Рядом лежит OpenSpec, у которого структура тоже строгая.
Цена принята сознательно: плагин не переносится на чужой репозиторий как есть — Цена принята сознательно: плагин не переносится на чужой репозиторий как есть —
чужой репозиторий **приводится** к канону скиллом `doc-canon`. чужой репозиторий **приводится** к канону скиллом `canon`.
Раскладка отвечает, **где** текст лежит и на какой вопрос отвечает. Каким он Раскладка отвечает, **где** текст лежит и на какой вопрос отвечает. Каким он
должен быть **словами** — общий для всех документов канона файл должен быть **словами** — общий для всех документов канона файл
@@ -29,7 +29,7 @@
## Сопровождение и эксплуатация — целое и часть ## Сопровождение и эксплуатация — целое и часть
Словарь этой темы — [shared/operations.md](../../../shared/operations.md): Словарь этой темы — [shared/operations.md](../../../shared/operations.md):
целое и часть, три места одной темы (секция роадмапа, раздел архитектуры, тема целое и часть, три места одной темы (задачи `chore`, раздел архитектуры, тема
ревью `operations`) и граница с возможностями проекта. Здесь он не ревью `operations`) и граница с возможностями проекта. Здесь он не
пересказывается: копия жила рядом с домом в одном дереве и была ровно тем пересказывается: копия жила рядом с домом в одном дереве и была ровно тем
вторым домом, против которого правило и написано. вторым домом, против которого правило и написано.
@@ -71,6 +71,9 @@ openspec/
## Три категории документов ## Три категории документов
Категория документа — ось процесса; перечень осей и того, чего каждая **не**
решает, — [shared/axes.md](../../../shared/axes.md).
Раньше здесь стояло плоское правило «каждый документ `docs/` — тема ревью». Оно Раньше здесь стояло плоское правило «каждый документ `docs/` — тема ревью». Оно
неверно ровно наполовину: паспорт и схема хранилища ревью нужны, но темами не неверно ровно наполовину: паспорт и схема хранилища ревью нужны, но темами не
являются, а журнал решений и журнал наблюдений ревью изменения не нужны вовсе. являются, а журнал решений и журнал наблюдений ревью изменения не нужны вовсе.
@@ -348,42 +351,42 @@ kebab-case.** Причина не эстетическая: имя файла с
скриптом и своими проверками; где каталог лежит и как названы его части, говорит скриптом и своими проверками; где каталог лежит и как названы его части, говорит
секция `[tasks]` в `.av-dev.toml`. Версия там одна на всю раскладку: канон и секция `[tasks]` в `.av-dev.toml`. Версия там одна на всю раскладку: канон и
каталог задач двигаются вместе, потому что ведёт их один плагин. каталог задач двигаются вместе, потому что ведёт их один плагин.
Канон **резервирует место** в `docs/` и внутрь не смотрит: Каталог лежит **в корне репозитория, вне `docs/`** — места в `docs/` канон ему
`docs.py` каталог не открывает, его отсутствия не считает дрейфом и согласованность больше не резервирует (переезд — запись 11 закрытого журнала), а внутрь не
задач не проверяет. Проект, не заведший каталог задач, их не ведёт смотрит и подавно: `docs.py` каталог не открывает, его отсутствия не считает
дрейфом и согласованность задач не проверяет. Проект, не заведший каталог задач, их не ведёт
вовсе, и отказом это быть не может. вовсе, и отказом это быть не может.
Раскладку, форму записи и команды держит скилл `av-dev:task-track`. Ниже — то, Раскладку, форму записи и команды держит скилл `av-dev:task-track`. Ниже — то,
от чего зависит, читается ли проект как продукт: канон высказывается об этом от чего зависит, читается ли проект как продукт.
потому, что роадмап отвечает на вопрос о **системе**, а не о работах.
**`ROADMAP.md` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это **Индекс работ один — `BACKLOG.md`, и «что приложение уже умеет» он не
не очередь работ: цель — **возможность приложения**, задача — шаг к ней. отвечает.** На этот вопрос отвечают `openspec/specs/` (нормативное поведение) и
Достигнутая цель из роадмапа **не исчезает** — строка с датой переезжает в `git log` индекса (когда и в каком порядке оно появилось). Роадмапа в каноне
секцию достигнутого, потому что «что умеет» и есть половина вопроса, ради нет: половину своего вопроса он дублировал беклогом, а вторую — спеками.
которого документ открывают. Вторым домом поведения роадмап при этом не
становится: нормативное поведение живёт в `openspec/specs/`, роадмап отвечает,
**когда и в каком порядке** оно появилось.
**У каждой записи есть тип, и тип решает, что с ней можно делать.** Дом типа — **У проекта есть стадия, и она решает, что значит порядок строк беклога:**
`build` — зависимость, `support` — важность. Канон её называет, потому что от
неё зависит, читается ли список работ как план стройки или как очередь правок;
механика — `task-track`, «Две стадии».
**У каждой задачи есть тип, и тип решает, что с ней можно делать.** Дом типа —
поле меты `Тип` первой строкой; эмодзи в заголовке от него производна. Словарь поле меты `Тип` первой строкой; эмодзи в заголовке от него производна. Словарь
закрыт: закрыт:
| Тип | Что это | | Тип | Что это |
| --- | --- | | --- | --- |
| 🎯 `goal` | возможность приложения |
| ✨ `feature` | снаружи появляется то, чего не было | | ✨ `feature` | снаружи появляется то, чего не было |
| 🐞 `fix` | поведение расходится с заявленным | | 🐞 `fix` | поведение расходится с заявленным |
| 🧹 `chore` | обслуживание, поведение не меняется | | 🧹 `chore` | обслуживание, поведение не меняется |
| 🔬 `research` | исход — знание, а не изменение | | 🔬 `research` | исход — знание, а не изменение |
**Схемы записи здесь нет намеренно.** Какие разделы тип требует, нужна ли ему **Схемы записи здесь нет намеренно.** Какие разделы тип требует — скилл
цель и берётся ли он в работу — скилл `av-dev:task-track`, раздел «Тип `av-dev:task-track`, раздел «Тип записи», подробно — по файлу на тип в его
записи», подробно — по файлу на тип в его `references/task-<тип>.md`. Канон `references/task-<тип>.md`. Канон фиксирует **словарь**, потому что от него
фиксирует **словарь**, потому что зависит, читается ли проект как продукт; схема — механика ведения задач, и второй
от него зависит, читается ли проект как продукт; схема — механика ведения задач, её экземпляр разошёлся бы с первым (он и разошёлся: канон успел объявить цель у
и второй её экземпляр разошёлся бы с первым (он и разошёлся: канон успел `fix` запрещённой, хотя она была необязательна, — и целей теперь нет вовсе).
объявить цель у `fix` запрещённой, хотя она там необязательна).
Схема требуется **к взятию в работу**, а не к заведению: беклог пополняется чаще, Схема требуется **к взятию в работу**, а не к заведению: беклог пополняется чаще,
чем разбирается, и требование на входе выгоняло бы в заметки то, что должно чем разбирается, и требование на входе выгоняло бы в заметки то, что должно
@@ -451,7 +454,7 @@ kebab-case.** Причина не эстетическая: имя файла с
| почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки | | почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки |
| граница домена, «чем не является» | `passport.md` | | граница домена, «чем не является» | `passport.md` |
| инвариант и его severity | `CLAUDE.md` | | инвариант и его severity | `CLAUDE.md` |
| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` | | что осталось сделать и в каком порядке | `tasks/BACKLOG.md` |
| измеренное число | `research/` | | измеренное число | `research/` |
| настройка с числовым значением | `database.md` | | настройка с числовым значением | `database.md` |
| периметр и модель угроз | `security.md` | | периметр и модель угроз | `security.md` |
@@ -485,8 +488,8 @@ kebab-case.** Причина не эстетическая: имя файла с
| --- | --- | | --- | --- |
| `docs/review-brief.md` | документы канона и есть бриф; остаток — в `review.md` | | `docs/review-brief.md` | документы канона и есть бриф; остаток — в `review.md` |
| `docs/specs/` | `openspec/specs/` (поведение) и `architecture.md` (обзор) | | `docs/specs/` | `openspec/specs/` (поведение) и `architecture.md` (обзор) |
| `docs/drafts/` | идея → запись `research`; отказ → ADR; порядок → `ROADMAP.md`; размышление → `opsx:explore` | | `docs/drafts/` | идея → запись `research`; отказ → ADR; порядок → `BACKLOG.md`; размышление → `opsx:explore` |
| `docs/plan.md` | `tasks/ROADMAP.md` | | `docs/plan.md` | `tasks/BACKLOG.md` |
| `BRIEF.md` | `passport.md` | | `BRIEF.md` | `passport.md` |
| `docs/backlog/` | `tasks/` в корне репозитория | | `docs/backlog/` | `tasks/` в корне репозитория |
| `docs/review-journal.md`, `docs/review/journal.md` | `docs/review.md` | | `docs/review-journal.md`, `docs/review/journal.md` | `docs/review.md` |
@@ -524,7 +527,7 @@ kebab-case.** Причина не эстетическая: имя файла с
разрез, что между `task-form` и `task-wording`. разрез, что между `task-form` и `task-wording`.
**Зовутся оба одинаково и одним скиллом — `av-dev:doc-healthcheck`, на весь **Зовутся оба одинаково и одним скиллом — `av-dev:doc-healthcheck`, на весь
канон разом; шагом `adopt` и шагом `upgrade` его зовёт `doc-canon`.** Не на синке канон разом; шагом `adopt` и шагом `upgrade` его зовёт `canon`.** Не на синке
документации: `doc-consistency` на документации: `doc-consistency` на
`opus` по каждой сделанной задаче не окупается, а расхождение между двумя документами по `opus` по каждой сделанной задаче не окупается, а расхождение между двумя документами по
определению требует двух, и на большинстве задач синк правит один. Пачка, определению требует двух, и на большинстве задач синк правит один. Пачка,
+197
View File
@@ -0,0 +1,197 @@
# Журнал версий раскладки
Одна запись на версию. Проект знает свою версию из ключа `version` в
`.av-dev.toml`; операция `upgrade` скилла `av-dev:canon` идёт по записям
снизу вверх от версии проекта до текущей и делает то, что в них названо.
Правило записи: **что добавилось, что переехало, что удалено, что сделать
проекту**. Без последнего пункта запись бесполезна — по ней и работает
`upgrade`.
Версия — целое число. Обратной совместимости нет: есть «приведён» и «не
приведён». Версия **одна на всю раскладку** — и на документы канона, и на
каталог задач: ведёт их один плагин, и второе число означало бы только вопрос,
по какому журналу повышать.
**До слияния журналов было два**, и нумерация в них своя:
[changelog-before-merge.md](changelog-before-merge.md) — канон документов,
версии 114; [changelog-tasks-before-merge.md](changelog-tasks-before-merge.md) —
формат задач, версия 1. Оба **закрыты и не переписаны**: адрес, верный на день
записи, там и остался. Проект, отставший от канона 14, идёт сперва по ним, а
потом по этому журналу — порядок назван в записи 1.
---
## Версия 3 — 2026-08-13
Тип записи `goal` и индекс `ROADMAP.md` упразднены; у проекта появилась
**стадия**`build` (беклог это план стройки, порядок строк значит зависимость)
или `support` (очередь правок, порядок значит важность).
Цель была зонтиком над параллельными направлениями — она нужна там, где список
работ нельзя выстроить в один порядок. У проекта, который ведёт один человек,
такого не бывает, и роадмап при этом наполовину дублировал беклог («чего ещё не
умеет» = «что осталось в списке»), а вторую половину («что уже умеет») отвечают
`openspec/specs/` и `git log` индекса.
**Что переехало.** Индекс остался один — `BACKLOG.md`. Поле меты `Секция` стало
`Категория`; теги `goal:<слаг>`, `decomposed` и раздел `Завершение` упразднены;
команды `list --goal`, `edit --goal`, `edit --section` и ключи `[tasks] roadmap`,
`[tasks] completion_heading` — тоже. Появились ключ `[tasks] stage`, команда
`tasks.py stage` и флаги `init --stage`, `adopt scan --stage`.
**Что сделать проекту. Порядок шагов обязателен**, и первый шаг — не команда:
пока в `[tasks]` лежит упразднённый ключ, **любая** подкоманда `tasks.py`
отвечает кодом 3 и работать нечем.
1. **Вычистить конфиг руками.** Из секции `[tasks]` в `.av-dev.toml` удалить
ключи `roadmap` (или `plan`) и `completion_heading`. Каждый из них — код 3 на
любой команде, и названы они здесь оба: второй легко пропустить, потому что
его упразднение не видно по имени файла.
2. **Удалить `tasks/ROADMAP.md`.** Секция `Готово` уходит вместе с ним и **не
переносится**: «что приложение умеет» отвечают спеки, «когда это появилось» —
`git log` беклога. Проект без `openspec/specs/` теряет здесь единственный
связный перечень достигнутого — если он нужен, сохрани его сам до удаления
(документом проекта, не задачами).
3. **Прогнать `tasks.py check --fix`.** Он снимет теги `goal:<слаг>` и
`decomposed`, переименует поле `Секция``Категория` и перепишет старую
форму меты — **в том числе у самих записей типа `goal`**. Записи `goal` при
этом останутся: во что превращается цель, машина не решает и говорит
`НЕОДНОЗНАЧНО`.
4. **Разобрать цели поштучно.** У каждой два исхода, и выбирает человек: она
становится задачей (`edit <слаг> --type feature|fix|chore|research`) либо
уходит (`close <слаг> --reason …`). Строки в беклоге у неё нет — её жильём
был роадмап, — и `edit --type` заведёт её сам, в первую секцию и в конец,
сказав об этом; место назначь потом. Раздел `Завершение` в теле переехавшей
записи **удали руками**: схеме нового типа он не принадлежит, и `check`
оставит о нём замечание. Задачи, носившие тег цели, живут дальше сами по
себе — разбирать их не нужно.
5. **Объявить стадию**`tasks.py stage build` или `tasks.py stage support`.
Приложение ещё строится и список работ линеен по зависимости — `build`;
работает и правится точечно — `support`. Без ключа `check` отказывает: порядок
строк нечем прочитать.
**Объявление беклог не трогает** — ни секций, ни файлов: оно называет то, что
уже верно. Поэтому проекту с несколькими полками, объявляющему `build`,
команда откажет и назовёт выход: слить полки самому (`move <слаг> --section
<куда> --reason …`), потому что порядок строк в слитом списке знает только
человек. Флаг `--sections` при объявлении не принимается — он для **смены**
стадии, где сливать просят явно.
6. **Поправить шапку `BACKLOG.md`.** Абзац про стадию теперь размечен парой
`<!-- стадия -->``<!-- /стадия -->`, и по нему `check` сверяет шапку с
конфигом. В беклоге, заведённом до этой версии, разметки нет — `stage` об
этом скажет. Возьми готовый абзац из свежего каталога (`tasks.py init` во
временном месте) или напиши сам: он объясняет, что значит порядок строк, и
читают вместо документации именно его.
7. **Поднять версию**`docs.py bump`. Последним шагом. Он двигает **одну**
запись за раз: отставшему на две записи проекту зовётся дважды, следом за
шагами каждой.
8. `docs.py check` и `tasks.py check --dir <каталог задач>` — до отсутствия
дрейфа.
**Проект, не прошедший записи 1 и 2, начинает с этой.** Их собственные шаги
велят гонять `tasks.py check` до зелёного, а он на упразднённом ключе отвечает
кодом 3 — то есть пройти их сегодня нельзя, не сделав шаг 1 отсюда. Записи от
этого не переписываются: порядок между ними прежний, добавлено одно условие
входа.
---
## Версия 2 — 2026-08-13
Скилл `doc-canon` стал `canon`: префикс называл материал (`doc-`), а скилл
занят не материалом, а **формой** — раскладкой всех частей проекта и общим
повышением версии. Ни один файл проекта от этого не переехал; сменились **путь к
скрипту** и **имя вызова**, а оба живут в проекте: первый — строкой гейта, второй
— в `CLAUDE.md` и в записях задач.
**Что переехало в вызовах.** `av-dev:doc-canon``av-dev:canon`. Прочие имена не
тронуты.
**Что сделать проекту.**
1. **Поправить шаг гейта.** Путь к `docs.py` сменился вместе с именем каталога
скилла: `skills/doc-canon/scripts/docs.py`
`skills/canon/scripts/docs.py`. Шаг, который не нашёл скрипт, обязан
краснеть, а не пропускаться, — проверь, что он краснеет.
2. **Поправить свои вызовы скилла**`grep -rn "doc-canon" --exclude-dir=.git .`
по проекту целиком: имя встречается в `CLAUDE.md`, в `Taskfile`, в записях
задач и в документах канона. Прежнее полное имя не разрешится вовсе.
3. **Поднять версию**`docs.py bump`. Последним шагом.
4. `docs.py check` и `tasks.py check --dir <каталог задач>` — до отсутствия
дрейфа.
**Проект, не прошедший запись 1, переименовывает дважды подряд**`skills/canon/`
`skills/doc-canon/` записью 1 и обратно этой. Порядок записей от этого не
меняется: каждая исполняется на том состоянии, которое оставила предыдущая, и
прошлая запись под новое имя не переписывается.
---
## Версия 1 — 2026-08-13
Три плагина — `av-dev-docs`, `av-dev-tasks` и `av-dev-code` — слились в один,
`av-dev`. Раскол делался под раздельную установку: проект мог взять учёт работ
без документов канона или конвейер без обоих. Практика посылку не подтвердила —
подмножество не понадобилось ни разу, — а платился раскол помеченными копиями
общих правил и веткой «плагина нет» на каждый вызов соседа.
**Что переехало в проекте.** Служебных файла было два, стал один:
| Было | Стало |
| --- | --- |
| `docs/.docs.json`, ключ `canon` | `.av-dev.toml` в корне, ключ `version` |
| `docs/.docs.json`, ключ `migrations` | `.av-dev.toml`, секция `[docs]` |
| `<каталог задач>/.tasks.json`, ключ `tasks` | тот же `version`: версия теперь одна |
| `<каталог задач>/.tasks.json`, имена частей | `.av-dev.toml`, секция `[tasks]` |
Корень выбран потому, что он есть у обоих: и у проекта без `docs/`, и у проекта
без каталога задач. Формат TOML — ради комментариев: файл лежит в репозитории
проекта, и назначение числа читают из него самого, а не из документации плагина.
**Что переехало в вызовах.** Имена скиллов сменили пространство имён и получили
префикс по прежнему плагину: `av-dev-docs:canon``av-dev:doc-canon`,
`av-dev-docs:init``av-dev:doc-init`, `av-dev-docs:docs``av-dev:doc-sync`,
`av-dev-docs:healthcheck``av-dev:doc-healthcheck`, `av-dev-tasks:tasks`
`av-dev:task-track`, `av-dev-tasks:groom``av-dev:task-groom`,
`av-dev-code:openspec``av-dev:code-openspec`, `av-dev-code:resolve`
`av-dev:code-resolve`, `av-dev-code:review``av-dev:code-review`.
**Что сделать проекту.**
1. **Отставшим сперва прежние журналы.** Версия канона в `docs/.docs.json`
меньше 14 — пройди записи до 14 по
[changelog-before-merge.md](changelog-before-merge.md), и только потом эту.
Иначе повышение объявит приведённым то, чего никто не делал.
2. **Завести `.av-dev.toml`** в корне репозитория: `version = 1`, секция
`[docs]` с `migrations`, если ключ был, секция `[tasks]` с `dir` и теми
именами частей, которые в `.tasks.json` отличались от умолчаний. Комментарии
пиши свои — файл читает человек.
3. **Удалить `docs/.docs.json` и `<каталог задач>/.tasks.json`.** Прежние имена
не читаются: два дома для одной версии расходятся молча. Пока старые файлы на
месте, `docs.py check` и `tasks.py check` называют это прежней раскладкой.
4. **Переставить плагины.** `av-dev-docs`, `av-dev-tasks` и `av-dev-code`
удалить, `av-dev` поставить — команды в README репозитория плагинов.
5. **Поправить гейт проекта.** Пути к `docs.py`, `tasks.py` и `openspec.py`
сменились вместе с именами каталогов скиллов: `skills/canon/`
`skills/doc-canon/`, `skills/tasks/``skills/task-track/`,
`skills/openspec/``skills/code-openspec/`. Шаг, который не нашёл скрипт,
обязан краснеть, а не пропускаться, — проверь, что он краснеет.
6. **Поправить свои вызовы скиллов** — в `CLAUDE.md`, в `Taskfile`, в записях
задач: короткое имя разрешится в проектную копию, а прежнее полное не
разрешится вовсе.
7. **Найти, где проект читает служебный файл сам.** Шаг гейта, скрипт, шаблон —
что угодно, что брало значение из `docs/.docs.json`, чтобы не заводить факту
второй дом. Такое чтение переезжает на `.av-dev.toml` и на `tomllib` вместо
`json`: `python3 -c 'import tomllib; print(tomllib.load(open(".av-dev.toml","rb"))["docs"]["migrations"])'`.
Ищется командой `grep -rn "\.docs\.json\|\.tasks\.json" --exclude-dir=.git .`
— по проекту целиком, а не по документам: на первом же живом переезде это
нашлось в `Taskfile.yml`, и нашёл это гейт, а не человек.
8. **Поднять версию**`docs.py bump`. Последним шагом: число объявляет
пройденными шаги журнала, и раньше времени поднятое врёт.
9. `docs.py check` и `tasks.py check --dir <каталог задач>` — до отсутствия
дрейфа.
**Чего делать не надо.** Переписывать прошлые записи журналов под новые имена.
Они описывают состояния, которые были, и адрес, верный на день записи, остаётся
верным как свидетельство.
@@ -1,6 +1,6 @@
# Скелеты документов канона # Скелеты документов канона
Что кладут `init` и `doc-canon adopt` в незаполненный слот. Правило одно: Что кладут `init` и `canon adopt` в незаполненный слот. Правило одно:
**честная информативная строка вместо заглушки**. Проход читает строку как факт; **честная информативная строка вместо заглушки**. Проход читает строку как факт;
`<!-- заполнить: … -->` он читает как пробел, и `docs.py check` о таком `<!-- заполнить: … -->` он читает как пробел, и `docs.py check` о таком
плейсхолдере напоминает. плейсхолдере напоминает.
@@ -32,7 +32,7 @@
# Паспорт проекта # Паспорт проекта
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «в каком порядке», паспорт — устроено», [tasks/BACKLOG.md](../tasks/BACKLOG.md) — «что осталось», паспорт —
«зачем и для кого». «зачем и для кого».
## Цель ## Цель
@@ -209,7 +209,7 @@
Верно одно из трёх: Верно одно из трёх:
<!-- копия: adr-когда-заводить из av-dev/skills/doc-canon/references/canon.md --> <!-- копия: adr-когда-заводить из av-dev/skills/canon/references/canon.md -->
- **дорогой откат** — переделка стоит дороже переписывания одного файла; - **дорогой откат** — переделка стоит дороже переписывания одного файла;
- **намеренный отказ** от очевидного подхода; - **намеренный отказ** от очевидного подхода;
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус - **пересмотр прежнего решения** — тогда у старой записи обязателен статус
@@ -261,7 +261,7 @@
## Последствия ## Последствия
- `+` что стало лучше. - `+` что стало лучше.
- `` чем платим: ограничения, риски, нагрузка на поддержку. - `` чем платим: ограничения, риски, нагрузка на сопровождение.
``` ```
## `docs/review.md` ## `docs/review.md`
@@ -6,12 +6,8 @@
маркеры долга и две сверки с кодом. Смысловые дубли и оставшееся в архитектуре маркеры долга и две сверки с кодом. Смысловые дубли и оставшееся в архитектуре
поведение судит агент скрипт об этом говорит вслух в конце отчёта. поведение судит агент скрипт об этом говорит вслух в конце отчёта.
Коды выхода тот же словарь, что у tasks.py: Коды выхода общий словарь скриптов av-dev; дом словаря и разбор «дрейф
0 сошлось против окружения» av-dev/shared/axes.md. Значения в константах ниже.
1 дрейф раскладки (рабочая ситуация, чинится)
2 ошибка употребления
3 окружение: не тот каталог, битый конфиг
4 внутренний сбой
""" """
from __future__ import annotations from __future__ import annotations
@@ -131,10 +127,10 @@ NOT_DOCS = {".docs.json", ".pm.json", "tasks"}
RETIRED = { RETIRED = {
"review-brief.md": "документы канона и есть бриф; остаток — в review", "review-brief.md": "документы канона и есть бриф; остаток — в review",
"review-journal.md": "→ документ review", "review-journal.md": "→ документ review",
"plan.md": "→ tasks/ROADMAP.md (ведёт скилл task-track)", "plan.md": "→ tasks/BACKLOG.md (ведёт скилл task-track)",
"local-research.md": "→ документ research", "local-research.md": "→ документ research",
"specs": "поведение → openspec/specs/, обзор → тема architecture", "specs": "поведение → openspec/specs/, обзор → тема architecture",
"drafts": "идея → запись research, отказ → ADR, порядок → ROADMAP.md", "drafts": "идея → запись research, отказ → ADR, порядок → BACKLOG.md",
"backlog": "→ tasks/ в корне репозитория (ведёт скилл task-track)", "backlog": "→ tasks/ в корне репозитория (ведёт скилл task-track)",
} }
@@ -320,7 +316,7 @@ def check_version(root: Path, cfg: dict, rep: Report) -> None:
if got < LAYOUT_VERSION: if got < LAYOUT_VERSION:
rep.error( rep.error(
f"проект приведён к раскладке версии {got}, текущая — {LAYOUT_VERSION}:" f"проект приведён к раскладке версии {got}, текущая — {LAYOUT_VERSION}:"
f" нужно повышение (скилл av-dev:doc-canon, операция upgrade)" f" нужно повышение (скилл av-dev:canon, операция upgrade)"
) )
elif got > LAYOUT_VERSION: elif got > LAYOUT_VERSION:
rep.error( rep.error(
@@ -377,7 +373,7 @@ def check_legacy(root: Path, rep: Report) -> None:
f"нет {CONFIG}, а настройки лежат по прежней раскладке" f"нет {CONFIG}, а настройки лежат по прежней раскладке"
f" ({', '.join(legacy)}) — она осталась от трёх плагинов, которые" f" ({', '.join(legacy)}) — она осталась от трёх плагинов, которые"
f" слились в один: перенеси значения и удали старые файлы операцией" f" слились в один: перенеси значения и удали старые файлы операцией"
f" upgrade скилла av-dev:doc-canon (журнал, версия 1). Прежние имена не" f" upgrade скилла av-dev:canon (журнал, версия 1). Прежние имена не"
f" читаются, поэтому в этом прогоне всё остальное проверено так, будто" f" читаются, поэтому в этом прогоне всё остальное проверено так, будто"
f" настроек нет вовсе" f" настроек нет вовсе"
) )
@@ -706,9 +702,22 @@ def cmd_bump(args: argparse.Namespace) -> int:
if was is not None and was > LAYOUT_VERSION: if was is not None and was > LAYOUT_VERSION:
fail(ENV, f"проект на версии {was}, скрипт знает {LAYOUT_VERSION}:" fail(ENV, f"проект на версии {was}, скрипт знает {LAYOUT_VERSION}:"
f" устарел плагин, обнови маркетплейс") f" устарел плагин, обнови маркетплейс")
conf.set_version(root, LAYOUT_VERSION) # Двигается **одна** запись за раз, а не сразу до текущей: число объявляет
# пройденными шаги журнала, и прыжок через запись объявил бы пройденным то,
# чего никто не делал. Отставшему на три записи проекту `bump` зовётся три
# раза — по разу на запись, следом за её шагами.
#
# Версии нет вовсе — случай другой: проект не жил ни одной записью журнала,
# его раскладку только что вывели сегодняшним форматом (`adopt`), и
# объявлять ему нечего, кроме текущего числа.
target = LAYOUT_VERSION if was is None else was + 1
conf.set_version(root, target)
print(f"версия раскладки: {was if was is not None else 'не была объявлена'}" print(f"версия раскладки: {was if was is not None else 'не была объявлена'}"
f"{LAYOUT_VERSION} в {CONFIG}") f"{target} в {CONFIG}")
if target < LAYOUT_VERSION:
print(f" до текущей ({LAYOUT_VERSION}) осталось записей журнала:"
f" {LAYOUT_VERSION - target}. Пройди шаги следующей и позови bump"
f" снова — по разу на запись")
return OK return OK
+27 -7
View File
@@ -74,10 +74,30 @@ python3 $os check --dir <корень> # форма config.yaml в проек
python3 $os form # слепок формы против живого OpenSpec python3 $os form # слепок формы против живого OpenSpec
``` ```
**Коды выхода — общий словарь скриптов av-dev:** 0 сошлось, 1 дрейф, 2 ошибка **Копия.** Дом словаря — `shared/axes.md` в репозитории плагина: словарь общий
употребления, 3 окружение, 4 внутренний сбой. Ветвись на коде, а не на тексте. для всех скриптов, и ни один скилл им не владеет. Правится дом, а не этот файл.
Различать 1 и 3 обязательно: «форма разошлась» — рабочая ситуация, «openspec не
отвечает» — нерабочая. <!-- копия: коды-выхода из av-dev/shared/axes.md -->
**Коды выхода — общий словарь всех скриптов `av-dev`. Ветвись на коде, а не на
тексте вывода.**
| Код | Что случилось |
| --- | --- |
| 0 | сошлось |
| 1 | дрейф: рабочая ситуация, чинится |
| 2 | ошибка употребления: аргументы или нарушенное правило |
| 3 | окружение: не тот каталог, битый конфиг, нет инструмента |
| 4 | внутренний сбой — дефект скрипта, доложить |
**Различать 1 и 3 обязательно.** «Дрейф» — рабочая ситуация, и чинится она
правкой предмета; «окружение» — нерабочая, и повтор той же командой не поможет.
Одинаковая реакция на них неверна в обоих случаях.
<!-- /копия: коды-выхода -->
Здесь это значит: «форма разошлась» — рабочая ситуация, «openspec не отвечает» —
нерабочая.
`check` проверяет форму, и каждая проверка — про молчащий пробел, а не про вкус: `check` проверяет форму, и каждая проверка — про молчащий пробел, а не про вкус:
каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об этом каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об этом
@@ -118,7 +138,7 @@ python3 $os form # слепок формы против жив
## Кто зовёт этот скилл ## Кто зовёт этот скилл
- `av-dev:doc-init` — шагом заведения нового проекта, до первого документа; - `av-dev:doc-init` — шагом заведения нового проекта, до первого документа;
- `av-dev:doc-canon` в режиме `adopt` — если на переводимом проекте каталога нет - `av-dev:canon` в режиме `adopt` — если на переводимом проекте каталога нет
или `config.yaml` остался примером; или `config.yaml` остался примером;
- `av-dev:code-resolve` и `av-dev:code-review` — не вызовом по ходу, а отсылкой: - `av-dev:code-resolve` и `av-dev:code-review` — не вызовом по ходу, а отсылкой:
OpenSpec у обоих жёсткая предпосылка, и на проекте без каталога оба посылают OpenSpec у обоих жёсткая предпосылка, и на проекте без каталога оба посылают
@@ -140,7 +160,7 @@ python3 $os form # слепок формы против жив
| учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе |
| источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять |
**Свой скилл зовётся полным именем**`av-dev:doc-canon`, `av-dev:task-track`, **Свой скилл зовётся полным именем**`av-dev:canon`, `av-dev:task-track`,
`av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную
копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в
поведении. поведении.
@@ -168,7 +188,7 @@ python3 $os form # слепок формы против жив
## Чего этот скилл не делает ## Чего этот скилл не делает
- **Не пишет спеки и предложения.** Это `opsx:propose` и конвейер задачи. - **Не пишет спеки и предложения.** Это `opsx:propose` и конвейер задачи.
- **Не ведёт документы канона** — их дом скилл `av-dev:doc-canon`, и адреса в - **Не ведёт документы канона** — их дом скилл `av-dev:canon`, и адреса в
`context` только на них ссылаются. `context` только на них ссылаются.
- **Не чинит расхождение формы с версией OpenSpec в проекте.** Оно чинится в - **Не чинит расхождение формы с версией OpenSpec в проекте.** Оно чинится в
плагине: константы скрипта, образец здесь, запись в журнал версий канона. плагине: константы скрипта, образец здесь, запись в журнал версий канона.
@@ -16,12 +16,8 @@
PyYAML в стандартной библиотеке нет. Всё проверяемое различимо построчно, PyYAML в стандартной библиотеке нет. Всё проверяемое различимо построчно,
комментарии отброшены, ключи верхнего уровня стоят в первой колонке. комментарии отброшены, ключи верхнего уровня стоят в первой колонке.
Коды выхода — общий словарь скриптов av-dev: Коды выхода — общий словарь скриптов av-dev; дом словаря и разбор «дрейф
0 сошлось против окружения» — av-dev/shared/axes.md. Значения — в константах ниже.
1 дрейф: форма разошлась с ожидаемой
2 ошибка употребления: аргументы
3 окружение: не тот каталог, инструмент не отвечает
4 внутренний сбой
""" """
from __future__ import annotations from __future__ import annotations
@@ -220,7 +216,7 @@ def check_form(root: Path, rep: Report) -> None:
rep.skip( rep.skip(
f"{where} в проекте нет — ссылка на него в context не " f"{where} в проекте нет — ссылка на него в context не "
f"требуется. Документы канона проект не завёл, и без них " f"требуется. Документы канона проект не завёл, и без них "
f"конвейер работает вслепую: заводит их av-dev:doc-canon" f"конвейер работает вслепую: заводит их av-dev:canon"
) )
continue continue
if pointer not in live: if pointer not in live:
+25 -14
View File
@@ -41,12 +41,20 @@ description: "Взять одну задачу и довести её до за
делает скилл `av-dev:code-openspec`. **Сценариям разведки и обслуживания делает скилл `av-dev:code-openspec`. **Сценариям разведки и обслуживания
OpenSpec не нужен** — они не заводят change; `opsx:explore` берётся разведкой, OpenSpec не нужен** — они не заводят change; `opsx:explore` берётся разведкой,
если плагин есть. если плагин есть.
- **Проектные копии этих скиллов и агентов удаляются при установке плагина** - **Проектные копии этих скиллов и агентов удаляются при установке плагина.**
(`.claude/skills/` — голые имена `resolve`, `review`, а у проектов прошлого
поколения ещё `task-pipeline`, `review-pipeline`, `task-batch`, и с префиксом <!-- копия: проектные-копии из README.md -->
проекта: `<проект>-task-pipeline`, `<проект>-review-pipeline`;
`.claude/agents/<проект>-review-*.md`). Две копии одного скилла расходятся, и Голые имена в `.claude/skills/`: `resolve`, `review`, а у проектов прошлого
побеждает та, что короче названа. поколения ещё `task-pipeline`, `review-pipeline`, `task-batch`. С префиксом
проекта: `<проект>-task-pipeline`, `<проект>-review-pipeline`. Агенты:
`.claude/agents/<проект>-review-*.md`.
Две копии одного скилла расходятся, и побеждает та, что **короче названа**:
короткое имя разрешится в устаревшую проектную копию молча и без признаков
подмены.
<!-- /копия: проектные-копии -->
### Чего может не быть ### Чего может не быть
@@ -66,7 +74,7 @@ description: "Взять одну задачу и довести её до за
| учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе |
| источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять |
**Свой скилл зовётся полным именем**`av-dev:doc-canon`, `av-dev:task-track`, **Свой скилл зовётся полным именем**`av-dev:canon`, `av-dev:task-track`,
`av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную
копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в
поведении. поведении.
@@ -97,7 +105,7 @@ description: "Взять одну задачу и довести её до за
карта «что где» — `references/project-facts.md` конвейера ревью. карта «что где» — `references/project-facts.md` конвейера ревью.
**Документов канона нет — проект к нему не приведён.** Скажи это строкой и **Документов канона нет — проект к нему не приведён.** Скажи это строкой и
предложи скилл `av-dev:doc-canon`: одна операция на проект против поразрядной предложи скилл `av-dev:canon`: одна операция на проект против поразрядной
деградации на каждой задаче. Работу при этом не останавливай. деградации на каждой задаче. Работу при этом не останавливай.
## Вход ## Вход
@@ -108,8 +116,7 @@ description: "Взять одну задачу и довести её до за
**Запись из каталога сперва проверяется на готовность, и проверяет её машина.** **Запись из каталога сперва проверяется на готовность, и проверяет её машина.**
Вызови Skill `av-dev:task-track` и попроси прогнать `ready <слаг>`: он смотрит Вызови Skill `av-dev:task-track` и попроси прогнать `ready <слаг>`: он смотрит
тип, цель у `feature`, пустой ли раздел вопросов и собраны ли разделы схемы тип, пустой ли раздел вопросов и собраны ли разделы схемы типа. Судить это глазами нельзя — ровно тот случай, где машина дешевле и точнее,
типа. Судить это глазами нельзя — ровно тот случай, где машина дешевле и точнее,
а цена ошибки отложенная: недостающие критерии приёмки обнаружатся на приёмке, а цена ошибки отложенная: недостающие критерии приёмки обнаружатся на приёмке,
когда сверять уже не с чем. когда сверять уже не с чем.
@@ -126,6 +133,9 @@ description: "Взять одну задачу и довести её до за
## Развилка: какой сценарий ## Развилка: какой сценарий
Сценарий — ось процесса; перечень осей и их границ —
[shared/axes.md](../../shared/axes.md).
Она в два вопроса, и оба стоят до всякой работы. Она в два вопроса, и оба стоят до всякой работы.
**Первый: есть ли у задачи один очевидный способ решения?** **Первый: есть ли у задачи один очевидный способ решения?**
@@ -298,13 +308,14 @@ flowchart TD
## Границы: чем этот скилл не владеет ## Границы: чем этот скилл не владеет
- **Беклогом, целями и приоритетами.** Задача приходит извне. Скилл её не - **Беклогом и порядком работ.** Задача приходит извне. Скилл её не выбирает,
выбирает, не приоритизирует, не заводит и не переоценивает. не переставляет, не заводит и не переоценивает.
- **Форматом задач и документов.** Индексы и документы канона руками не правятся, - **Форматом задач и документов.** Индексы и документы канона руками не правятся,
путь к чужому скрипту не выдумывается: этим владеют `av-dev:task-track` и путь к чужому скрипту не выдумывается: этим владеют `av-dev:task-track` и
`av-dev:doc-sync`. Закрытие — работа этого скилла, и это осознанное решение с `av-dev:doc-sync`. Закрытие — работа этого скилла, и это осознанное решение с
названной ценой: **приёмщик и исполнитель совпали**. Закрытие поэтому **не окончательно** — человек на названной ценой: **приёмщик и исполнитель совпали**. Закрытие поэтому **не окончательно** — человек
груминге (`av-dev:task-groom`) возвращает задачу `reopen` с причиной, а доклад возвращает задачу `reopen` с причиной а доработке это делают грумингом,
`av-dev:task-groom`, на стройке — сразу, как заметили), а доклад
по критериям приёмки становится единственным, по чему приёмка вообще возможна. по критериям приёмки становится единственным, по чему приёмка вообще возможна.
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос этого - **Определением ценности.** «Нужна ли эта функциональность» — не вопрос этого
скилла ни на одном шаге и ни в одном сценарии. Чекпоинт решения спрашивает «так скилла ни на одном шаге и ни в одном сценарии. Чекпоинт решения спрашивает «так
@@ -81,10 +81,15 @@
- **чем задача становится** — `fix` или `feature`, с причиной из разреза выше; - **чем задача становится** — `fix` или `feature`, с причиной из разреза выше;
- **что уже сделано** и что из этого лежит в рабочем дереве. - **что уже сделано** и что из этого лежит в рабочем дереве.
Проверка на простой язык та же, что у чекпоинтов двух других сценариев: **в Проверка на простой язык — общая у трёх сценариев:
тексте нет `SHALL`, нет имён файлов и функций там, где вещь называется
<!-- копия: чекпоинт-простой-язык из av-dev/skills/code-resolve/references/solve.md -->
**в тексте нет `SHALL`, нет имён файлов и функций там, где вещь называется
по-русски, и нет слов, которых нет в паспорте проекта.** по-русски, и нет слов, которых нет в паспорте проекта.**
<!-- /копия: чекпоинт-простой-язык -->
**3. Дай два решения и жди ответа.** Их ровно два, и оба законны: **3. Дай два решения и жди ответа.** Их ровно два, и оба законны:
- **переформулировать запись** — тип меняется на названный, и дальше задача идёт - **переформулировать запись** — тип меняется на названный, и дальше задача идёт
@@ -172,9 +177,12 @@ flowchart TD
остаётся в рабочем дереве незакоммиченным. Доклад называет **оба**: какой тип остаётся в рабочем дереве незакоммиченным. Доклад называет **оба**: какой тип
предложен и что человек выбрал; предложен и что человек выбрал;
- **нужна разведка** — форма правки неизвестна (мажорное обновление, смена - **нужна разведка** — форма правки неизвестна (мажорное обновление, смена
сборщика, переезд гейта на другой инструмент) **или сработал триггер ADR**: сборщика, переезд гейта на другой инструмент) **или сработал триггер ADR**.
дорогой откат, намеренный отказ от очевидного подхода, пересмотр прежнего Перечень триггеров не пересказывается: он живёт в
решения. Стоп с названной причиной, разведка идёт следующим прогоном. [canon.md](../../canon/references/canon.md#adr), и здесь он работает
стоп-признаком — то есть от его точности зависит выбор сценария, а пересказ
расходится с домом молча. Стоп с названной причиной, разведка идёт следующим
прогоном.
**Последний исход — не редкость, и его стоит ждать.** Незнакомое обслуживание — **Последний исход — не редкость, и его стоит ждать.** Незнакомое обслуживание —
это выбор подхода с ценой и с тем, что становится невозможным, — предмет чекпоинта это выбор подхода с ценой и с тем, что становится невозможным, — предмет чекпоинта
@@ -223,6 +231,14 @@ ADR: список источников канон закрыл двумя — а
Код и конфиги — по конвенциям проекта. Правка по размеру задачи: чинится названное в записи, соседнее не улучшается Код и конфиги — по конвенциям проекта. Правка по размеру задачи: чинится названное в записи, соседнее не улучшается
заодно. заодно.
**Гейта ещё нет — сказать это, а не изображать сверку.** Первые шаги плана
стройки заводят гейт, сборку и хуки: у них нет ни «до», ни «прежнего», и
определение сделанного через зелёный гейт на них не выполнимо буквально. Такая
задача сделана, когда **заведённое работает на чистом клоне** и это показано в
докладе; пункты 1 и 3 определения ниже закрываются строкой «заводится впервые,
сверять не с чем». Изображать сверку с несуществующим прежним состоянием нельзя —
это ровно то враньё, против которого весь абзац ниже и написан.
**Гейт правится — сверь состав, а не цвет.** Красный, ставший зелёным, виден **Гейт правится — сверь состав, а не цвет.** Красный, ставший зелёным, виден
сразу; убыли проверок гейт не покажет — он зелёный и до, и после. Это самая сразу; убыли проверок гейт не покажет — он зелёный и до, и после. Это самая
дорогая из возможных правок оснастки: молча выключено то, чем проверяется всё дорогая из возможных правок оснастки: молча выключено то, чем проверяется всё
@@ -242,7 +258,9 @@ ADR: список источников канон закрыл двумя — а
**Поведенческая верификация здесь другая, чем в решении.** Проверяется не новое **Поведенческая верификация здесь другая, чем в решении.** Проверяется не новое
поведение, а то, что прежнее не поехало: команда из `CLAUDE.md` поднимается, шаг поведение, а то, что прежнее не поехало: команда из `CLAUDE.md` поднимается, шаг
сборки отрабатывает, хук ставится на чистом клоне. Правка, которую нельзя сборки отрабатывает, хук ставится на чистом клоне. **У оснастки, заводимой
впервые, прежнего нет** — тогда проверяется, что заведённое работает на чистом
клоне, и в докладе это называется своим именем, а не «регрессий не найдено». Правка, которую нельзя
проверить ничем, кроме «у меня локально работает», называется в докладе строкой. проверить ничем, кроме «у меня локально работает», называется в докладе строкой.
### 4. Ревью — план фиксирован сценарием ### 4. Ревью — план фиксирован сценарием
@@ -259,12 +277,16 @@ Change ты не передаёшь — его нет.
Поэтому план у сценария **свой и постоянный**, и глубину он называет сам — Поэтому план у сценария **свой и постоянный**, и глубину он называет сам —
проходы берут её из метки, а метки здесь нет: проходы берут её из метки, а метки здесь нет:
<!-- дом: план-без-метки -->
| Тема | Дом | Кто закрывает | Глубина и вход | Когда | | Тема | Дом | Кто закрывает | Глубина и вход | Когда |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| `autotests` | `CLAUDE.md`, семантика гейта | `review-autotests` | как обычно: гейт запускается целиком | всегда | | `autotests` | `CLAUDE.md`, семантика гейта | `review-autotests` | как обычно: гейт запускается целиком | всегда |
| `operations` | `architecture.*`, раздел эксплуатации | `review-basics` | **сверка**: дом темы против диффа, потолок 2 | всегда | | `operations` | `architecture.*`, раздел эксплуатации | `review-basics` | **сверка**: дом темы против диффа, потолок 2 | всегда |
| `conventions` + технический разбор | `conventions.*` | `review-code` | вход `small`: только индекс конвенций; потолки 3 технических и 2 конвенционных; **третья половина включена** — сверка с инвариантами `CLAUDE.md`, потолок 1 | дифф трогает код, а не только оснастку | | `conventions` + технический разбор | `conventions.*` | `review-code` | вход `small`: только индекс конвенций; потолки 3 технических и 2 конвенционных; **третья половина включена** — сверка с инвариантами `CLAUDE.md`, потолок 1 | дифф трогает код, а не только оснастку |
<!-- /дом: план-без-метки -->
**Глубина названа в плане потому, что иначе её неоткуда взять.** Вход и потолки **Глубина названа в плане потому, что иначе её неоткуда взять.** Вход и потолки
`review-code` заданы меткой, у `review-basics` меткой задана и сама возможность `review-code` заданы меткой, у `review-basics` меткой задана и сама возможность
запуска; на прогоне без метки оба взяли бы их наугад — то есть по-разному от запуска; на прогоне без метки оба взяли бы их наугад — то есть по-разному от
@@ -324,16 +346,16 @@ Change ты не передаёшь — его нет.
**`adr/` этот сценарий не пополняет, и это не пропуск.** У ADR закрытый список **`adr/` этот сценарий не пополняет, и это не пропуск.** У ADR закрытый список
источников — архивный `design.md` либо записка разведки, — и ни того ни другого источников — архивный `design.md` либо записка разведки, — и ни того ни другого
обслуживание не производит. Поэтому триггеры ADR здесь работают **стоп-признаком, обслуживание не производит. Поэтому триггеры ADR здесь работают **стоп-признаком,
а не поводом завести запись**: сработал дорогой откат, намеренный отказ от а не поводом завести запись**: сработал любой из них — сценарий выбран неверно,
очевидного подхода или пересмотр прежнего решения — сценарий выбран неверно, объявляй исход **нужна разведка** и останавливайся. Перечень триггеров — в
объявляй исход **нужна разведка** и останавливайся. Решение с ценой обязано [canon.md](../../canon/references/canon.md#adr) и здесь не пересказывается. Решение с ценой обязано
пройти чекпоинт вариантов, а не появиться в коммите обслуживания, чей смысл — пройти чекпоинт вариантов, а не появиться в коммите обслуживания, чей смысл —
«ничего не решали, поменяли оснастку». «ничего не решали, поменяли оснастку».
Список документов и их триггеров здесь не дублируется — он в чек-листе скилла Список документов и их триггеров здесь не дублируется — он в чек-листе скилла
`av-dev:doc-sync`; копия уже однажды разошлась с оригиналом. Документов канона в `av-dev:doc-sync`; копия уже однажды разошлась с оригиналом. Документов канона в
проекте нет — синка нет вовсе: скажи это исходом и предложи завести канон проекте нет — синка нет вовсе: скажи это исходом и предложи завести канон
скиллом `av-dev:doc-canon`. скиллом `av-dev:canon`.
### 6. Коммит ### 6. Коммит
@@ -32,7 +32,7 @@
**Отсутствие канона бьёт по разведке сильнее, чем по решению**, и сказать об этом **Отсутствие канона бьёт по разведке сильнее, чем по решению**, и сказать об этом
строкой мало: без документов у ответа нет дома, и знание осядет в переписке. строкой мало: без документов у ответа нет дома, и знание осядет в переписке.
Назови исход и предложи `av-dev:doc-canon`; работу не останавливай, но адрес Назови исход и предложи `av-dev:canon`; работу не останавливай, но адрес
ответа тогда выбираешь сам и говоришь об этом вслух. ответа тогда выбираешь сам и говоришь об этом вслух.
## Что этот сценарий требует от входа ## Что этот сценарий требует от входа
@@ -103,10 +103,10 @@ git и читается диффом, а второй стоп на каждой
ходу. Исключение ровно одно и оно не про изменение системы: одноразовый ходу. Исключение ровно одно и оно не про изменение системы: одноразовый
**читающий** прогон (запрос, замер, скрипт в песочнице), чей результат уезжает **читающий** прогон (запрос, замер, скрипт в песочнице), чей результат уезжает
в ответ с провенансом и который ничего не оставляет в репозитории. в ответ с провенансом и который ничего не оставляет в репозитории.
- **Приоритетом.** Заведённая задача встаёт в конец своей секции; где ей стоять - **Местом в списке.** Заведённая задача встаёт в конец своей секции; куда её
в очереди, решает человек на груминге (`av-dev:task-groom`). Разведка, сама поставить, решает человек на доработке грумингом (`av-dev:task-groom`), на
ставящая свой исход первым в беклоге, назначает приоритет тому, что только что стройке сразу же, по зависимости. Разведка, сама ставящая свой исход первым,
придумала. назначает место тому, что только что придумала.
- **Форматом задач и документов.** Индексы и документы руками не правятся: их - **Форматом задач и документов.** Индексы и документы руками не правятся: их
ведут `av-dev:task-track` и `av-dev:doc-sync`. Твоё — содержание ответа, их — ведут `av-dev:task-track` и `av-dev:doc-sync`. Твоё — содержание ответа, их —
форма и дом. форма и дом.
@@ -227,9 +227,14 @@ git и читается диффом, а второй стоп на каждой
отвергнутое (отвергнутый вариант с названной причиной — половина будущего ADR); отвергнутое (отвергнутый вариант с названной причиной — половина будущего ADR);
приносить один вариант и называть это выбором. приносить один вариант и называть это выбором.
Проверка на простой язык та же, что у чекпоинта решения: **в тексте нет `SHALL`, Проверка на простой язык — общая у трёх сценариев:
нет имён файлов и функций там, где вещь называется по-русски, и нет слов, которых
нет в паспорте проекта.** <!-- копия: чекпоинт-простой-язык из av-dev/skills/code-resolve/references/solve.md -->
**в тексте нет `SHALL`, нет имён файлов и функций там, где вещь называется
по-русски, и нет слов, которых нет в паспорте проекта.**
<!-- /копия: чекпоинт-простой-язык -->
Исходы чекпоинта: Исходы чекпоинта:
@@ -255,8 +260,8 @@ git и читается диффом, а второй стоп на каждой
- **ответ на вопрос** — по адресу из шага 1; - **ответ на вопрос** — по адресу из шага 1;
- **отвергнутые варианты с причинами.** Это половина будущего ADR и единственная - **отвергнутые варианты с причинами.** Это половина будущего ADR и единственная
защита от повторной разведки того же самого; защита от повторной разведки того же самого;
- **решение с ценой — в ADR**, если оно проходит триггер канона (дорогой откат, - **решение с ценой — в ADR**, если оно проходит [триггер
намеренный отказ, пересмотр прежнего решения). У разведки, кончившейся без канона](../../canon/references/canon.md#adr). У разведки, кончившейся без
кода, `design.md` не будет никогда, поэтому здесь ADR цитирует **записку кода, `design.md` не будет никогда, поэтому здесь ADR цитирует **записку
разведки**, а не архивный change; канон это допускает прямо, и в записи разведки**, а не архивный change; канон это допускает прямо, и в записи
источник называется. источник называется.
@@ -267,7 +272,7 @@ git и читается диффом, а второй стоп на каждой
перечня адресов неотличим от доклада о ненаписанном. перечня адресов неотличим от доклада о ненаписанном.
**Документов канона в проекте нет** — писать ответ некуда: назови это исходом, **Документов канона в проекте нет** — писать ответ некуда: назови это исходом,
предложи завести канон скиллом `av-dev:doc-canon` и оставь ответ в докладе предложи завести канон скиллом `av-dev:canon` и оставь ответ в докладе
целиком, чтобы работа не пропала. Заводить `docs/` мимо канона нельзя. целиком, чтобы работа не пропала. Заводить `docs/` мимо канона нельзя.
### 5. Задачи: завести и уточнить ### 5. Задачи: завести и уточнить
+12 -5
View File
@@ -195,10 +195,17 @@ flowchart TD
накопленные до этого места, и находки ревью с пометкой `развилка`; накопленные до этого места, и находки ревью с пометкой `развилка`;
- **что дальше**, если возражений нет. - **что дальше**, если возражений нет.
Проверка на «простой язык» одна и механическая: **в тексте нет `SHALL`, нет имён Проверка на «простой язык» одна и механическая, и она общая у чекпоинтов всех
файлов и функций там, где вещь называется по-русски, и нет слов, которых нет в трёх сценариев — поэтому её дом здесь, а у соседей помеченные копии:
паспорте проекта.** Не проходит — переписывай, а не объясняй, почему иначе
нельзя. <!-- дом: чекпоинт-простой-язык -->
**в тексте нет `SHALL`, нет имён файлов и функций там, где вещь называется
по-русски, и нет слов, которых нет в паспорте проекта.**
<!-- /дом: чекпоинт-простой-язык -->
Не проходит — переписывай, а не объясняй, почему иначе нельзя.
Длина — экран. Длинное объяснение не читают, а пролистывают, и чекпоинт Длина — экран. Длинное объяснение не читают, а пролистывают, и чекпоинт
превращается в ритуал одобрения. превращается в ритуал одобрения.
@@ -333,7 +340,7 @@ flowchart TD
триггера. триггера.
**Документов канона в проекте нет** — синка нет вовсе: назови это исходом и **Документов канона в проекте нет** — синка нет вовсе: назови это исходом и
предложи завести канон скиллом `av-dev:doc-canon`. Придумывать раскладку под предложи завести канон скиллом `av-dev:canon`. Придумывать раскладку под
задачу нельзя: документ, заведённый мимо канона, окажется вторым домом ровно задачу нельзя: документ, заведённый мимо канона, окажется вторым домом ровно
тому, что канон потом заведёт своим. тому, что канон потом заведёт своим.
+76 -31
View File
@@ -54,17 +54,25 @@ description: "Конвейер ревью изменения, устроенны
непроверенная ветка деградации хуже честного отказа. Заводить руками не надо: непроверенная ветка деградации хуже честного отказа. Заводить руками не надо:
этим владеет скилл `av-dev:code-openspec` — он заводит каталог и заменяет этим владеет скилл `av-dev:code-openspec` — он заводит каталог и заменяет
пример в `config.yaml` настройкой. Его же зовут `av-dev:doc-init` на новом пример в `config.yaml` настройкой. Его же зовут `av-dev:doc-init` на новом
проекте и `av-dev:doc-canon` в режиме `adopt` — на переводимом. проекте и `av-dev:canon` в режиме `adopt` — на переводимом.
**Предпосылка эта — про изменение поведения, а не про всякий прогон:** **Предпосылка эта — про изменение поведения, а не про всякий прогон:**
сценарий обслуживания зовёт конвейер без change и без дельта-спек, и ни один сценарий обслуживания зовёт конвейер без change и без дельта-спек, и ни один
проход его плана на них не завязан. См. «Прогон без change». проход его плана на них не завязан. См. «Прогон без change».
- **Документы канона** — см. следующий раздел. - **Документы канона** — см. следующий раздел.
- **Проектные копии этих скиллов и агентов удаляются при установке.** Если в - **Проектные копии этих скиллов и агентов удаляются при установке.**
проекте уже лежат свои `.claude/skills/review`,
`.claude/skills/review-pipeline`, `.claude/skills/task-pipeline`, <!-- копия: проектные-копии из README.md -->
`.claude/skills/task-batch`, `.claude/skills/resolve` или
`.claude/agents/<проект>-review-*.md` — снеси их. Иначе короткое имя разрешится Голые имена в `.claude/skills/`: `resolve`, `review`, а у проектов прошлого
в устаревшую проектную копию, молча и без признаков подмены. поколения ещё `task-pipeline`, `review-pipeline`, `task-batch`. С префиксом
проекта: `<проект>-task-pipeline`, `<проект>-review-pipeline`. Агенты:
`.claude/agents/<проект>-review-*.md`.
Две копии одного скилла расходятся, и побеждает та, что **короче названа**:
короткое имя разрешится в устаревшую проектную копию молча и без признаков
подмены.
<!-- /копия: проектные-копии -->
### Чего может не быть ### Чего может не быть
@@ -83,7 +91,7 @@ description: "Конвейер ревью изменения, устроенны
| учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе |
| источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять |
**Свой скилл зовётся полным именем** — `av-dev:doc-canon`, `av-dev:task-track`, **Свой скилл зовётся полным именем** — `av-dev:canon`, `av-dev:task-track`,
`av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную
копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в
поведении. поведении.
@@ -132,7 +140,7 @@ description: "Конвейер ревью изменения, устроенны
критерий, по которому судит изменение. `adr.*`, `research.*` и `tasks/` не критерий, по которому судит изменение. `adr.*`, `research.*` и `tasks/` не
открывает никто. открывает никто.
Дом канона этой раскладки — скилл `av-dev:doc-canon`, раздел «Три категории Дом канона этой раскладки — скилл `av-dev:canon`, раздел «Три категории
документов». Конвейер её **читатель**: категории и имена тем он берёт документов». Конвейер её **читатель**: категории и имена тем он берёт
оттуда и своих не заводит. оттуда и своих не заводит.
@@ -191,7 +199,7 @@ description: "Конвейер ревью изменения, устроенны
дом для тех же фактов разошёлся бы и выглядел актуальным. дом для тех же фактов разошёлся бы и выглядел актуальным.
**Документов канона нет вовсе** — проект не приведён к канону. Скажи это строкой **Документов канона нет вовсе** — проект не приведён к канону. Скажи это строкой
и предложи скилл `av-dev:doc-canon`: одна операция на проект против деградации на и предложи скилл `av-dev:canon`: одна операция на проект против деградации на
каждой задаче. Прогон при этом не останавливается. каждой задаче. Прогон при этом не останавливается.
## Что получает каждый проход ## Что получает каждый проход
@@ -313,6 +321,11 @@ charter'а, а модель потом двигает калибровка, и
## Метки ## Метки
**«Стадия» и «ступень» — разные членения, и путать их нельзя.** Стадий ревью
две — дизайна и кода, — и они видны снаружи: их зовёт `av-dev:code-resolve` в
разных точках цикла. Ступеней внутри прогона кода пять, они нумерованы и наружу
не выходят. Перечень осей процесса целиком — [shared/axes.md](../../shared/axes.md).
**Классификация задачи выдаёт ровно одно значение — метку**: `small`, `medium` **Классификация задачи выдаёт ровно одно значение — метку**: `small`, `medium`
или `large`. Это **единственный вход, по которому конвейер выбирает или `large`. Это **единственный вход, по которому конвейер выбирает
исполнителей**: и на дизайне, и на коде состав читается из неё, а не из класса исполнителей**: и на дизайне, и на коде состав читается из неё, а не из класса
@@ -330,6 +343,8 @@ charter'а, а модель потом двигает калибровка, и
Ревью кода: Ревью кода:
<!-- дом: тема-метка-глубина -->
| Тема | `small` | `medium` | `large` | | Тема | `small` | `medium` | `large` |
|---|---|---|---| |---|---|---|---|
| `requirements` | `specs`, сверка | `specs`, разбор | `specs`, разбор | | `requirements` | `specs`, сверка | `specs`, разбор | `specs`, разбор |
@@ -340,6 +355,8 @@ charter'а, а модель потом двигает калибровка, и
| `operations` | `code`, сверка по инвариантам | `basics`, разбор | `ops`, доказательство | | `operations` | `code`, сверка по инвариантам | `basics`, разбор | `ops`, доказательство |
| тема проекта | `basics`, сверка | `basics`, разбор | `basics`, разбор | | тема проекта | `basics`, сверка | `basics`, разбор | `basics`, разбор |
<!-- /дом: тема-метка-глубина -->
Весь процесс с выбором исполнителей на каждом этапе — одной схемой. **Метка Весь процесс с выбором исполнителей на каждом этапе — одной схемой. **Метка
считается один раз, в узле разметки, и дальше только читается:** считается один раз, в узле разметки, и дальше только читается:**
@@ -402,7 +419,7 @@ flowchart TD
Отсюда состав обеих стадий: Отсюда состав обеих стадий:
| Метка | Когда | Ревью дизайна | Ревью кода: стадии | Проходов всего | Доля задач | | Метка | Когда | Ревью дизайна | Ревью кода: ступени | Проходов всего | Доля задач |
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `small` | малое **и** знакомое: багфикс, локальная правка, доки | `specs` | 1, 2, 5 (+3 при своих темах) | **56** | **до трети, и меньше, чем `medium`** | | `small` | малое **и** знакомое: багфикс, локальная правка, доки | `specs` | 1, 2, 5 (+3 при своих темах) | **56** | **до трети, и меньше, чем `medium`** |
| `medium` | **рабочее умолчание**: среднее и знакомое | `specs`, `rubric` | 1, 2, 3, 5 | **7** | **большинство** | | `medium` | **рабочее умолчание**: среднее и знакомое | `specs`, `rubric` | 1, 2, 3, 5 | **7** | **большинство** |
@@ -447,8 +464,8 @@ flowchart TD
— и очередь между ними была бы платой ни за что. — и очередь между ними была бы платой ни за что.
Рёбер два вида, и они разной природы. Путать их нельзя: первое про Рёбер два вида, и они разной природы. Путать их нельзя: первое про
**осмысленность** (без плана задание не определено, на красном гейте проход с мнением **осмысленность** (без плана задание не определено, а на красном гейте проходу с
проход не о чем), второе про **железо**. мнением не о чем судить), второе про **железо**.
| Ребро | Смысл | Между кем | | Ребро | Смысл | Между кем |
|---|---|---| |---|---|---|
@@ -463,7 +480,7 @@ flowchart TD
```mermaid ```mermaid
flowchart TD flowchart TD
plan[/"план разметки задачи<br/>(готов до ревью кода)"/] plan[/"план разметки задачи<br/>(готов до ревью кода)"/]
autotests["autotests<br/>(стадия 1, держит машину)"] autotests["autotests<br/>(ступень 1, держит машину)"]
specs["specs"] specs["specs"]
code["code"] code["code"]
basics["basics<br/>(medium: темы ядра и свои;<br/>small, large: только свои темы проекта)"] basics["basics<br/>(medium: темы ядра и свои;<br/>small, large: только свои темы проекта)"]
@@ -513,7 +530,7 @@ flowchart TD
Ресурс один и неделимый: **машина** — тесты, поднятый сервис, СУБД, порты, диск. Ресурс один и неделимый: **машина** — тесты, поднятый сервис, СУБД, порты, диск.
Проходы, заявившие его, сериализуются между собой при любой метке и на любой Проходы, заявившие его, сериализуются между собой при любой метке и на любой
стадии; порядок внутри цепочки произволен. ступени; порядок внутри цепочки произволен.
| Проход | Держит машину | Почему | | Проход | Держит машину | Почему |
|---|---|---| |---|---|---|
@@ -653,6 +670,30 @@ flowchart TD
(тулчейн и сборка, зависимости, гит-хуки, перенос, чистка). Он приходит **без (тулчейн и сборка, зависимости, гит-хуки, перенос, чистка). Он приходит **без
change**: у работы, не меняющей поведения, дельта-спек нет по построению. change**: у работы, не меняющей поведения, дельта-спек нет по построению.
**Копия.** Дом оси — `shared/axes.md` в репозитории плагина: режим делят конвейер,
сценарий обслуживания и два устава, и ни один из них им не владеет. Правится дом,
а не этот файл.
<!-- копия: режим-прогона из av-dev/shared/axes.md -->
**Прогон ревью идёт в одном из двух режимов, и режим — не глубина.**
- **С меткой** — обычный прогон по change: разметку сделал `review-scope`, состав
обеих стадий выведен из метки.
- **Без метки** — прогон сценария обслуживания: change нет, размечать нечего,
план фиксирован и назван сценарием. Разметчик не запускается вовсе.
**Без метки — не то же самое, что `small`.** `small` — это суждение о размере и
сложности, снятое с изменения; отсутствие метки — утверждение, что снимать её
не с чего. Проход, подставивший себе `small` там, где метки нет, вывел бы
глубину из ничего.
**Режим правит не только состав, но и саму возможность запуска.** Проход, у
которого запуск задан меткой, без метки не имеет ответа на вопрос «запускаться
ли» — и ответ ему даёт план сценария, а не умолчание.
<!-- /копия: режим-прогона -->
**Метка на таком прогоне не назначается, и разметчик не зовётся.** Обе его оси **Метка на таком прогоне не назначается, и разметчик не зовётся.** Обе его оси
здесь не определены: размер он выводит из `proposal.md`, `design.md`, `tasks.md` здесь не определены: размер он выводит из `proposal.md`, `design.md`, `tasks.md`
и дельта-спек, а сложность — из формы решения, которая у обслуживания либо и дельта-спек, а сложность — из формы решения, которая у обслуживания либо
@@ -664,11 +705,15 @@ change**: у работы, не меняющей поведения, дельт
называет глубину и вход каждого прохода** — их обычный источник метка, и без неё называет глубину и вход каждого прохода** — их обычный источник метка, и без неё
проходы взяли бы их наугад: проходы взяли бы их наугад:
| Тема | Кто закрывает | Глубина и вход | Когда | <!-- копия: план-без-метки из av-dev/skills/code-resolve/references/maintain.md -->
|---|---|---|---|
| `autotests` | `review-autotests` | как обычно | всегда | | Тема | Дом | Кто закрывает | Глубина и вход | Когда |
| `operations` | `review-basics` | сверка, потолок 2 | всегда | | --- | --- | --- | --- | --- |
| `conventions` + технический разбор | `review-code` | вход `small` (индекс конвенций), потолки 3 и 2, третья половина включена — потолок 1 | дифф трогает код | | `autotests` | `CLAUDE.md`, семантика гейта | `review-autotests` | как обычно: гейт запускается целиком | всегда |
| `operations` | `architecture.*`, раздел эксплуатации | `review-basics` | **сверка**: дом темы против диффа, потолок 2 | всегда |
| `conventions` + технический разбор | `conventions.*` | `review-code` | вход `small`: только индекс конвенций; потолки 3 технических и 2 конвенционных; **третья половина включена** — сверка с инвариантами `CLAUDE.md`, потолок 1 | дифф трогает код, а не только оснастку |
<!-- /копия: план-без-метки -->
Триаж обязателен и здесь — он единственный сток и единственный, кто сверяет план Триаж обязателен и здесь — он единственный сток и единственный, кто сверяет план
с исходом; на его вход подаётся этот план вместо плана разметки. Тема с исходом; на его вход подаётся этот план вместо плана разметки. Тема
@@ -687,7 +732,7 @@ change**: у работы, не меняющей поведения, дельт
проект семантикой гейта в `CLAUDE.md`; не объявил — это строка границ покрытия, а проект семантикой гейта в `CLAUDE.md`; не объявил — это строка границ покрытия, а
не догадка прохода. не догадка прохода.
## Стадия 1 — Автотесты (обязательна при любой метке) ## Ступень 1 — Автотесты (обязательна при любой метке)
Агент `review-autotests`, тема `autotests`. Запускает команду гейта из семантики Агент `review-autotests`, тема `autotests`. Запускает команду гейта из семантики
гейта в `CLAUDE.md` и интерпретирует вывод. гейта в `CLAUDE.md` и интерпретирует вывод.
@@ -714,11 +759,11 @@ change**: у работы, не меняющей поведения, дельт
Шаги, которые красят гейт безусловно, перечислены в `CLAUDE.md` с причиной. Проходу Шаги, которые красят гейт безусловно, перечислены в `CLAUDE.md` с причиной. Проходу
запрещено списывать такой отказ в мелочь. запрещено списывать такой отказ в мелочь.
## Стадия 2 — Сверка (обязательна при любой метке) ## Ступень 2 — Сверка (обязательна при любой метке)
Два прохода, оба против **записанного** критерия. Машину не держат ни один, ребра Два прохода, оба против **записанного** критерия. Машину не держат ни один, ребра
между ними нет — уходят одним сообщением сразу после зелёного гейта, вместе со между ними нет — уходят одним сообщением сразу после зелёного гейта, вместе со
стадией 3 или 4 — той, что в метки. стадией 3 или 4 — той, которую назначила метка.
- `review-specs` закрывает тему `requirements`. Критерий взят из **дельта-спек - `review-specs` закрывает тему `requirements`. Критерий взят из **дельта-спек
предлагаемого изменения**, а не из proposal, сообщения коммита или описания предлагаемого изменения**, а не из proposal, сообщения коммита или описания
@@ -728,7 +773,7 @@ change**: у работы, не меняющей поведения, дельт
обычном входе: необработанная ветка отказа, пустое значение, граница диапазона, обычном входе: необработанная ветка отказа, пустое значение, граница диапазона,
перепутанный операнд, неосвобождённый ресурс, неверно применённый интерфейс перепутанный операнд, неосвобождённый ресурс, неверно применённый интерфейс
библиотеки. Вторая сверяет с конвенциями проекта, беря только ту их часть, библиотеки. Вторая сверяет с конвенциями проекта, беря только ту их часть,
которая **не выражается правилом**: механизируемое уже проверила стадия 1. которая **не выражается правилом**: механизируемое уже проверила ступень 1.
**На `small` у него есть третья, узкая обязанность** — сверить дифф с **На `small` у него есть третья, узкая обязанность** — сверить дифф с
записанными инвариантами `CLAUDE.md` по темам `security`, `operations` и записанными инвариантами `CLAUDE.md` по темам `security`, `operations` и
`architecture`, потому что с этой меткой `basics` не идёт. Потолок 1 находка `architecture`, потому что с этой меткой `basics` не идёт. Потолок 1 находка
@@ -756,7 +801,7 @@ change**: у работы, не меняющей поведения, дельт
недосмотренной темы. недосмотренной темы.
Recall темы `conventions` равен длине конвенций проекта — это предел любой Recall темы `conventions` равен длине конвенций проекта — это предел любой
сверки, и ровно ради него существуют стадии 3 и 4. сверки, и ровно ради него существуют ступени 3 и 4.
**Оба прохода на верхней модели, и по одной причине — цене пропуска.** У `specs` **Оба прохода на верхней модели, и по одной причине — цене пропуска.** У `specs`
это направление `code → spec`: надо заметить **отсутствие** — тихий фолбэк, это направление `code → spec`: надо заметить **отсутствие** — тихий фолбэк,
@@ -765,7 +810,7 @@ Recall темы `conventions` равен длине конвенций прое
границах покрытия; прочие проходы с мнением держат `opus` из-за цены **ложных** границах покрытия; прочие проходы с мнением держат `opus` из-за цены **ложных**
находок, эти двое — из-за цены пропущенных. находок, эти двое — из-за цены пропущенных.
## Стадия 3 — Темы (`medium` целиком; `small` и `large` — только свои темы проекта) ## Ступень 3 — Темы (`medium` целиком; `small` и `large` — только свои темы проекта)
Агент `review-basics`. Один проход, машину не держит, ничего не запускает и не Агент `review-basics`. Один проход, машину не держит, ничего не запускает и не
меряет — уходит одним сообщением вместе со стадией 2, сразу после зелёного гейта. меряет — уходит одним сообщением вместе со стадией 2, сразу после зелёного гейта.
@@ -802,7 +847,7 @@ Recall темы `conventions` равен длине конвенций прое
взгляда на ось времени — значит изменение, которое не откатывается обратной взгляда на ось времени — значит изменение, которое не откатывается обратной
правкой, на `small` не идёт вовсе, каким бы малым оно ни было. правкой, на `small` не идёт вовсе, каким бы малым оно ни было.
## Стадия 4 — Доказательство (только `large`) ## Ступень 4 — Доказательство (только `large`)
Три прохода, и все три уходят сразу после зелёного гейта, в одном ряду со Три прохода, и все три уходят сразу после зелёного гейта, в одном ряду со
стадией 2. Каждый берёт свою тему и доводит её до **доказательства**: стадией 2. Каждый берёт свою тему и доводит её до **доказательства**:
@@ -831,7 +876,7 @@ Recall темы `conventions` равен длине конвенций прое
ось времени и эксплуатации. Ровно поэтому они и стоят денег: оракул добывается ось времени и эксплуатации. Ровно поэтому они и стоят денег: оракул добывается
запуском, а запуск — это машина, цепочка и часы. запуском, а запуск — это машина, цепочка и часы.
Раньше эта пара стояла в `medium`, то есть на большинстве задач. Стадия Раньше эта пара стояла в `medium`, то есть на большинстве задач. Ступень
переехала в `large` **сознательно и по цене, а не потому, что перестала находить**: переехала в `large` **сознательно и по цене, а не потому, что перестала находить**:
она осталась самой ценной, но её ценность оплачивается на каждой задаче, а она осталась самой ценной, но её ценность оплачивается на каждой задаче, а
получается — на немногих. Что из-за этого перестало проверяться на младших метках, названо в «Честном пределе» и обязано идти строкой в границы покрытия получается — на немногих. Что из-за этого перестало проверяться на младших метках, названо в «Честном пределе» и обязано идти строкой в границы покрытия
@@ -849,7 +894,7 @@ Recall темы `conventions` равен длине конвенций прое
записки; для архитектурного — что граница домена берётся из `passport.*`, а не из записки; для архитектурного — что граница домена берётся из `passport.*`, а не из
истории решений. Обе потери названы в «Честном пределе». истории решений. Обе потери названы в «Честном пределе».
**Условие стадии и есть условие метки `large`:** изменение крупное **или** **Условие ступени и есть условие метки `large`:** изменение крупное **или**
незнакомое — любая из двух осей. Разведены они не для красоты: у архитектурного незнакомое — любая из двух осей. Разведены они не для красоты: у архитектурного
прохода работа появляется от **размера** (трогается несколько слоёв разом или в прохода работа появляется от **размера** (трогается несколько слоёв разом или в
проекте становится больше сущностей, чем было), у меряющей пары — от проекте становится больше сущностей, чем было), у меряющей пары — от
@@ -870,7 +915,7 @@ Recall темы `conventions` равен длине конвенций прое
конфигурируемость, подстраховка поверх подстраховки. Потолок — 3 находки плюс конфигурируемость, подстраховка поверх подстраховки. Потолок — 3 находки плюс
секция «дешевле переделать до мерджа». секция «дешевле переделать до мерджа».
## Стадия 5 — Triage (обязательна) ## Ступень 5 — Triage (обязательна)
Агент `review-triage`. **Единственный сток графа и единственный, кто агрегирует.** Агент `review-triage`. **Единственный сток графа и единственный, кто агрегирует.**
Входящие рёбра — все запущенные проходы: пока хоть один не вернул отчёт, триаж не Входящие рёбра — все запущенные проходы: пока хоть один не вернул отчёт, триаж не
@@ -1113,7 +1158,7 @@ flowchart TD
и где это лежит в документах проекта; таблица поразрядной деградации. и где это лежит в документах проекта; таблица поразрядной деградации.
- [references/review-levels.md](references/review-levels.md) — дом правила выбора - [references/review-levels.md](references/review-levels.md) — дом правила выбора
метки: две оси, спорное вниз, чем `small` дешевле, доли как проверка правила. метки: две оси, спорное вниз, чем `small` дешевле, доли как проверка правила.
- Skill `av-dev:doc-canon` — приведение проекта к канону документов. - Skill `av-dev:canon` — приведение проекта к канону документов.
- [references/finding-contract.md](references/finding-contract.md) — контракт находок. - [references/finding-contract.md](references/finding-contract.md) — контракт находок.
- [references/promote.md](references/promote.md) — промоут находка → конвенция → правило → удаление. - [references/promote.md](references/promote.md) — промоут находка → конвенция → правило → удаление.
- [references/calibration.md](references/calibration.md) — калибровка инъекцией, вердикты keep/retune/drop. - [references/calibration.md](references/calibration.md) — калибровка инъекцией, вердикты keep/retune/drop.
@@ -43,6 +43,8 @@
## Шкала severity ## Шкала severity
Severity — ось процесса; перечень осей — [shared/axes.md](../../../shared/axes.md).
| Severity | Что это | Пример | | Severity | Что это | Пример |
|---|---|---| |---|---|---|
| `critical` | нарушение инварианта проекта, потеря или порча данных, утечка секрета, построенный путь к отказу | запись потеряна при слиянии; тело пользовательской выгрузки в поле лога | | `critical` | нарушение инварианта проекта, потеря или порча данных, утечка секрета, построенный путь к отказу | запись потеряна при слиянии; тело пользовательской выгрузки в поле лога |
@@ -8,7 +8,7 @@
и проход читает их напрямую: пути жёсткие, посредник не нужен, а и проход читает их напрямую: пути жёсткие, посредник не нужен, а
второй дом для тех же фактов разошёлся бы и выглядел актуальным. второй дом для тех же фактов разошёлся бы и выглядел актуальным.
Определение канона держит скилл `av-dev:doc-canon`. Здесь только карта «тема → Определение канона держит скилл `av-dev:canon`. Здесь только карта «тема →
её дом → что оттуда берётся». её дом → что оттуда берётся».
## Карта тем ## Карта тем
@@ -118,7 +118,7 @@
строка неотличима от «мы просто не стали» и перестаёт читаться на третьей задаче. строка неотличима от «мы просто не стали» и перестаёт читаться на третьей задаче.
**Документов канона нет вовсе** — проект не приведён к канону. Это не повод **Документов канона нет вовсе** — проект не приведён к канону. Это не повод
работать вслепую: скажи об этом строкой и предложи `av-dev:doc-canon`. Одна работать вслепую: скажи об этом строкой и предложи `av-dev:canon`. Одна
операция на проект против деградации на каждой задаче. операция на проект против деградации на каждой задаче.
## Правило чтения ## Правило чтения
@@ -41,7 +41,7 @@
## Форма записи ## Форма записи
**Это дом формы, и у него есть копия.** Скелет `docs/review.md`, который кладёт **Это дом формы, и у него есть копия.** Скелет `docs/review.md`, который кладёт
в проект `av-dev:doc-canon`, повторяет её дословно — он уезжает в репозиторий и обязан там что-то говорить. Правка формы в проект `av-dev:canon`, повторяет её дословно — он уезжает в репозиторий и обязан там что-то говорить. Правка формы
здесь **обязана** тянуть правку скелета и запись в журнал версий канона; иначе здесь **обязана** тянуть правку скелета и запись в журнал версий канона; иначе
проекты продолжат писать по старой форме, а конвейер — ждать поля, которого нет. проекты продолжат писать по старой форме, а конвейер — ждать поля, которого нет.
Дословность сверяет `scripts/copies.py` маркетплейса по маркерам ниже — но Дословность сверяет `scripts/copies.py` маркетплейса по маркерам ниже — но
@@ -5,20 +5,26 @@
его достаточно. Здесь то, что читают, когда метку **выбирают, оспаривают или его достаточно. Здесь то, что читают, когда метку **выбирают, оспаривают или
калибруют**. калибруют**.
Применяет правило `review-scope` при разметке задачи — не автор изменения. Его Применяет правило `review-scope` при разметке задачи — не автор изменения. Сама
рабочая выжимка лежит в уставе агента; расходиться она с этим файлом не вправе, а матрица уехала в его устав **помеченной копией**, и дословность её держит
при расхождении прав этот. `copies.py`, а не обещание: прежде здесь стояло «расходиться не вправе», и
подкреплено это было ничем. Проза вокруг матрицы — отрицательный тест `small`,
доли, цена — принадлежит месту и живёт только здесь.
## Правило выбора — две оси, а не один вопрос ## Правило выбора — две оси, а не один вопрос
**Оси две, они измеряют разное, и метка есть максимум по ним.** **Оси две, они измеряют разное, и метка есть максимум по ним.**
<!-- дом: матрица-метки -->
| | **знакомое** — форму решения можно назвать до начала | **незнакомое** — форму предстоит нащупать по ходу | | | **знакомое** — форму решения можно назвать до начала | **незнакомое** — форму предстоит нащупать по ходу |
|---|---|---| |---|---|---|
| **малое** — один узел | `small` | `large` | | **малое** — один узел | `small` | `large` |
| **среднее** — несколько узлов одного слоя | `medium` | `large` | | **среднее** — несколько узлов одного слоя | `medium` | `large` |
| **крупное** — несколько слоёв, перенос ответственности, большой рефакторинг | `large` | `large` | | **крупное** — несколько слоёв, перенос ответственности, большой рефакторинг | `large` | `large` |
<!-- /дом: матрица-метки -->
**Метка — не синоним размера.** Совпадают они только в левом верхнем углу: малое **Метка — не синоним размера.** Совпадают они только в левом верхнем углу: малое
**незнакомое** изменение получает `large`, трогая один узел. Поэтому в плане **незнакомое** изменение получает `large`, трогая один узел. Поэтому в плане
стоят три строки, а не одна: размер, сложность и метка — каждая со своим стоят три строки, а не одна: размер, сложность и метка — каждая со своим
@@ -1,91 +0,0 @@
# Журнал версий раскладки
Одна запись на версию. Проект знает свою версию из ключа `version` в
`.av-dev.toml`; операция `upgrade` скилла `av-dev:doc-canon` идёт по записям
снизу вверх от версии проекта до текущей и делает то, что в них названо.
Правило записи: **что добавилось, что переехало, что удалено, что сделать
проекту**. Без последнего пункта запись бесполезна — по ней и работает
`upgrade`.
Версия — целое число. Обратной совместимости нет: есть «приведён» и «не
приведён». Версия **одна на всю раскладку** — и на документы канона, и на
каталог задач: ведёт их один плагин, и второе число означало бы только вопрос,
по какому журналу повышать.
**До слияния журналов было два**, и нумерация в них своя:
[changelog-before-merge.md](changelog-before-merge.md) — канон документов,
версии 114; [changelog-tasks-before-merge.md](changelog-tasks-before-merge.md) —
формат задач, версия 1. Оба **закрыты и не переписаны**: адрес, верный на день
записи, там и остался. Проект, отставший от канона 14, идёт сперва по ним, а
потом по этому журналу — порядок назван в записи 1.
---
## Версия 1 — 2026-08-13
Три плагина — `av-dev-docs`, `av-dev-tasks` и `av-dev-code` — слились в один,
`av-dev`. Раскол делался под раздельную установку: проект мог взять учёт работ
без документов канона или конвейер без обоих. Практика посылку не подтвердила —
подмножество не понадобилось ни разу, — а платился раскол помеченными копиями
общих правил и веткой «плагина нет» на каждый вызов соседа.
**Что переехало в проекте.** Служебных файла было два, стал один:
| Было | Стало |
| --- | --- |
| `docs/.docs.json`, ключ `canon` | `.av-dev.toml` в корне, ключ `version` |
| `docs/.docs.json`, ключ `migrations` | `.av-dev.toml`, секция `[docs]` |
| `<каталог задач>/.tasks.json`, ключ `tasks` | тот же `version`: версия теперь одна |
| `<каталог задач>/.tasks.json`, имена частей | `.av-dev.toml`, секция `[tasks]` |
Корень выбран потому, что он есть у обоих: и у проекта без `docs/`, и у проекта
без каталога задач. Формат TOML — ради комментариев: файл лежит в репозитории
проекта, и назначение числа читают из него самого, а не из документации плагина.
**Что переехало в вызовах.** Имена скиллов сменили пространство имён и получили
префикс по прежнему плагину: `av-dev-docs:canon``av-dev:doc-canon`,
`av-dev-docs:init``av-dev:doc-init`, `av-dev-docs:docs``av-dev:doc-sync`,
`av-dev-docs:healthcheck``av-dev:doc-healthcheck`, `av-dev-tasks:tasks`
`av-dev:task-track`, `av-dev-tasks:groom``av-dev:task-groom`,
`av-dev-code:openspec``av-dev:code-openspec`, `av-dev-code:resolve`
`av-dev:code-resolve`, `av-dev-code:review``av-dev:code-review`.
**Что сделать проекту.**
1. **Отставшим сперва прежние журналы.** Версия канона в `docs/.docs.json`
меньше 14 — пройди записи до 14 по
[changelog-before-merge.md](changelog-before-merge.md), и только потом эту.
Иначе повышение объявит приведённым то, чего никто не делал.
2. **Завести `.av-dev.toml`** в корне репозитория: `version = 1`, секция
`[docs]` с `migrations`, если ключ был, секция `[tasks]` с `dir` и теми
именами частей, которые в `.tasks.json` отличались от умолчаний. Комментарии
пиши свои — файл читает человек.
3. **Удалить `docs/.docs.json` и `<каталог задач>/.tasks.json`.** Прежние имена
не читаются: два дома для одной версии расходятся молча. Пока старые файлы на
месте, `docs.py check` и `tasks.py check` называют это прежней раскладкой.
4. **Переставить плагины.** `av-dev-docs`, `av-dev-tasks` и `av-dev-code`
удалить, `av-dev` поставить — команды в README репозитория плагинов.
5. **Поправить гейт проекта.** Пути к `docs.py`, `tasks.py` и `openspec.py`
сменились вместе с именами каталогов скиллов: `skills/canon/`
`skills/doc-canon/`, `skills/tasks/``skills/task-track/`,
`skills/openspec/``skills/code-openspec/`. Шаг, который не нашёл скрипт,
обязан краснеть, а не пропускаться, — проверь, что он краснеет.
6. **Поправить свои вызовы скиллов** — в `CLAUDE.md`, в `Taskfile`, в записях
задач: короткое имя разрешится в проектную копию, а прежнее полное не
разрешится вовсе.
7. **Найти, где проект читает служебный файл сам.** Шаг гейта, скрипт, шаблон —
что угодно, что брало значение из `docs/.docs.json`, чтобы не заводить факту
второй дом. Такое чтение переезжает на `.av-dev.toml` и на `tomllib` вместо
`json`: `python3 -c 'import tomllib; print(tomllib.load(open(".av-dev.toml","rb"))["docs"]["migrations"])'`.
Ищется командой `grep -rn "\.docs\.json\|\.tasks\.json" --exclude-dir=.git .`
— по проекту целиком, а не по документам: на первом же живом переезде это
нашлось в `Taskfile.yml`, и нашёл это гейт, а не человек.
8. **Поднять версию**`docs.py bump`. Последним шагом: число объявляет
пройденными шаги журнала, и раньше времени поднятое врёт.
9. `docs.py check` и `tasks.py check --dir <каталог задач>` — до отсутствия
дрейфа.
**Чего делать не надо.** Переписывать прошлые записи журналов под новые имена.
Они описывают состояния, которые были, и адрес, верный на день записи, остаётся
верным как свидетельство.
+6 -6
View File
@@ -1,6 +1,6 @@
--- ---
name: doc-healthcheck name: doc-healthcheck
description: "Проверка здоровья документации проекта судом, а не машиной: не разошлись ли документы между собой и с кодом. Зовёт двух агентов на весь канон разом — doc-consistency (один факт в двух домах, прямое противоречие, поведение в architecture.md вместо спек, ADR без парного статуса, число без провенанса) и doc-code-drift (протухший факт: имя ветки, команды, пути, зависимости поимённо, настройки с числом, единые точки, capability). Разбирает урожай порциями: строка на замену идёт в документ сразу, работа больше абзаца становится задачей. Использовать, когда с прошлой сверки сделан десяток задач, когда вернулись к проекту после перерыва, перед тем как опереться на документ в решении, а также шагом adopt и upgrade. Дорого — не на каждой задаче. Раскладку и версию раскладки проверяет скилл av-dev:doc-canon, язык документов — агент doc-wording." description: "Проверка здоровья документации проекта судом, а не машиной: не разошлись ли документы между собой и с кодом. Зовёт двух агентов на весь канон разом — doc-consistency (один факт в двух домах, прямое противоречие, поведение в architecture.md вместо спек, ADR без парного статуса, число без провенанса) и doc-code-drift (протухший факт: имя ветки, команды, пути, зависимости поимённо, настройки с числом, единые точки, capability). Разбирает урожай порциями: строка на замену идёт в документ сразу, работа больше абзаца становится задачей. Использовать, когда с прошлой сверки сделан десяток задач, когда вернулись к проекту после перерыва, перед тем как опереться на документ в решении, а также шагом adopt и upgrade. Дорого — не на каждой задаче. Раскладку и версию раскладки проверяет скилл av-dev:canon, язык документов — агент doc-wording."
--- ---
# Здоровье документации # Здоровье документации
@@ -23,7 +23,7 @@ check` и его скрипт; здесь начинается там, где к
`architecture.md` и уже живущий в `CLAUDE.md`; `architecture.md` и уже живущий в `CLAUDE.md`;
- **вернулись к проекту после перерыва** — прежде чем опираться на написанное; - **вернулись к проекту после перерыва** — прежде чем опираться на написанное;
- **перед тем как опереться на документ в решении**, если оно дорогое; - **перед тем как опереться на документ в решении**, если оно дорогое;
- шагом `adopt` и шагом `upgrade` — их зовёт скилл `doc-canon` сам. - шагом `adopt` и шагом `upgrade` — их зовёт скилл `canon` сам.
**Не на каждой задаче и не на каждом синке документации.** Цена реальная: **Не на каждой задаче и не на каждом синке документации.** Цена реальная:
`doc-consistency` идёт на `opus`, потому что сличение утверждений — суждение; `doc-consistency` идёт на `opus`, потому что сличение утверждений — суждение;
@@ -52,7 +52,7 @@ check` и его скрипт; здесь начинается там, где к
| учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе |
| источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять |
**Свой скилл зовётся полным именем** — `av-dev:doc-canon`, `av-dev:task-track`, **Свой скилл зовётся полным именем** — `av-dev:canon`, `av-dev:task-track`,
`av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную
копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в
поведении. поведении.
@@ -132,15 +132,15 @@ check` и его скрипт; здесь начинается там, где к
идёт из его собственного отчёта — перечень фактов у него закрытый, и он идёт из его собственного отчёта — перечень фактов у него закрытый, и он
называет, какие из них проверить было нечем. называет, какие из них проверить было нечем.
- Канона в проекте нет вовсе — это исход, а не пустой прогон: скажи строкой и - Канона в проекте нет вовсе — это исход, а не пустой прогон: скажи строкой и
предложи `av-dev:doc-canon`. предложи `av-dev:canon`.
## Чего этот скилл не делает ## Чего этот скилл не делает
- **Не проверяет раскладку, версию и ссылки** — это `doc-canon check`, там машина. - **Не проверяет раскладку, версию и ссылки** — это `canon check`, там машина.
- **Не судит язык** документов: залог, англицизмы, жаргон, термин без дома — это - **Не судит язык** документов: залог, англицизмы, жаргон, термин без дома — это
агент `doc-wording`, и зовут его отдельно, по пачке правленных документов. агент `doc-wording`, и зовут его отдельно, по пачке правленных документов.
Звонящие у него названные — последний шаг синка в `av-dev:doc-sync`, шаг 9 Звонящие у него названные — последний шаг синка в `av-dev:doc-sync`, шаг 9
`av-dev:doc-init` и шаг вычитки в обоих режимах `doc-canon`, — просто ни один из `av-dev:doc-init` и шаг вычитки в обоих режимах `canon`, — просто ни один из
них не здесь. У него другой ритм: он нужен там, где текст только что писали, а них не здесь. У него другой ритм: он нужен там, где текст только что писали, а
не там, где он год лежал. Оркестровать его нечем — он один и работает по не там, где он год лежал. Оркестровать его нечем — он один и работает по
названному списку. названному списку.
+20 -18
View File
@@ -1,6 +1,6 @@
--- ---
name: doc-init name: doc-init
description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром и скелет остальных документов; первые цели собирает интервью, а записывает их вызовом скилла av-dev:task-track — роадмап принадлежит учёту работ. OpenSpec заводит не сам, а вызовом скилла av-dev:code-openspec — каталог принадлежит конвейеру. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл doc-canon." description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром и скелет остальных документов; первый план работ собирает интервью, а записывает его вызовом скилла av-dev:task-track — беклог принадлежит учёту работ. OpenSpec заводит не сам, а вызовом скилла av-dev:code-openspec — каталог принадлежит конвейеру. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon."
--- ---
# Заведение нового проекта # Заведение нового проекта
@@ -8,9 +8,9 @@ description: "Завести новый проект — сессия вопро
Вход — свободный текст «что мне нужно и почему». Выход — канон документов, с Вход — свободный текст «что мне нужно и почему». Выход — канон документов, с
которого дальше работают все остальные скиллы. которого дальше работают все остальные скиллы.
**Определение канона — [канон](../doc-canon/references/canon.md).** Прочитай его до **Определение канона — [канон](../canon/references/canon.md).** Прочитай его до
первого вопроса: интервью идёт по слотам канона, а не по вкусу. Что класть в первого вопроса: интервью идёт по слотам канона, а не по вкусу. Что класть в
каждый файл — [скелеты](../doc-canon/references/skeletons.md); не выдумывай заглушки каждый файл — [скелеты](../canon/references/skeletons.md); не выдумывай заглушки
своей формы, `docs.py` узнаёт только плейсхолдер оттуда. своей формы, `docs.py` узнаёт только плейсхолдер оттуда.
## Что `init` физически не может произвести ## Что `init` физически не может произвести
@@ -32,10 +32,10 @@ description: "Завести новый проект — сессия вопро
Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет, Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет,
заводится первой задачей». Проход читает её как факт. заводится первой задачей». Проход читает её как факт.
**`tasks/ROADMAP.md` в таблице нет намеренно.** Первые цели `init` собирает **`tasks/BACKLOG.md` в таблице нет намеренно.** Первый план работ `init`
интервью (блок 6), но записывает их не он: каталогом задач и формой целей владеет собирает интервью (блок 6), но записывает его не он: каталогом задач владеет
`av-dev:task-track`, и это шаг 7. Человек от каталога задач отказался — цели `av-dev:task-track`, и это шаг 7. Человек от каталога задач отказался — план
остаются списком в докладе, роадмапа в проекте не появляется, и это говорится остаётся списком в докладе, беклога в проекте не появляется, и это говорится
строкой. строкой.
## Порядок интервью — зависимость, а не удобство ## Порядок интервью — зависимость, а не удобство
@@ -54,9 +54,11 @@ description: "Завести новый проект — сессия вопро
проекте нельзя откатить — деплой, выкладка наружу, перезапись данных. проекте нельзя откатить — деплой, выкладка наружу, перезапись данных.
5. **Чем краснеет гейт.** Какие проверки обязательны; что красит безусловно; 5. **Чем краснеет гейт.** Какие проверки обязательны; что красит безусловно;
чего в гейте намеренно не будет и кто тогда это гоняет. чего в гейте намеренно не будет и кто тогда это гоняет.
6. **Первые цели.** Возможности приложения, а не задачи: три-пять целей в 6. **Первые шаги стройки.** Новый проект по определению начинается со стадии
`Запланировано`, каждая — ответ на «что приложение будет уметь», с `build`: приложения ещё нет. Собери **список от базы к деталям** — что нужно
обоснованием очереди прозой. сделать, чтобы приложение заработало, — и обоснуй **порядок**: он значит
зависимость, а не важность. Пять-десять шагов достаточно: план дописывается
по ходу стройки, и это законно.
### Как вести ### Как вести
@@ -90,7 +92,7 @@ description: "Завести новый проект — сессия вопро
| учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе |
| источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять |
**Свой скилл зовётся полным именем** — `av-dev:doc-canon`, `av-dev:task-track`, **Свой скилл зовётся полным именем** — `av-dev:canon`, `av-dev:task-track`,
`av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную
копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в
поведении. поведении.
@@ -133,12 +135,12 @@ description: "Завести новый проект — сессия вопро
5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и 5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и
отдельным файлом не остаётся: два дома для одного замысла разойдутся на отдельным файлом не остаётся: два дома для одного замысла разойдутся на
первом же уточнении. первом же уточнении.
6. Заведи скелет остальных по [скелетам](../doc-canon/references/skeletons.md) — 6. Заведи скелет остальных по [скелетам](../canon/references/skeletons.md) —
каждый с честной строкой. каждый с честной строкой.
7. Каталог задач и первые цели — **вызови скилл `av-dev:task-track`**: он владеет 7. Каталог задач и первые шаги — **вызови скилл `av-dev:task-track`**: он владеет
форматом целей и задач. Не разрешился — учёт задач остаётся владельцу, и это форматом задач и стадией (`init --stage build`). Не разрешился — учёт задач
тоже строка доклада. остаётся владельцу, и это тоже строка доклада.
8. `docs.py check` из скилла `doc-canon` — до отсутствия дрейфа. Замечания о 8. `docs.py check` из скилла `canon` — до отсутствия дрейфа. Замечания о
незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа. незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа.
9. **Вычитай написанное — агент `doc-wording`**, по пачке заполненных документов 9. **Вычитай написанное — агент `doc-wording`**, по пачке заполненных документов
(`passport.md`, `CLAUDE.md`, `security.md`). Здесь он нужен сильнее, чем где (`passport.md`, `CLAUDE.md`, `security.md`). Здесь он нужен сильнее, чем где
@@ -152,7 +154,7 @@ description: "Завести новый проект — сессия вопро
## Что дальше ## Что дальше
- Содержимое канона по ходу разработки ведёт скилл `doc-sync`. - Содержимое канона по ходу разработки ведёт скилл `doc-sync`.
- Раскладку проверяет `doc-canon check`. - Раскладку проверяет `canon check`.
- Первую задачу берёт конвейер проекта; `architecture.md` и `conventions/` - Первую задачу берёт конвейер проекта; `architecture.md` и `conventions/`
наполняются его шагом синка, а не заранее. наполняются его шагом синка, а не заранее.
@@ -160,6 +162,6 @@ description: "Завести новый проект — сессия вопро
- **Не проектирует систему.** Архитектура выводится из кода, а не наоборот. - **Не проектирует систему.** Архитектура выводится из кода, а не наоборот.
- **Не пишет код** и не заводит сборку. - **Не пишет код** и не заводит сборку.
- **Не переводит существующий проект** — это `doc-canon adopt`. Признак: в - **Не переводит существующий проект** — это `canon adopt`. Признак: в
репозитории уже есть документация или беклог в какой-то раскладке. репозитории уже есть документация или беклог в какой-то раскладке.
- **Не решает за человека**, что важно: цель, границы и периметр — его ответы. - **Не решает за человека**, что важно: цель, границы и периметр — его ответы.
+13 -13
View File
@@ -1,12 +1,12 @@
--- ---
name: doc-sync name: doc-sync
description: Вести содержимое документов канона по ходу разработки — синк после сделанной задачи с построчным отчётом по каждому документу, заведение ADR промоутом из архивного design.md или из записки разведки, запись наблюдения в research, запись дефекта и настройки конвейера в review.md, чистка architecture.md от поведения с маркерами долга. Использовать, когда задача сделана и надо обновить документацию, когда просят завести ADR или записать решение, занести находку о внешних данных, записать проскочивший дефект, разгрузить разросшуюся архитектуру. Раскладку и соответствие канону проверяет скилл av-dev:doc-canon. description: Вести содержимое документов канона по ходу разработки — синк после сделанной задачи с построчным отчётом по каждому документу, заведение ADR промоутом из архивного design.md или из записки разведки, запись наблюдения в research, запись дефекта и настройки конвейера в review.md, чистка architecture.md от поведения с маркерами долга. Использовать, когда задача сделана и надо обновить документацию, когда просят завести ADR или записать решение, занести находку о внешних данных, записать проскочивший дефект, разгрузить разросшуюся архитектуру. Раскладку и соответствие канону проверяет скилл av-dev:canon.
--- ---
# Ведение содержимого канона # Ведение содержимого канона
Скилл владеет **содержимым** документов канона; раскладкой владеет `doc-canon`. Скилл владеет **содержимым** документов канона; раскладкой владеет `canon`.
Определение канона и роли документов — [канон](../doc-canon/references/canon.md), Определение канона и роли документов — [канон](../canon/references/canon.md),
здесь не пересказывается. здесь не пересказывается.
Главный вызывающий — **шаг синка документации в конвейере задачи**: скилл Главный вызывающий — **шаг синка документации в конвейере задачи**: скилл
@@ -36,7 +36,7 @@ description: Вести содержимое документов канона
| `openspec/specs/` | всегда при изменении поведения | вливает `opsx:archive` | | `openspec/specs/` | всегда при изменении поведения | вливает `opsx:archive` |
| `database.md` | тронуты миграции | `docs.py check --base` | | `database.md` | тронуты миграции | `docs.py check --base` |
| `architecture.md` | новый компонент, граница, внешняя зависимость, изменилось окружение | `docs.py`: capability без упоминания | | `architecture.md` | новый компонент, граница, внешняя зависимость, изменилось окружение | `docs.py`: capability без упоминания |
| `adr/` | дорогой откат, намеренный отказ, пересмотр прежнего | нет — только этот чек-лист | | `adr/` | сработал триггер канона (перечень — [canon.md](../canon/references/canon.md#adr)) | нет — только этот чек-лист |
| `research/` | узнали новое о внешнем формате или данных | нет | | `research/` | узнали новое о внешнем формате или данных | нет |
| `security.md` | новый недоверенный вход, токен, путь наружу, сдвиг периметра | нет | | `security.md` | новый недоверенный вход, токен, путь наружу, сдвиг периметра | нет |
| `conventions/` | находка принята и не специфична для одного места | промоут | | `conventions/` | находка принята и не специфична для одного места | промоут |
@@ -106,11 +106,11 @@ description: Вести содержимое документов канона
`av-dev:code-resolve`, сценарий разведки: решение, принятое разведкой (намеренный отказ, выбор `av-dev:code-resolve`, сценарий разведки: решение, принятое разведкой (намеренный отказ, выбор
подхода, «проверили и не делаем»), `design.md` не имеет по построению — change по подхода, «проверили и не делаем»), `design.md` не имеет по построению — change по
нему не будет никогда. Промоут при этом тот же: цитата и ссылка, но на записку. нему не будет никогда. Промоут при этом тот же: цитата и ссылка, но на записку.
Перечень источников закрыт и живёт в [каноне](../doc-canon/references/canon.md), Перечень источников закрыт и живёт в [каноне](../canon/references/canon.md),
раздел `adr/`. раздел `adr/`.
**Триггер заведения, форма имени и правило замены — в **Триггер заведения, форма имени и правило замены — в
[каноне](../doc-canon/references/canon.md), раздел `adr/`.** Здесь они не [каноне](../canon/references/canon.md), раздел `adr/`.** Здесь они не
повторяются: копия правила расходится с оригиналом на первой же смене версии повторяются: копия правила расходится с оригиналом на первой же смене версии
канона, а расходится незаметно. канона, а расходится незаметно.
@@ -126,7 +126,7 @@ description: Вести содержимое документов канона
Обзор не держит поведение — его нормативный дом `openspec/specs/`. **Форма Обзор не держит поведение — его нормативный дом `openspec/specs/`. **Форма
маркера долга и правило «гейт от них не краснеет» — в маркера долга и правило «гейт от них не краснеет» — в
[каноне](../doc-canon/references/canon.md), раздел `architecture.md`.** [каноне](../canon/references/canon.md), раздел `architecture.md`.**
Разбирается порциями: раздел вычищает та задача, которая его касается. Разбирается порциями: раздел вычищает та задача, которая его касается.
Содержимое не выбрасывается, а переезжает — требования в дельта-спеку change, Содержимое не выбрасывается, а переезжает — требования в дельта-спеку change,
@@ -136,7 +136,7 @@ description: Вести содержимое документов канона
Наблюдение о внешнем мире: что реально шлёт источник, чем документация формата Наблюдение о внешнем мире: что реально шлёт источник, чем документация формата
расходится с практикой. **Требование провенанса и правило про расходящееся расходится с практикой. **Требование провенанса и правило про расходящееся
число — в [каноне](../doc-canon/references/canon.md), раздел `research/`.** число — в [каноне](../canon/references/canon.md), раздел `research/`.**
Твоя часть — заметить, что по ходу задачи узналось новое о внешних данных, и не Твоя часть — заметить, что по ходу задачи узналось новое о внешних данных, и не
дать этому остаться в контексте. Признак: ты правил разбор, опираясь на то, чего дать этому остаться в контексте. Признак: ты правил разбор, опираясь на то, чего
@@ -163,7 +163,7 @@ description: Вести содержимое документов канона
| учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе |
| источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять |
**Свой скилл зовётся полным именем** — `av-dev:doc-canon`, `av-dev:task-track`, **Свой скилл зовётся полным именем** — `av-dev:canon`, `av-dev:task-track`,
`av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную
копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в
поведении. поведении.
@@ -191,7 +191,7 @@ description: Вести содержимое документов канона
Файл держит два раздела с разными сроками жизни — журнал дефектов и настройку Файл держит два раздела с разными сроками жизни — журнал дефектов и настройку
конвейера. **Что в каком и в какой форме — в конвейера. **Что в каком и в какой форме — в
[каноне](../doc-canon/references/canon.md), раздел `review.md`**; подробности формы [каноне](../canon/references/canon.md), раздел `review.md`**; подробности формы
записи и выбор адреса, куда она ведёт, — у конвейера ревью: `Skill записи и выбор адреса, куда она ведёт, — у конвейера ревью: `Skill
av-dev:code-review`, его `references/review-journal.md`. av-dev:code-review`, его `references/review-journal.md`.
@@ -205,7 +205,7 @@ av-dev:code-review`, его `references/review-journal.md`.
Находка → конвенция → правило линтера → **удаление из прозы**. Процедура целиком Находка → конвенция → правило линтера → **удаление из прозы**. Процедура целиком
принадлежит конвейеру ревью — его `references/promote.md`, читается через принадлежит конвейеру ревью — его `references/promote.md`, читается через
`Skill av-dev:code-review`; роль каталога конвенций — в `Skill av-dev:code-review`; роль каталога конвенций — в
[каноне](../doc-canon/references/canon.md). **Прогон идёт вне конвейера** [каноне](../canon/references/canon.md). **Прогон идёт вне конвейера**
(находку принесли руками) — три шага всё равно твои, просто без его процедуры: (находку принесли руками) — три шага всё равно твои, просто без его процедуры:
сформулируй правило, сформулируй правило,
поищи, чем оно механизируется, и вычеркни прозу, если механизировалось. поищи, чем оно механизируется, и вычеркни прозу, если механизировалось.
@@ -217,8 +217,8 @@ av-dev:code-review`, его `references/review-journal.md`.
## Чего этот скилл не делает ## Чего этот скилл не делает
- **Не проверяет раскладку** — это `doc-canon`. - **Не проверяет раскладку** — это `canon`.
- **Не заводит недостающие документы** — их скелет кладёт `doc-canon adopt` или - **Не заводит недостающие документы** — их скелет кладёт `canon adopt` или
`doc-init`. `doc-init`.
- **Не сочиняет содержание.** Нечего записать — так и пишется, честной строкой. - **Не сочиняет содержание.** Нечего записать — так и пишется, честной строкой.
- **Не переоформляет документы «заодно»**: правится то, чего коснулась работа. - **Не переоформляет документы «заодно»**: правится то, чего коснулась работа.
+46 -14
View File
@@ -27,7 +27,7 @@ description: "Груминг беклога — интерактивный ра
## Три правила, из которых всё следует ## Три правила, из которых всё следует
1. **Порядок назначает человек, машина его не выводит.** Ни давность, ни тип, ни 1. **Порядок назначает человек, машина его не выводит.** Ни давность, ни тип, ни
число задач под целью приоритетом не являются. Единственное место в очереди, размер секции приоритетом не являются. Единственное место в очереди,
назначенное не человеком, — конец секции у сырья, и оно из очереди изъято назначенное не человеком, — конец секции у сырья, и оно из очереди изъято
(`task-track`, правило 4). (`task-track`, правило 4).
2. **Порция важнее охвата.** Тридцать задач за заход — это усталость и 2. **Порция важнее охвата.** Тридцать задач за заход — это усталость и
@@ -37,6 +37,36 @@ description: "Груминг беклога — интерактивный ра
`--reason` у закрытия и переноса, ответ в теле задачи, строка в докладе. `--reason` у закрытия и переноса, ответ в теле задачи, строка в докладе.
Решение, оставшееся в переписке, будет принято заново через месяц. Решение, оставшееся в переписке, будет принято заново через месяц.
## Груминг — операция доработки
**Стадия проекта решает, применим ли груминг вообще** (дом стадии —
[`task-track`, «Две стадии»](../task-track/SKILL.md#две-стадии); посмотреть —
`tasks.py stage`).
На **доработке** он и есть основная гигиена: беклог пополняется извне и
вразнобой, порядок значит важность, и назначить её может только человек.
На **стройке** оба вопроса скилла отвечены заранее. «Что сейчас самое важное» —
первая строка плана, и назначил её не приоритет, а зависимость: переставить её
значит сломать стройку. «Что перестало быть важным» возникает не порциями, а
разом — когда меняется замысел, — и тогда пересматривается **план целиком**, а
не 5–8 задач из середины. Порционный разбор здесь вреден: он вынимает шаги из
списка, порядок которого и есть его содержание.
Поэтому на стройке скилл говорит это строкой и **отсылает к другой работе**:
[пересмотр плана целиком](../task-track/SKILL.md#пересмотр-плана-стройки) —
сценарий скилла `task-track`, гигиена полей — тоже его, а исчерпанный беклог
значит переход (`tasks.py stage support`). Четыре вещи он делает и на стройке,
потому что от стадии они не зависят: `tasks.py check --fix`, разбор
накопившихся вопросов, закрытие сделанного попутно и **возврат неудавшейся
приёмки** (`reopen`).
**Возврат приёмки от стадии не зависит вовсе, и это надо сказать отдельно.**
Приёмщик и исполнитель у нас совпадают, и опор против этого две: независимый
отчёт ревью и `reopen`. Вторая привязана к грумингу только по привычке — заметил,
что закрытая задача сделана не тем, чем обещала, возвращай сразу, на любой
стадии и в любой момент.
## Когда груминг созрел ## Когда груминг созрел
**Зовёт человек.** Скилл сам себя не назначает, но обязан **напоминать**, и **Зовёт человек.** Скилл сам себя не назначает, но обязан **напоминать**, и
@@ -47,6 +77,8 @@ description: "Груминг беклога — интерактивный ра
- на верхних строках очереди есть задача с открытым вопросом — очередь - на верхних строках очереди есть задача с открытым вопросом — очередь
показывает то, что взять нельзя; показывает то, что взять нельзя;
- `tasks.py check` печатает «готово к взятию: 0 из N» — брать сегодня нечего. - `tasks.py check` печатает «готово к взятию: 0 из N» — брать сегодня нечего.
**На стройке этот признак читается иначе**: он значит «план ещё не дописан», а
не «пора грумить».
Порога в неделях нет намеренно: счётчик простоя пришлось бы вести руками, а Порога в неделях нет намеренно: счётчик простоя пришлось бы вести руками, а
решает всё равно человек. Признак — **очередь перестала быть твоей**: взялся решает всё равно человек. Признак — **очередь перестала быть твоей**: взялся
@@ -122,7 +154,7 @@ flowchart TD
**3. Что перестало быть важным.** Порциями по 5–8. Сперва то, что решается **3. Что перестало быть важным.** Порциями по 5–8. Сперва то, что решается
фактом и не требует ничьего суждения (сделано попутно, отменено решением, фактом и не требует ничьего суждения (сделано попутно, отменено решением,
дублируется, симптомы одного дефекта), затем то, что решает человек (жива ли, дублируется, симптомы одного дефекта), затем то, что решает человек (жива ли,
та ли цель, задача ли это ещё). задача ли это ещё).
**4. Что важно сейчас.** Расстановка порядка — `move --after <слаг>` и **4. Что важно сейчас.** Расстановка порядка — `move --after <слаг>` и
`move --first`. Разбирается **не весь беклог, а верх очереди**: первые три-пять `move --first`. Разбирается **не весь беклог, а верх очереди**: первые три-пять
@@ -146,15 +178,15 @@ flowchart TD
кодом стоит меньше, чем та же работа через квартал; кодом стоит меньше, чем та же работа через квартал;
- **что дорожает от ожидания** — данные копятся, миграция усложняется, внешний - **что дорожает от ожидания** — данные копятся, миграция усложняется, внешний
срок приближается; срок приближается;
- **цель, которую человек назвал следующей.** - **то, что человек назвал следующим.**
Довод **записывается причиной** (`move --after <слаг> --reason …`). Порядок без Довод **записывается причиной** (`move --after <слаг> --reason …`). Порядок без
причины — это порядок, который на следующем груминге назначат заново с нуля. причины — это порядок, который на следующем груминге назначат заново с нуля.
**Цель и приоритет — независимые оси.** Очередь может идти поперёк целей, и это **Тема и приоритет — независимые оси.** Очередь может идти поперёк полок, и это
законно: задачи одной цели не обязаны стоять подряд. Но если под целью годами законно: задачи одной темы не обязаны стоять подряд. Но если из одной полки
ничего не поднимается наверх — это разговор про цель, а не про очередь, и он годами ничего не поднимается наверх — это разговор про саму работу, а не про
идёт на шаге 3. очередь, и он идёт на шаге 3.
## Документы устаревают тем же ходом работы ## Документы устаревают тем же ходом работы
@@ -193,9 +225,9 @@ flowchart TD
- **независимый отчёт ревью** — артефакт, написанный не исполнителем: отчёт - **независимый отчёт ревью** — артефакт, написанный не исполнителем: отчёт
триажа в триажа в
`openspec/changes/archive/<id>/review/` (до архивации — `changes/<id>/review/`); `openspec/changes/archive/<id>/review/` (до архивации — `changes/<id>/review/`);
- **`reopen <слаг> --reason`** — закрытие не окончательно. Заметил на груминге, - **`reopen <слаг> --reason`** — закрытие не окончательно. Заметил, что закрытая
что закрытая задача сделана не тем, чем обещала, — возвращай, это штатная задача сделана не тем, чем обещала, — возвращай, это штатная операция, а не
операция, а не скандал. Индексы под git: `git log -p` по беклогу показывает, скандал, и ждать груминга она не требует (на стройке его и не будет). Индексы под git: `git log -p` по беклогу показывает,
что и когда закрыто, потому что закрытие коммитится отдельным коммитом учёта. что и когда закрыто, потому что закрытие коммитится отдельным коммитом учёта.
Известные обходы: Известные обходы:
@@ -231,11 +263,11 @@ flowchart TD
- Что просмотрено: N из M, сколько порций, по какому признаку отобраны. - Что просмотрено: N из M, сколько порций, по какому признаку отобраны.
- Вопросы: разобрано N, из них отвечено без человека N, снято тегов N. - Вопросы: разобрано N, из них отвечено без человека N, снято тегов N.
- **Что перестало быть важным**: удалено как реализованное (со ссылками), ушло - **Что перестало быть важным**: удалено как реализованное (со ссылками), ушло
без реализации (с причинами), понижено до сырья, слито, сменило тип или цель. без реализации (с причинами), понижено до сырья, слито, сменило тип.
- **Что важно сейчас**: верх очереди по каждой секции — слаги в порядке, и по - **Что важно сейчас**: верх очереди по каждой секции — слаги в порядке, и по
каждому движению довод одной строкой. каждому движению довод одной строкой.
- **Границы покрытия**: сколько задач не трогали и какие именно секции, теги или - **Границы покрытия**: сколько задач не трогали и какие именно секции или теги
цели остались — иначе доклад читается как «беклог разобран». остались — иначе доклад читается как «беклог разобран».
- `tasks.py check` после правок — результат строкой. - `tasks.py check` после правок — результат строкой.
## Чего этот скилл не делает ## Чего этот скилл не делает
@@ -244,4 +276,4 @@ flowchart TD
себе — формат и содержимое ведёт `task-track` (груминг зовёт его операции). Не решает себе — формат и содержимое ведёт `task-track` (груминг зовёт его операции). Не решает
за человека, что важно: он готовит развилки и рекомендует. Не принимает за человека, что важно: он готовит развилки и рекомендует. Не принимает
закрытые задачи отдельным ритуалом — `reopen` есть, момента у него нет. Не судит закрытые задачи отдельным ритуалом — `reopen` есть, момента у него нет. Не судит
документы проекта — это скиллы `av-dev:doc-canon` и `av-dev:doc-healthcheck`. документы проекта — это скиллы `av-dev:canon` и `av-dev:doc-healthcheck`.
+12 -19
View File
@@ -41,8 +41,8 @@
проверки на нужность. Свежесть меряется git'ом, как и залежалость, — датой проверки на нужность. Свежесть меряется git'ом, как и залежалость, — датой
появления файла в истории; появления файла в истории;
2. дальше **по залежалости**`list --stale`; 2. дальше **по залежалости**`list --stale`;
3. по потребности — одна секция целиком, один тег (партия ревью), одна цель 3. по потребности — одна секция целиком, один тег (партия ревью), список от
(`--goal`), список от человека. человека.
- **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось». - **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось».
Между порциями — промежуточный доклад. Между порциями — промежуточный доклад.
@@ -84,20 +84,14 @@
6. **Жива ли она вообще.** Контекст мог измениться: ушла зависимость, отпал 6. **Жива ли она вообще.** Контекст мог измениться: ушла зависимость, отпал
сценарий, обошли иначе. Здесь и звучит вопрос, выкидывать ли. сценарий, обошли иначе. Здесь и звучит вопрос, выкидывать ли.
7. **Та ли цель — и нужна ли она вообще.** `feature`, которой не находится цель, 7. **Тот ли тип.** Заводилась починкой, а после разбора оказалось, что
— кандидат на выход: новая возможность вне цели это возможность, которой никто поведение никогда и не было заявлено, — это `feature`. Тип, оставшийся от
не заказывал. Операционной задаче (`fix`, `chore`, `research`) цель не нужна, прошлой формулировки, врёт ровно там, где по нему отбирают, и требует не тех
и выдумывать её здесь не надо. разделов.
**Отменяется и сама цель** — когда замысел оказался неверен, а не когда
задача выбрала не ту. Тогда порция расширяется до всех задач этой цели: каждую
либо закрыть своей причиной, либо перевесить на другую цель, и только потом
закрыть цель. Порядок и почему он такой —
[task-goal.md](../../task-track/references/task-goal.md#отменённая-цель--сперва-задачи-потом-цель).
8. **Задача ли это по-прежнему.** Не проходит `ready` по существу, а не по 8. **Задача ли это по-прежнему.** Не проходит `ready` по существу, а не по
недописанным разделам → `edit <slug> --type research` и опустошённый раздел недописанным разделам → `edit <slug> --type research` и опустошённый раздел
«Вопрос», то есть сырьё; дальше штурм. Разрослась → это несколько задач под «Вопрос», то есть сырьё; дальше штурм. Разрослась → это несколько задач,
той же целью, дальше декомпозиция. дальше декомпозиция.
9. **Не подешевела ли она.** Сделанная с прошлого раза работа меняет цену 9. **Не подешевела ли она.** Сделанная с прошлого раза работа меняет цену
**других** задач: рядом с только что тронутым кодом та же работа стоит меньше. **других** задач: рядом с только что тронутым кодом та же работа стоит меньше.
Это довод и на шаге 4 — задача, внезапно подешевевшая, поднимается в очереди Это довод и на шаге 4 — задача, внезапно подешевевшая, поднимается в очереди
@@ -111,7 +105,7 @@
нигде не хранится. нигде не хранится.
Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений, Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений,
**либо двигается (меняет цель, поднимается в очереди, уходит с причиной), либо **либо двигается (меняет полку, поднимается в очереди, уходит с причиной), либо
остаётся с явно записанной причиной**, почему её держим (`move <slug> --reason остаётся с явно записанной причиной**, почему её держим (`move <slug> --reason
` — без `--section` секция берётся текущая). Молчаливое «оставить как есть» на ` — без `--section` секция берётся текущая). Молчаливое «оставить как есть» на
давно неподвижной задаче — это решение не принимать решение; запись причины давно неподвижной задаче — это решение не принимать решение; запись причины
@@ -123,9 +117,8 @@
Разбирается **верх очереди**, а не весь беклог: первые три-пять строк каждой Разбирается **верх очереди**, а не весь беклог: первые три-пять строк каждой
секции. Ниже пятой строки порядок всё равно перестаёт что-либо значить. секции. Ниже пятой строки порядок всё равно перестаёт что-либо значить.
1. **Покажи текущий верх**`list --index backlog`, по секциям, в том порядке, 1. **Покажи текущий верх**`list`, по секциям, в том порядке, в каком строки
в каком строки лежат. Плюс состояние проекта из роадмапа: секция `Готово` лежат.
отвечает на «где мы», `Запланировано` — на «куда шли».
2. **Спрашивай сравнением, а не оценкой.** «Что из этих двух делают раньше» 2. **Спрашивай сравнением, а не оценкой.** «Что из этих двух делают раньше»
имеет проверяемый ответ, «насколько важна эта задача» — нет. Веди попарно и имеет проверяемый ответ, «насколько важна эта задача» — нет. Веди попарно и
сверху: что первое, что после него. сверху: что первое, что после него.
@@ -133,7 +126,7 @@
или `move <slug> --first --reason …`. Довод берётся из перечня в или `move <slug> --first --reason …`. Довод берётся из перечня в
[SKILL.md](../SKILL.md#приоритет-как-его-расставляют): сломано сейчас, [SKILL.md](../SKILL.md#приоритет-как-его-расставляют): сломано сейчас,
разблокирует остальное, дешевеет от сделанного, дорожает от ожидания, разблокирует остальное, дешевеет от сделанного, дорожает от ожидания,
названная цель. названо человеком.
4. **Проверь верх на готовность**`tasks.py ready <слаг> …` по первым строкам. 4. **Проверь верх на готовность**`tasks.py ready <слаг> …` по первым строкам.
Задача, стоящая первой и не проходящая `ready`, — это очередь, которая врёт: Задача, стоящая первой и не проходящая `ready`, — это очередь, которая врёт:
взять её нельзя. Либо дописывается здесь же, либо уступает место. взять её нельзя. Либо дописывается здесь же, либо уступает место.
+254 -258
View File
@@ -1,13 +1,13 @@
--- ---
name: task-track name: task-track
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Каталог отстал от версии раскладки — это скажет tasks.py check, а повышает проект скилл av-dev:doc-canon по общему журналу версий. Расстановка приоритетов и разбор накопившегося — скилл av-dev:task-groom. Не реализует задачи — этим занимается скилл решения задачи. description: Ведение задач как каталога markdown-файлов (одна задача = один файл в items/ + строка в BACKLOG.md). У каждой задачи есть тип (feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. У проекта есть стадия (build — беклог это план стройки, порядок строк значит зависимость; support — очередь правок, порядок значит важность). Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей, смена стадии и проверка согласованности индекса. Использовать, когда просят добавить задачу или идею, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат, объявить стадию или проверить беклог. Каталог отстал от версии раскладки — это скажет tasks.py check, а повышает проект скилл av-dev:canon по общему журналу версий. Расстановка приоритетов и разбор накопившегося — скилл av-dev:task-groom. Не реализует задачи — этим занимается скилл решения задачи.
--- ---
# Задачи # Задачи
Задачи — каталог markdown-файлов. Одна запись = один файл `items/<slug>.md` плюс Задачи — каталог markdown-файлов. Одна задача = один файл `items/<slug>.md` плюс
строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**: строка в `BACKLOG.md`. Скилл владеет **форматом и содержимым**: заводит,
заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё. редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё.
Чем он **не** владеет: **очередью** — что делать следующим и что перестало быть Чем он **не** владеет: **очередью** — что делать следующим и что перестало быть
важным, решает скилл `task-groom`, а этот скилл лишь даёт ему операции; и выполнением важным, решает скилл `task-groom`, а этот скилл лишь даёт ему операции; и выполнением
@@ -17,57 +17,53 @@ description: Ведение задач и целей как каталога mar
Ситуация не покрыта инструкцией — решай по ним. Ситуация не покрыта инструкцией — решай по ним.
0. **Цель — возможность приложения, задача — шаг к ней.** Цель отвечает на «что 0. **Стадия решает, что значит порядок строк.** Проект живёт в одной из двух
приложение будет уметь», её «Завершение» — наблюдаемый признак того, что уже стадий, и обе ведут один и тот же беклог, но читают его по-разному.
умеет; задача отвечает на «что для этого нужно сделать». Оценивается проект На **стройке** (`build`) беклог это план от базы к деталям: порядок —
по **поведению**, а не по внутреннему устройству, поэтому и роадмап отвечает зависимость, «раньше нельзя». На **доработке** (`support`) беклог это очередь
не «сколько работ осталось», а «что уже умеет и чего ещё не умеет». правок: порядок — важность, «раньше лучше». Из этого следует остальное —
Свойство поведения — тоже возможность: «сообщает о своём состоянии», сколько у беклога секций, как его пополняют, что значит его опустошение и
«исход слияния не зависит от порядка доставки» — законные цели. нужен ли груминг. Стадия объявлена ключом `[tasks] stage`; молчание ответом
1. **Беклог гниёт с той стороны, где его пополняют.** Заведение — самая частая не считается, и `check` без неё отказывает.
операция и с худшим отказом: из одного разговора рождается пять файлов, а 1. **Беклог гниёт с той стороны, где его пополняют — на доработке.** Заведение
переоценка потом разгребает то, чего не надо было заводить. Дедупликация и там самая частая операция и с худшим отказом: из одного разговора рождается
фильтр на входе дешевле любой чистки. Заводим только то, что **не делаем пять файлов, а переоценка потом разгребает то, чего не надо было заводить.
сейчас** и о потере чего пожалеем. Дедупликация и фильтр на входе дешевле любой чистки: заводим только то, что
2. **Файл — источник истины, индексы производны.** Разошлись — неправы индексы. **не делаем сейчас** и о потере чего пожалеем.
**На стройке правило не применяется**, и это не послабление. Список стройки
пишется вперёд целиком — он и есть замысел, — а фильтр «не заводи то, чего не
делаешь сейчас» запретил бы написать план дальше первого шага. Дедуп остаётся
в обеих стадиях: две записи об одном плохи всегда.
2. **Файл — источник истины, индекс производен.** Разошлись — неправ индекс.
Согласованность механизируема и проверяется командой, а не вниманием: всё, Согласованность механизируема и проверяется командой, а не вниманием: всё,
что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт. что ловит `tasks.py check`, не должно попадать ни в чек-лист, ни в промпт.
Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь
повторяет: пока поле лежало только в индексе, восстановление пропавшей повторяет: пока поле лежало только в индексе, восстановление пропавшей
строки теряло его молча и навсегда. Единственное исключение намеренное: **в каком строки теряло его молча и навсегда. Единственное исключение намеренное:
индексе лежит запись, знают индексы** — поля-состояния в файле нет. И **порядок строк в беклоге**он свойство списка, а не задачи, и в файле ему
**порядок строк в беклоге**: приоритет это свойство очереди, а не задачи, и в места нет (правило 4).
файле ему места нет (правило 4).
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через 3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
оставляет ничего, поэтому у неё есть `REJECTED.md`. оставляет ничего, поэтому у неё есть `REJECTED.md`.
4. **Приоритет — это порядок строк, а цель есть не у всякой задачи.** Очередь 4. **Порядок строк — единственное, чего в файле нет.** Он значим в обеих
внутри секции беклога значима: **первая строка — то, что делают следующим**. стадиях, и назначает его человек: на стройке — раскладывая шаги по
Приоритет назначает человек на груминге, машина его не выводит и не угадывает. зависимости, на доработке — на груминге. Машина порядок не выводит и не
угадывает; всё, что она делает сама, — ставит машинную позицию в **конец**
секции и говорит об этом вслух.
Прежде здесь стояло «порядка нет, есть цель», и обосновано это было тем, что **Дом порядка — индекс, а не файл.** Положи его в файл числом, и два соседних
на «что делать дальше» отвечает **набор спринта**. Набора больше нет, а файла смогли бы утверждать одно и то же место, а строка индекса —
вопрос остался — и без порядка отвечать на него стало нечем. противоречить обоим.
**Дом приоритета — индекс, а не файл.** Это то же исключение из правила 2,
что и «в каком индексе лежит запись»: приоритет — свойство очереди. Положи он
в файл числом, и два соседних файла смогли бы утверждать одно и то же место,
а строка индекса — противоречить обоим.
Цель обязательна там, где она и есть содержание работы, — у **новой
возможности** (`feature`). Починка, техдолг и разведка служат
работоспособности, а не направлению, и живут без цели законно. Придуманная им
цель — то же враньё, от которого спасает тип. **Цель и приоритет —
независимые оси:** очередь может идти поперёк целей, и это законно.
Одно место в очереди назначено **не человеком, а типом**: **сырьё** Одно место в очереди назначено **не человеком, а типом**: **сырьё**
(`research` без раздела «Вопрос») стоит в конце своей категории. Его не берут, (`research` без раздела «Вопрос») стоит в конце своей секции. Его не берут,
и между берущимся оно каждый раз требует открыть файл, чтобы это понять. Раз и между берущимся оно каждый раз требует открыть файл, чтобы это понять. Раз
это выводится, проверяет и чинит это машина. это выводится, проверяет и чинит это машина.
5. **Тип решает, что с записью можно делать.** Тип — единственная ось и первое 5. **Тип решает, что с задачей можно делать.** Тип — вторая ось и первое
поле меты: от него зависит, какие разделы обязательны в теле, нужна ли цель, поле меты: от него зависит, какие разделы обязательны в теле и берётся ли
берётся ли запись в работу и в каком индексе живёт её строка. Словарь закрыт; запись в работу. Словарь закрыт; ни один тип не подошёл — значит, в записи их
ни один тип не подошёл — значит, в записи их два, и её надо разделить. два, и её надо разделить.
## Раскладка ## Раскладка
@@ -79,55 +75,23 @@ description: Ведение задач и целей как каталога mar
``` ```
tasks/ tasks/
items/ задачи и цели файлами, <slug>.md, слаги английские items/ задачи файлами, <slug>.md, слаги английские
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет BACKLOG.md что можно взять. Порядок строк в секции значим,
BACKLOG.md что можно взять — только задачи, целей здесь нет. и значит он разное на разных стадиях
Порядок строк в секции значим: это очередь
REJECTED.md ушедшее БЕЗ реализации, с причиной и датой REJECTED.md ушедшее БЕЗ реализации, с причиной и датой
``` ```
Правило, снимающее путаницу: **`BACKLOG.md` — то, что берут; `ROADMAP.md` — то, **Индекс один.** `REJECTED.md` индексом не считается: он не говорит, где запись
подо что берут.** Цель в работу взять нельзя — берут её задачи, — поэтому в числится, — это кладбище ушедшего.
списке берущихся ей не место.
**Четыре секции роадмапа, и последняя отвечает на половину вопроса:** **Секции беклога называет проект**, и `check` проверяет у них ровно две вещи:
что секция есть хоть одна и что на стройке она **одна**. Смысла секции не несут
— это полки домена (`Ядро`, `Инфра`), — и подгонять их имена под свой вкус
скрипт права не имеет. Поле меты, называющее полку, зовётся **Категория**.
| Секция | Англ. | Что в ней | **Заголовок секции отбит пустой строкой с обеих сторон и написан с прописной**;
| --- | --- | --- | отбивку правит `check --fix`. Написание секции в мете файлов он тоже правит: имя
| `Запланировано` | `Planned` | очередь значима и обосновывается прозой рядом | секции принадлежит заголовку индекса, файл на неё только ссылается.
| `Направления` | `Directions` | очереди нет, тянутся долго |
| `Сопровождение` | `Operations` | чем держат проект: инструмент, процесс, эксплуатация — не возможности приложения, и потому отдельно |
| `Готово` | `Done` | достигнутые цели — строкой с датой, **без ссылки на файл**: файл удалён, поведение живёт в спеках |
**Порядок тоже канонический, и `Готово` стоит последним не из скромности.**
Достигнутое **копится**: через год этой секции больше, чем всех остальных
вместе. Стоя первой, она отодвигает за экран ровно то, ради чего роадмап
открывают чаще всего, — что делается сейчас и что дальше. Порядок проверяет
`check`, переставляет `check --fix`.
**Секции роадмапа канонические, категории беклога — нет**, и разница не в любви к
единообразию. У каждой секции роадмапа свой смысл, в достигнутое пишет сам `close`, и
роадмап, названный по-своему, читался бы только своим автором. Категории беклога
(`Ядро`, `Инфра`) смысла не несут — это полки домена, и остаются делом проекта.
Отсюда и разные имена поля меты: у цели **Секция** (часть роадмапа — состояние
очереди), у задачи **Категория** (полка домена, на которой она лежит).
Отсюда четыре правила, которые проверяет `tasks.py check`: **состав закреплён**
(чужая секция — ошибка, а не вольность), **все четыре обязаны быть** (нет
секции — нет ответа на её часть вопроса), **язык один на весь индекс**, **порядок
канонический**. `--roadmap-sections` у `init` нет: выбирать нечего.
**Заголовок секции отбит пустой строкой с обеих сторон и написан с прописной.**
Во всех индексах одинаково, включая категории беклога, которые проект называет сам.
Написание канонических секций правит `check --fix` (заодно и ссылку на секцию в
мете файлов: имя секции принадлежит заголовку индекса, файл на неё только
ссылается); отбивку и порядок он правит везде.
Оговорка про `Сопровождение`: слово `окружение` сюда не годится — в
`architecture.md` оно уже значит боевое окружение приложения, и одно слово в двух
смыслах развело бы документы канона. А `Разработка`, стоявшая тут раньше,
называла слишком много: роадмап **весь** про разработку, и секция с таким именем
не отличалась от остальных ничем.
**Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (работа не может **Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (работа не может
продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и
@@ -138,53 +102,31 @@ tasks/
который переезжает с такой секцией, её надо удалить** — это единственное место, который переезжает с такой секцией, её надо удалить** — это единственное место,
где это сказано. где это сказано.
**Запись живёт в одном индексе за раз.** Индексов два, и выбирает между ними **Порядок строк — единственное, чего в файле нет** (правило 4). Отсюда следствие
тип: цель в роадмапе, задача в беклоге. Сменился тип — строка переезжает для всякой машинной правки индекса: восстановленная или перенесённая строка
(`edit --type`). Файл в `items/` при этом **не двигается**: он и есть запись, встаёт **в конец своей секции**, и скрипт об этом говорит. Молчаливая вставка
индексы лишь показывают, где она числится и в каком порядке стоит. выдала бы машинную позицию за решение человека — а решение это его.
**Порядок строк в беклоге — приоритет**, и он единственное, чего в файле нет
(правило 4). Отсюда следствие для всякой машинной правки индекса:
восстановленная или перенесённая строка встаёт **в конец своей секции**, и
скрипт об этом говорит. Молчаливая вставка выдала бы машинную позицию за
решение человека — а решение это его.
**У сделанной задачи записи не остаётся** — файл и строка удаляются (`close **У сделанной задачи записи не остаётся** — файл и строка удаляются (`close
--implemented`). Ей хватает коммита и документации проекта; вторая запись была --implemented`). Ей хватает коммита и документации проекта; вторая запись была
бы вторым домом для того же факта. Вопрос «что было сделано и когда» отвечается бы вторым домом для того же факта. Вопрос «что было сделано и когда» отвечается
даром: индексы лежат под git, а закрытие коммитится отдельным коммитом учёта — даром: индекс лежит под git, а закрытие коммитится отдельным коммитом учёта —
`git log -p tasks/BACKLOG.md` отдаёт историю без отдельного журнала. `git log -p tasks/BACKLOG.md` отдаёт историю без отдельного журнала.
**У достигнутой цели запись остаётся, и это единственное исключение.** Файл
удаляется так же, а строка переезжает в секцию `Готово` с датой. Причина в том,
что цель — не работа, а **возможность**: «что приложение умеет» это половина
вопроса, ради которого роадмап и открывают, и стирать её вместе с файлом значит
оставить инструмент, отвечающий только «что осталось». Вторым домом это не
становится: поведение живёт в `openspec/specs/`, а роадмап отвечает **когда и в
каком порядке оно появилось** — другой вопрос. Ссылки на файл в строке нет
намеренно: файл удалён, а битая ссылка — законная ошибка `check`.
Куда запись может переехать и какой командой — весь набор переходов: Куда запись может переехать и какой командой — весь набор переходов:
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
state "BACKLOG.md — что берут" as B state "BACKLOG.md — что берут" as B
state "ROADMAP.md — подо что берут" as P
state "REJECTED.md — ушла без реализации" as R state "REJECTED.md — ушла без реализации" as R
state "записи нет — реализована" as D state "записи нет — реализована" as D
state "ROADMAP.md, «умеет» — цель достигнута" as A
[*] --> B: add --type feature|fix|chore|research [*] --> B: add --type feature|fix|chore|research
[*] --> P: add --type goal B --> B: move --after | --first | --section
B --> P: edit --type goal --section
P --> B: edit --type feature|fix|chore|research --section
B --> D: close --implemented B --> D: close --implemented
P --> A: close --implemented
B --> R: close --reason B --> R: close --reason
P --> R: close --reason
D --> B: reopen --reason D --> B: reopen --reason
R --> B: reopen --reason R --> B: reopen --reason
A --> P: reopen --reason
``` ```
Состояния здесь — **где числится строка**, а не где лежит файл: файл Состояния здесь — **где числится строка**, а не где лежит файл: файл
@@ -195,85 +137,92 @@ stateDiagram-v2
Схема — **сводка**: условия и оговорки живут в тексте разделов, и при Схема — **сводка**: условия и оговорки живут в тексте разделов, и при
расхождении прав текст. расхождении прав текст.
## Цели ## Две стадии
**Цель — возможность приложения.** Такой же файл в `items/`, тип `goal` (🎯), **Стадия проекта — ось, и решает она, что значит порядок строк беклога.**
перечисленный в `ROADMAP.md`. Формулируется ответом на вопрос **«что приложение Значения два, дом — ключ `[tasks] stage` в `.av-dev.toml`.
будет уметь»**, а не названием области работ: не «Работа с чтением», а «Чтение
данных клиентами»; не «Рефакторинг слияния», а «Исход слияния не зависит от
порядка доставки».
**Свойство поведения — тоже возможность.** «Наблюдаемость» это «приложение | | `build` — стройка | `support` — доработка |
сообщает о своём состоянии»; «прочность слияния» это «исход не зависит от | --- | --- | --- |
порядка». Такие цели законны и переформулировки в функцию не требуют — требуют | Порядок строк | зависимость: раньше **нельзя** | важность: раньше **лучше** |
только, чтобы формулировка отвечала на «что приложение делает», а не на «какую | Секции | ровно одна: список от базы к деталям | полки домена, сколько нужно |
часть кода мы трогаем». | Заведение | список пишется вперёд целиком | по одной, по мере появления |
| Пустой беклог | план исчерпан, стройка окончена | нормальное состояние |
| Груминг | не применяется; замысел сменился — план пересматривается целиком | основная гигиена, порциями по 5–8 |
| Залежалость | не считается: шаг ждёт своей очереди законно | считается, `list --stale` |
**Целью не становится работа, которой держат проект.** Состав перечислен **Стадия называется явно, и молчание ответом не считается.** Без неё порядок
[в словаре сопровождения](../../shared/operations.md); строк нечем прочитать: переставить строку значит на стройке сломать план, а на
на вопрос «что приложение будет уметь» ничто из него не отвечает. Им отведена отдельная секция роадмапа, доработке — принять решение о важности, и это разные действия. `init --stage`
чтобы они были видны в том же экране и при этом не читались как возможности обязателен, `check` без ключа отказывает, `check --fix` его не подставляет:
продукта. какая стадия у проекта, знает человек, а подставленное умолчание соврало бы ровно
там, где по нему принимают решение.
**Граница проходит по тому, кто наблюдает, а не по теме.** «Приложение сообщает **Секций на стройке одна, и это не педантизм.** Порядок там — зависимость, и
о своём состоянии» — возможность: наблюдает пользователь сервиса, и цели место разложенный по полкам список перестаёт быть планом: два шага из разных секций
среди прочих. «Дежурный видит состояние на одном экране» — сопровождение: уже не сравнить. На доработке полки законны — правки независимы, и очередь
наблюдаем мы. Одна и та же наблюдаемость попадает в разные секции, и это верно — внутри полки самостоятельна.
секции отвечают на разные вопросы.
**Сопровождение и эксплуатация — целое и часть**, а не синонимы, и та же тема **Переход — событие, а не настройка.** `tasks.py stage support` переносит остаток
живёт ещё в двух местах: разделе «Эксплуатация» в `architecture.md` и теме ревью беклога в первую новую секцию, правит «Категорию» в файлах и говорит, что порядок
`operations`. Словарь у всех трёх общий, и дом у него один: с этого момента значит другое. Датой ему служит коммит: отдельного журнала ради
[shared/operations.md](../../shared/operations.md) — читается по ссылке. одной строки не заводится. Признак созревания наблюдаемый — беклог стройки
Пересказывать его своими словами нельзя: три перечня «чем держат проект» уже исчерпан, и `check` об этом напоминает; **запретить переход раньше скрипт не
разъезжались на «метриках и логах» против «мониторинга». берётся**: «приложение построено» решает человек, а не счётчик строк.
Секция выбирается так: очередь значима и обоснована прозой — `Запланировано`; Обратный переход (`stage build`) разрешён и устроен так же. Он редок — проект
тянется долго и очереди не имеет — `Направления`; не про приложение, а про то, уходит на стройку заново разве что при переделке замысла целиком, — но
чем его держат, — `Сопровождение`; в `Готово` кладёт сам `close`. запрещать его было бы запретом на то, что иногда и правда случается.
- **Список задач цели выводится, а не хранится.** В теле цели — зачем она и что ## Чего у задач больше нет
считается её завершением; перечня задач там нет. Он был бы третьим индексом и
поехал бы на первой же закрытой задаче, а `check` про него не знает. Связь **Тип `goal` и `ROADMAP.md` упразднены.** Цель была зонтиком над параллельными
однонаправленна: задача несёт тег `goal:<слаг>`, перечень даёт направлениями: она нужна там, где список работ нельзя выстроить в один порядок,
`tasks.py list --goal <слаг>`. и очередь идёт поперёк направлений. У проекта, который ведёт один человек, такого
- **Статус цели выводится.** Цель достигнута, когда у неё не осталось открытых не бывает — на стройке список линеен по зависимости, на доработке правки
задач; `[x]`/`[~]` руками не ведутся, а `close` цели с живыми задачами независимы, — и зонтик не стоял ни над чем.
скрипт запретит. Достижение — `close <цель> --implemented`: файл удаляется,
строка с датой переезжает в `Готово`. Ошиблись — `reopen` вернёт файл и Роадмап при этом отвечал на свой вопрос наполовину: «чего ещё не умеет» — это
**снимет строку достигнутого**, чтобы роадмап не утверждал того, чего нет. Единственная оговорка: цель без задач неотличима — «ещё не «что осталось в беклоге», то есть пересказ второго индекса. Вторая половина, «что
разобрана» или «всё закрыто». Различает **тег `decomposed`** в мете уже умеет», живёт в двух домах и без него: нормативное поведение — в
цели: он ставится, когда цель разложена на задачи. Тег, а не строка в теле — `openspec/specs/`, а когда и в каком порядке оно появилось — в `git log` индекса
потому что проверяется механически: `check` **напоминает** о нём у пустой цели и коммитах задач.
(замечанием, не ошибкой — неразобранная цель это законное состояние), а `check
--fix` сам проставляет его цели, у которой задачи есть. Вместе с целью ушли: секция `Готово` (её ответ дают спеки и история), теги
- **Тип `[epic]` упразднён.** Он был зонтиком между целью и задачами — «задача, `goal:<слаг>` и `decomposed`, поле меты `Секция` (осталась `Категория`), раздел
которая не мерджится целиком». Зонтик теперь цель, а слишком крупный шаг просто `Завершение`, ключи `[tasks] roadmap` и `[tasks] completion_heading`, флаги
дробится на шаги помельче под той же целью, и промежуточному типу места не `add --goal`, `list --goal`, `list --index`, `edit --goal`, `edit --section` и
осталось. Замер подтвердил: ноль употреблений на 97 записей двух живых `init --roadmap`. Встретились в проекте —
проектов. Встретился в чужом беклоге — это цель либо набор задач, и `check` `check` назовёт их поимённо, `check --fix` снимет теги, а запись типа `goal`
назовёт его неизвестным типом. оставит человеку: во что она превращается — в задачу или в ничто, — машина не
решает.
**Тип `[epic]` упразднён раньше и не вернулся.** Слишком крупный шаг дробится на
шаги помельче, стоящие в списке подряд.
## Тип записи ## Тип записи
**Тип — единственная ось, и он решает, что с записью можно делать.** Дом типа — **Тип решает, что с задачей можно делать.** Вторая ось скилла — первая стадия.
Перечень осей всего процесса и того, чего каждая **не** решает, —
[shared/axes.md](../../shared/axes.md). Дом типа —
**поле меты `Тип` первой строкой**; эмодзи в заголовке H1 от него производна, её **поле меты `Тип` первой строкой**; эмодзи в заголовке H1 от него производна, её
ставит `add` и чинит `check --fix`. ставит `add` и чинит `check --fix`.
| Тип | Обязательные разделы | Цель | В работу | Устав | | Тип | Обязательные разделы | Устав |
| --- | --- | --- | --- | --- | | --- | --- | --- |
| 🎯 `goal` | `Завершение` | — | нет | [task-goal.md](references/task-goal.md) | | `feature` | `Затрагивает`, `Критерии приёмки` | [task-feature.md](references/task-feature.md) |
| `feature` | `Затрагивает`, `Критерии приёмки` | **обязательна** | да | [task-feature.md](references/task-feature.md) | | 🐞 `fix` | `Воспроизведение`, `Затрагивает`, `Критерии приёмки` | [task-fix.md](references/task-fix.md) |
| 🐞 `fix` | `Воспроизведение`, `Затрагивает`, `Критерии приёмки` | необязательна | да | [task-fix.md](references/task-fix.md) | | 🧹 `chore` | `Затрагивает`, `Критерии приёмки` | [task-chore.md](references/task-chore.md) |
| 🧹 `chore` | `Затрагивает`, `Критерии приёмки` | нет | да | [task-chore.md](references/task-chore.md) | | 🔬 `research` | `Вопрос`, `Куда ляжет ответ` | [task-research.md](references/task-research.md) |
| 🔬 `research` | `Вопрос`, `Куда ляжет ответ` | нет | да | [task-research.md](references/task-research.md) |
Берутся в работу все четыре: записи, которую нельзя взять, больше не существует.
Сверх обязательных у любой задачи допустимы `Рамки` и `Вопросы`. Раздел не из Сверх обязательных у любой задачи допустимы `Рамки` и `Вопросы`. Раздел не из
схемы своего типа — **замечание, а не ошибка**: свой раздел законная вольность схемы своего типа — **замечание, а не ошибка**: свой раздел законная вольность
проекта, но `Воспроизведение` у `chore` почти всегда значит, что тип проставлен проекта, но `Воспроизведение` у `chore` почти всегда значит, что тип проставлен
не тот, и сказать об этом стоит, не запрещая. не тот, и сказать об этом стоит, не запрещая.
**Осей было две, и ортогональность у них была фальшивой.** Тип записи **Прежних осей было две, и ортогональность у них была фальшивой.** Тип записи
(`goal`/`idea`/`task`) и род работы (`kind:<род>` тегом) давали двенадцать клеток (`goal`/`idea`/`task`) и род работы (`kind:<род>` тегом) давали двенадцать клеток
произведения, из которых законны были шесть: у цели род запрещён, у задачи произведения, из которых законны были шесть: у цели род запрещён, у задачи
обязателен, у идеи пуст. Плюс алгоритм работы крепится не к `task`, а к `fix` и обязателен, у идеи пуст. Плюс алгоритм работы крепится не к `task`, а к `fix` и
@@ -299,7 +248,8 @@ stateDiagram-v2
изменения, а не по типу задачи: `chore` бывает миграцией схемы, `fix` — правкой изменения, а не по типу задачи: `chore` бывает миграцией схемы, `fix` — правкой
публичного контракта. Правило «предписание процесса в теле задачи снимается» публичного контракта. Правило «предписание процесса в теле задачи снимается»
типом не отменяется, а подтверждается: он описывает работу, а не то, как её типом не отменяется, а подтверждается: он описывает работу, а не то, как её
проверять. проверять. **Стадия проекта их тоже не выбирает**: изменение на стройке ничем не
проще того же изменения на доработке, и метку ему по-прежнему назначает разметка.
**Одно исполнителю тип всё же говорит — каким сценарием работу вести, и то не **Одно исполнителю тип всё же говорит — каким сценарием работу вести, и то не
один.** Скилл `av-dev:code-resolve` выбирает сценарий связкой из двух один.** Скилл `av-dev:code-resolve` выбирает сценарий связкой из двух
@@ -314,11 +264,10 @@ stateDiagram-v2
Три требования к тексту. Первое — про заголовок, два остальных про то, чтобы Три требования к тексту. Первое — про заголовок, два остальных про то, чтобы
задачу можно было **оценить, не открывая код**. задачу можно было **оценить, не открывая код**.
**Заголовок отвечает на вопрос своего типа.** Вопросов три, поэтому и форм три: **Заголовок отвечает на вопрос своего типа.** Вопросов два, поэтому и форм две:
| Тип | Отвечает на | Пример | | Тип | Отвечает на | Пример |
| --- | --- | --- | | --- | --- | --- |
| 🎯 `goal` | что приложение будет уметь | Соперником может быть компьютер |
| ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | Печатать поле одним куском кода | | ✨ `feature`, 🐞 `fix`, 🧹 `chore` | что нужно сделать | Печатать поле одним куском кода |
| 🔬 `research` | о чём разведка | Подсказка следующего хода | | 🔬 `research` | о чём разведка | Подсказка следующего хода |
@@ -331,10 +280,6 @@ stateDiagram-v2
исход знание, а что делать — ещё неизвестно, и заголовок-действие обещал бы исход знание, а что делать — ещё неизвестно, и заголовок-действие обещал бы
решённость, которой нет. решённость, которой нет.
Из этого же правила растёт разница индексов: роадмап — список возможностей,
беклог — список работ, и если заголовки перепутать формами, каждый из них
начинает читаться как другой.
`check` считает заголовки не в форме действия и печатает **число** в блоке `check` считает заголовки не в форме действия и печатает **число** в блоке
здоровья, не замечанием на файл: проверка эвристическая (первое слово на здоровья, не замечанием на файл: проверка эвристическая (первое слово на
`-ть`/`-ти`/`-чь`), а беклог, заведённый до правила, не переоформляют «заодно». `-ть`/`-ти`/`-чь`), а беклог, заведённый до правила, не переоформляют «заодно».
@@ -384,58 +329,70 @@ stateDiagram-v2
подкаталога — обычное дело. подкаталога — обычное дело.
``` ```
python3 $tk check --dir D # согласованность индексов + здоровье python3 $tk check --dir D # согласованность индекса + здоровье
python3 $tk check --dir D --fix # + починить дрейф (тип, эмодзи, место, заголовок, дубли, «зачем», форма меты) python3 $tk check --dir D --fix # + починить дрейф (тип, эмодзи, место, заголовок, дубли, «зачем», форма меты)
python3 $tk list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--goal S] [--raw] [--index …] [--questions] python3 $tk list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--raw] [--questions]
python3 $tk add --dir D --slug S --title T --type goal|feature|fix|chore|research [--section S] [--goal G] [--why «зачем»] [--tag a,b] python3 $tk add --dir D --slug S --title T --type feature|fix|chore|research [--section S] [--why «зачем»] [--tag a,b]
python3 $tk edit S --dir D [--title T] [--why «зачем»] [--type T] [--goal G] [--add-tag a,b] [--rm-tag c] python3 $tk edit S --dir D [--title T] [--why «зачем»] [--type T] [--add-tag a,b] [--rm-tag c]
python3 $tk move S --dir D [--section S] [--reason R] [--after S | --first] # без --section — текущая секция python3 $tk move S --dir D [--section S] [--reason R] [--after S | --first] # без --section — текущая секция
python3 $tk close S --dir D --reason R # в REJECTED.md + удалить (ушла без реализации) python3 $tk close S --dir D --reason R # в REJECTED.md + удалить (ушла без реализации)
python3 $tk close S --dir D --implemented # просто удалить (реализована и закоммичена) python3 $tk close S --dir D --implemented # просто удалить (реализована и закоммичена)
python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась
python3 $tk ready S… --dir D # схема типа выполнена — можно брать в работу python3 $tk ready S… --dir D # схема типа выполнена — можно брать в работу
python3 $tk init --dir D [--sections …] [--items …] [--backlog …] … python3 $tk stage --dir D # показать стадию
python3 $tk adopt scan --from … | apply --plan … # разовая адаптация, references/adopt.md python3 $tk stage support --dir D [--sections] # сменить стадию: секции и смысл порядка
python3 $tk init --dir D --stage build|support [--sections …] [--items …] …
python3 $tk adopt scan --from … --stage S | apply --plan … # разовая адаптация, references/adopt.md
``` ```
**Коды выхода — единый словарь; на нём ветвятся скиллы, а не на тексте вывода:** **Копия.** Дом словаря — `shared/axes.md` в репозитории плагина: словарь общий
для всех скриптов, и ни один скилл им не владеет. Правится дом, а не этот файл.
| Код | Что случилось | Что делать | <!-- копия: коды-выхода из av-dev/shared/axes.md -->
| --- | --- | --- |
| 0 | сошлось / сделано | дальше по сценарию |
| 1 | **только `check`:** найден дрейф индексов и файлов | `check --fix`, остаток разобрать |
| 2 | ошибка употребления: аргументы или нарушенное правило | читать сообщение, это отказ по существу |
| 3 | окружение: каталог не найден, конфиг битый или мимо диска | чинится путём или `.av-dev.toml` в корне, повтор не поможет |
| 4 | внутренний сбой | дефект скрипта, доложить |
Различать 1 и 3 обязательно: «дрейф в беклоге» — рабочая ситуация, «каталога **Коды выхода — общий словарь всех скриптов `av-dev`. Ветвись на коде, а не на
нет» — нерабочая, и одинаковая реакция на них была бы неверна в обоих случаях. тексте вывода.**
Тип — английское ключевое слово `goal` / `feature` / `fix` / `chore` / | Код | Что случилось |
`research` (как и прочие токены команд), у `add` **обязательное**: без него | --- | --- |
неизвестно, какой шаблон тела класть. Текст задачи при этом русский, а эмодзи в | 0 | сошлось |
заголовке ставит скрипт. | 1 | дрейф: рабочая ситуация, чинится |
| 2 | ошибка употребления: аргументы или нарушенное правило |
| 3 | окружение: не тот каталог, битый конфиг, нет инструмента |
| 4 | внутренний сбой — дефект скрипта, доложить |
**Мутации правят файл и индексы заодно** — руками строку индекса или мету **Различать 1 и 3 обязательно.** «Дрейф» — рабочая ситуация, и чинится она
не пиши, зови `add`/`edit`/`move`/`close`/`reopen`. Смена заголовка, «зачем», типа, правкой предмета; «окружение» — нерабочая, и повтор той же командой не поможет.
цели и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс Одинаковая реакция на них неверна в обоих случаях.
<!-- /копия: коды-выхода -->
Здесь это значит: код 1 приходит **только от `check`** — найден дрейф индекса и
файлов, чинится `check --fix`, остаток разбирается руками. Код 3 — каталог не
найден, конфиг битый или мимо диска: чинится путём или `.av-dev.toml` в корне.
Тип — английское ключевое слово `feature` / `fix` / `chore` / `research` (как и
прочие токены команд), у `add` **обязательное**: без него неизвестно, какой
шаблон тела класть. Стадия — такое же слово, `build` / `support`, и у `init` она
обязательна по той же причине: без неё неизвестно, что писать в шапке беклога и
сколько заводить секций. Текст задачи при этом русский, а эмодзи в заголовке
ставит скрипт.
**Мутации правят файл и индекс заодно** — руками строку индекса или мету
не пиши, зови `add`/`edit`/`move`/`close`/`reopen`. Смена заголовка, «зачем», типа
и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс
согласованными. Снятие тега — `--rm-tag` (после ответа на вопрос снимается согласованными. Снятие тега — `--rm-tag` (после ответа на вопрос снимается
`question`), смена цели — `--goal`, типа — `--type`; оба заменяют прежнее `question`), смена типа — `--type`; оба заменяют прежнее значение, а не
значение, а не добавляют второе. добавляют второе.
**Переезд между индексами — следствие смены типа, а не отдельная команда.** **Секцию меняет только `move`, и он пишет причину**: смена полки без причины и
`edit <slug> --type goal --section <часть роадмапа>` переносит строку из есть тот дрейф, который потом никто не объяснит.
`BACKLOG.md` в `ROADMAP.md` (и обратно — задачным типом плюс
`--section <категория беклога>`);
`move` двигает только внутри одного индекса и пишет причину. `--section` у
`edit` работает **только** при таком переезде — иначе он отсылает к `move`,
потому что смена секции без причины и есть тот дрейф, который потом никто не
объяснит.
**`move --after <слаг>` и `move --first` — это и есть расстановка приоритета.** **`move --after <слаг>` и `move --first` — это и есть расстановка порядка.**
Порядок строк в секции значим (правило 4), и двигают его только этой командой: Порядок строк в секции значим (правило 4), и двигают его только этой командой:
руками поправленная строка не оставляет причины, а причина здесь и есть половина руками поправленная строка не оставляет причины, а причина здесь и есть половина
решения. решения. Что именно этот порядок значит, говорит стадия: на стройке `--after`
называет зависимость, на доработке — приоритет.
Тело задачи скрипт не трогает: Тело задачи скрипт не трогает:
`add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь `add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь
@@ -446,18 +403,23 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся правки**, даже если правил мутациями: дрейф мог накопиться раньше. Накопившееся
чини `check --fix` — он детерминированно правит то, где истина однозначна (тип в чини `check --fix` — он детерминированно правит то, где истина однозначна (тип в
своё поле, эмодзи заголовка, имя поля места, секция, заголовок, дубли, «зачем» из своё поле, эмодзи заголовка, имя поля места, секция, заголовок, дубли, «зачем» из
индекса в файл, старая форма меты, пометка `decomposed` у цели с задачами, сырьё индекса в файл, старая форма меты, снятые теги упразднённых целей, сырьё
в конец категории), а неоднозначное (задача сразу в двух индексах, нечего в конец секции), а неоднозначное (нечего восстанавливать, **тип, которого
восстанавливать, **тип, которого неоткуда взять**) печатает отдельной пометкой неоткуда взять**, запись типа `goal`) печатает отдельной пометкой
`НЕОДНОЗНАЧНО` — это тебе, и это идёт строкой доклада. **Ссылка на исчезнувший `НЕОДНОЗНАЧНО` — это тебе, и это идёт строкой доклада. **Ссылка на исчезнувший
файл в пометку не попадает:** `--fix` её просто не трогает, и она остаётся файл в пометку не попадает:** `--fix` её просто не трогает, и она остаётся
`ОШИБКА` обычного `check` — то есть видна, но в докладе её надо назвать отдельно. `ОШИБКА` обычного `check` — то есть видна, но в докладе её надо назвать отдельно.
`--fix` правит **и файлы** — там, где источник ровно один и выбирать не из чего: `--fix` правит **и файлы** — там, где источник ровно один и выбирать не из чего:
тип переезжает из прежнего дома (тег `kind:`, префикс `[goal]`/`[idea]`) в поле тип переезжает из прежнего дома (тег `kind:`, префикс `[goal]`/`[idea]`) в поле
меты, заголовок получает эмодзи, поле места — имя по типу, «зачем», оставшееся меты, заголовок получает эмодзи, поле места зовётся «Категория», «зачем»,
только в индексе, переезжает в мету, цель с задачами получает `decomposed`. оставшееся только в индексе, переезжает в мету, теги `goal:` и `decomposed`
Каждый случай печатается поимённо. снимаются. Каждый случай печатается поимённо.
**Ни стадию, ни состав секций `--fix` не трогает.** Стадию он подставить не
может — это решение человека; секции стройки не сливает — в каком порядке пойдут
строки слитых полок, знает тоже только человек, а порядок здесь и есть
содержание.
**Тип, который не выводится ниоткуда, `--fix` не угадывает.** `feature` от **Тип, который не выводится ниоткуда, `--fix` не угадывает.** `feature` от
`chore` машина не отличает, и подставленное наугад значение врало бы ровно там, `chore` машина не отличает, и подставленное наугад значение врало бы ровно там,
@@ -468,7 +430,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
работу — там, где по ней принимают решение; `check` поимённо о ней не говорит, а работу — там, где по ней принимают решение; `check` поимённо о ней не говорит, а
считает: строка здоровья **«схема типа не выполнена: N из M»** называет число и считает: строка здоровья **«схема типа не выполнена: N из M»** называет число и
первые слаги, строка **«готово к взятию»** — сколько задач беклога пройдут первые слаги, строка **«готово к взятию»** — сколько задач беклога пройдут
`ready` целиком (схема плюс цель плюс отсутствие открытого вопроса). Это две `ready` целиком (схема плюс отсутствие открытого вопроса). Это две
разные строки, и совпадение их чисел — совпадение. У каждой части своя глубина: разные строки, и совпадение их чисел — совпадение. У каждой части своя глубина:
- **тип** — жёстко: назван и из закрытого словаря; - **тип** — жёстко: назван и из закрытого словаря;
@@ -476,7 +438,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
(меньше двух отказ, больше пяти замечание), наличие оракула **эвристикой** по (меньше двух отказ, больше пяти замечание), наличие оракула **эвристикой** по
слову «оракул» в пункте; слову «оракул» в пункте;
- **прочие разделы схемы** (`Затрагивает`, `Воспроизведение`, `Вопрос`, - **прочие разделы схемы** (`Затрагивает`, `Воспроизведение`, `Вопрос`,
`Куда ляжет ответ`, `Завершение`) — только **наличие непустого**. Содержимое `Куда ляжет ответ`) — только **наличие непустого**. Содержимое
машине не видно: границу, которую забыли назвать, она от отсутствующей не машине не видно: границу, которую забыли назвать, она от отсутствующей не
отличает, а шаги, по которым ничего не воспроизводится, — от годных. отличает, а шаги, по которым ничего не воспроизводится, — от годных.
@@ -485,10 +447,10 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
разделов своего типа и число критериев, годность оракулов и полнота границ — разделов своего типа и число критериев, годность оракулов и полнота границ —
глазами». глазами».
Формат записи, меты, слага, индексов и `REJECTED.md` Формат записи, меты, слага, индекса и `REJECTED.md`
[references/task-format.md](references/task-format.md); там же тест «готова к [references/task-format.md](references/task-format.md); там же тест «готова к
взятию». Схема и алгоритм каждого типа — по файлу на тип: взятию». Схема и алгоритм каждого типа — по файлу на тип:
[goal](references/task-goal.md) · [feature](references/task-feature.md) · [feature](references/task-feature.md) ·
[fix](references/task-fix.md) · [chore](references/task-chore.md) · [fix](references/task-fix.md) · [chore](references/task-chore.md) ·
[research](references/task-research.md). [research](references/task-research.md).
@@ -496,7 +458,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
Формат каталога задач меняется, и проект должен знать, к какой версии он Формат каталога задач меняется, и проект должен знать, к какой версии он
приведён. Число живёт ключом `version` в `.av-dev.toml` в корне репозитория, приведён. Число живёт ключом `version` в `.av-dev.toml` в корне репозитория,
журнал версий — [журнал скилла `doc-canon`](../doc-canon/references/changelog.md), журнал версий — [журнал скилла `canon`](../canon/references/changelog.md),
сверяет их `tasks.py check`: отстало — строка расхождения, ушло вперёд — устарел сверяет их `tasks.py check`: отстало — строка расхождения, ушло вперёд — устарел
плагин. Обратной совместимости нет: есть «приведён» и «не приведён». плагин. Обратной совместимости нет: есть «приведён» и «не приведён».
@@ -506,7 +468,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
половины проектов нет. Плагин один — довод ушёл, а два числа оставляли бы половины проектов нет. Плагин один — довод ушёл, а два числа оставляли бы
вопрос, по какому журналу повышать. вопрос, по какому журналу повышать.
**Повышает проект скилл `av-dev:doc-canon`, операция `upgrade`** — он идёт по **Повышает проект скилл `av-dev:canon`, операция `upgrade`** — он идёт по
журналу, двигает число и зовёт этот скилл там, где запись касается задач. Здесь журналу, двигает число и зовёт этот скилл там, где запись касается задач. Здесь
повышения нет намеренно: две операции, двигающие одно число, разъезжаются на повышения нет намеренно: две операции, двигающие одно число, разъезжаются на
первом же проекте, где прошла только одна из них. первом же проекте, где прошла только одна из них.
@@ -515,9 +477,13 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
### Завести запись из диалога ### Завести запись из диалога
1. **Фильтр.** Делаем прямо сейчас — не заводим. Не пожалеем о потере — не 0. **Посмотри стадию**`stage`. От неё зависят шаг 1 и место новой строки: на
заводим. Родилось три кандидата — покажи их и спроси, какие заводить: молча доработке беклог пополняют по одной и с фильтром, на стройке пишут планом.
заведённая пачка и есть тот самый отказ из правила 1. 1. **Фильтр — на доработке.** Делаем прямо сейчас — не заводим. Не пожалеем о
потере — не заводим. Родилось три кандидата — покажи их и спроси, какие
заводить: молча заведённая пачка и есть тот самый отказ из правила 1.
**На стройке фильтра нет**: план пишется вперёд целиком, и «этого мы сейчас
не делаем» — не довод против шага, а описание всякого шага, кроме первого.
2. **Дедуп.** `list` плюс поиск по слагам, полю «зачем» и телам (`grep -ril`), 2. **Дедуп.** `list` плюс поиск по слагам, полю «зачем» и телам (`grep -ril`),
**включая `REJECTED.md`**. Нашлось среди живых — **дописываем в существующий **включая `REJECTED.md`**. Нашлось среди живых — **дописываем в существующий
файл**, а не заводим соседний. Нашлось в `REJECTED.md` — покажи пользователю файл**, а не заводим соседний. Нашлось в `REJECTED.md` — покажи пользователю
@@ -526,7 +492,6 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
переоценки. переоценки.
3. **Тип**`--type` обязателен, и он же первое содержательное решение: 3. **Тип**`--type` обязателен, и он же первое содержательное решение:
- возможность приложения, а не шаг к ней → `goal`;
- снаружи появляется то, чего не было → `feature`; - снаружи появляется то, чего не было → `feature`;
- поведение расходится с заявленным и **воспроизводится**`fix` - поведение расходится с заявленным и **воспроизводится**`fix`
(не воспроизводится → `research`); (не воспроизводится → `research`);
@@ -535,12 +500,12 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
Не подходит ни один — в записи их два, разбирай. Не проходит тест готовности Не подходит ни один — в записи их два, разбирай. Не проходит тест готовности
(см. task-format) — это **сырьё**: `--type research`, раздел «Вопрос» пока (см. task-format) — это **сырьё**: `--type research`, раздел «Вопрос» пока
пуст, место в конце категории. Не делается одним заходом — это не эпик, а пуст, место в конце секции. Не делается одним заходом — дроби на шаги
несколько задач под одной целью: дроби сразу. помельче и ставь их в списке подряд.
4. **Цель — если тип её требует.** У `feature` должен быть `--goal <слаг>`: 4. **Место в списке.** `add` кладёт строку в конец секции всегда. На стройке это
новая возможность и есть содержание цели. Подходящей нет — либо она почти наверняка не то место: порядок там зависимость, и новый шаг чаще всего
заводится (`--type goal`), либо перед тобой не `feature`. У `fix`, `chore` и встаёт в середину — `move <слаг> --after <слаг>`. На доработке конец списка
`research` цели может не быть вовсе, и придумывать её не надо. законен: место в очереди назначает груминг, а не заведение.
5. `add …`, затем допиши тело редактором **по схеме своего типа** — шаблон её 5. `add …`, затем допиши тело редактором **по схеме своего типа** — шаблон её
уже разложил, устав типа объясняет каждый раздел. «Зачем» отвечает «зачем уже разложил, устав типа объясняет каждый раздел. «Зачем» отвечает «зачем
нужна эта задача» — состояние, остаток, боль, — а не пересказывает первый нужна эта задача» — состояние, остаток, боль, — а не пересказывает первый
@@ -554,18 +519,46 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
пять файлов из одной мысли, а сорок файлов из сорока сырых находок. Защита та пять файлов из одной мысли, а сорок файлов из сорока сырых находок. Защита та
же, что в самом ревью: кластеризация по причине, дедуп против живых и же, что в самом ревью: кластеризация по причине, дедуп против живых и
`REJECTED.md`, находка без свидетельства → сырьё (`research`), а не задача, и карта кластеров `REJECTED.md`, находка без свидетельства → сырьё (`research`), а не задача, и карта кластеров
пользователю до создания файлов. Порядок, отображение серьёзности и привязка к пользователю до создания файлов. **Стадию смотри и здесь**, тем же нулевым
целям — [references/from-review.md](references/from-review.md). шагом: от неё зависит, куда ляжет тяжёлая находка — наверх очереди или после
своей зависимости. Порядок и отображение серьёзности —
[references/from-review.md](references/from-review.md).
### Прийти в репозиторий, где задачи уже как-то ведутся ### Прийти в репозиторий, где задачи уже как-то ведутся
Разовая операция: вывести каталог задач из старой раскладки беклога, `TODO.md`, Разовая операция: вывести каталог задач из старой раскладки беклога, `TODO.md`,
заметок или списка шагов роадмапа — [references/adopt.md](references/adopt.md). заметок или списка шагов плана — [references/adopt.md](references/adopt.md).
Сюда же относится переименование транслитных слагов в английские: оно делается Сюда же относится переименование транслитных слагов в английские: оно делается
**одним проходом вместе с починкой перекрёстных ссылок**, а не по одному слагу. **одним проходом вместе с починкой перекрёстных ссылок**, а не по одному слагу.
Если переводить надо не только задачи, а весь `docs/` — это скилл Если переводить надо не только задачи, а весь `docs/` — это скилл
`av-dev:doc-canon`, и он зовёт этот сценарий сам на своём шаге. `av-dev:canon`, и он зовёт этот сценарий сам на своём шаге.
### Пересмотр плана стройки
Операция стадии `build`, и на доработке её нет: там переоценка идёт порциями и
называется грумингом. **Повод один — сменился замысел**, а не «давно не
смотрели»: план стройки протухает не по частям, а целиком, потому что порядок в
нём — зависимость, и одна изменившаяся посылка переставляет всё, что ниже.
Порционный разбор здесь вреден, и это не вкус: вынуть пять шагов из середины
списка, порядок которого и есть его содержание, — значит получить план, про
который никто уже не скажет, почему он такой.
1. **Назови, что изменилось в замысле.** Одной фразой, и она уедет причиной в
каждое движение. Не находится — значит повода нет, и пересмотр не нужен.
2. **Прочитай список целиком**, сверху вниз, и по каждой строке ответь одно из
трёх: остаётся как есть, переезжает (`move --after` с причиной), уходит
(`close --reason`). Дописанное новое встаёт туда, куда велит зависимость, а
не в конец.
3. **Покажи человеку весь новый список**, а не отдельные решения: план читается
только целиком. `AskUserQuestion` с готовым порядком и доводом на каждое
движение.
4. `check` и доклад: сколько строк тронуто из скольких, что ушло и почему.
**Границу с грумингом держи твёрдо.** Если хочется пересмотреть план «потому что
накопилось» — это не пересмотр, а признак того, что стройка кончилась: беклог
перестал быть планом и стал очередью. Проверь `stage`.
### Декомпозиция и штурм сырья ### Декомпозиция и штурм сырья
@@ -586,16 +579,15 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
| Проход | Что смотрит | Над чем работает | | Проход | Что смотрит | Над чем работает |
| --- | --- | --- | | --- | --- | --- |
| `task-form` | заголовок по типу, «зачем» вместо пересказа, границы вместо замысла, годность оракулов, предписание процесса, связь со строкой «Завершения» цели | только `items/`, **открывает файл цели** | | `task-form` | заголовок по типу, «зачем» вместо пересказа, границы вместо замысла, годность оракулов, предписание процесса | только `items/` |
| `task-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин, транслит в слаге | `items/` и строки индексов; документы проекта — только как словарь | | `task-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин, транслит в слаге | `items/` и строки индекса; документы проекта — только как словарь |
Разделены они не по охвату, а **по глубине**. Язык проверяется по словам и Разделены они не по охвату, а **по глубине**. Язык проверяется по словам и
фразам, поштучно; форма записи требует понять, что задача делает, и открыть фразам, поштучно; форма записи требует понять, что задача делает. Слитый проход
цель, на которую она ссылается. Слитый проход одну половину делает дорогой, а одну половину делает дорогой, а вторую — поверхностной.
вторую — поверхностной.
Модель у обоих одна, `sonnet`, и это не отменяет разреза. Оба судят по Модель у обоих одна, `sonnet`, и это не отменяет разреза. Оба судят по
**записанному правилу** — семь пунктов формы против правил языка, — а их находка **записанному правилу** — шесть пунктов формы против правил языка, — а их находка
приходит готовой формулировкой, которую читает и отклоняет человек, а не молча приходит готовой формулировкой, которую читает и отклоняет человек, а не молча
реализует оркестратор. Ошибка здесь стоит строки чтения, и платить за неё верхней реализует оркестратор. Ошибка здесь стоит строки чтения, и платить за неё верхней
моделью не за что. моделью не за что.
@@ -670,23 +662,25 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
- **Каталог задач — `tasks/` в корне, жёстко**, и `--dir` передаётся явно всегда: - **Каталог задач — `tasks/` в корне, жёстко**, и `--dir` передаётся явно всегда:
раскладка канона одинакова во всех проектах, и искать больше нечего. Каталога раскладка канона одинакова во всех проектах, и искать больше нечего. Каталога
нет — код 3 и вопрос человеку; `init` заводит его **только** когда проект нет — код 3 и вопрос человеку; `init` заводит его **только** когда проект
действительно новый, а перевод чужой раскладки делает `av-dev:doc-canon`. действительно новый, а перевод чужой раскладки делает `av-dev:canon`.
У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф. полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
- **Версия и настройки живут в `.av-dev.toml` в корне репозитория** — версия - **Версия и настройки живут в `.av-dev.toml` в корне репозитория** — версия
ключом `version`, настройки каталога секцией `[tasks]`: `dir` — где каталог ключом `version`, настройки каталога секцией `[tasks]`: `dir` — где каталог
лежит, плюс **имена** файлов и заголовков, и последние только если отличаются лежит, `stage` — стадия проекта, плюс **имена** файлов и заголовков, и
от умолчания. Неизвестный ключ в секции — код 3 на любой команде, так что последние только если отличаются от умолчания. Неизвестный ключ в секции — код
лишнее слово останавливает работу с задачами целиком. 3 на любой команде, так что лишнее слово останавливает работу с задачами
целиком.
Дом в корне, а не внутри каталога задач, по двум причинам: настройка, лежащая Дом в корне, а не внутри каталога задач, по двум причинам: настройка, лежащая
внутри настраиваемого каталога, не смогла бы сказать, **где он**; и версия внутри настраиваемого каталога, не смогла бы сказать, **где он**; и версия
одна на весь плагин, а корень есть и у проекта без `docs/`. Прежние одна на весь плагин, а корень есть и у проекта без `docs/`. Прежние
`<каталог задач>/.tasks.json` и `docs/.docs.json` не читаются — увидев их, `<каталог задач>/.tasks.json` и `docs/.docs.json` не читаются — увидев их,
скрипт говорит «прежняя раскладка» и зовёт `upgrade`. скрипт говорит «прежняя раскладка» и зовёт `upgrade`.
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество - **Секции беклога** берутся из заголовков `##` индекса как есть; их названия —
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** дело проекта (умолчание `План` на стройке, `Ядро` / `Инфра` на доработке), а
второй список разошёлся бы с заголовками молча. количество ограничено стадией: на стройке секция одна. **В конфиге секций
нет** — второй список разошёлся бы с заголовками молча.
### Вызов из другого плагина ### Вызов из другого плагина
@@ -723,8 +717,10 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
- **Развилки — пользователю.** Через `AskUserQuestion`, с уже сформулированным - **Развилки — пользователю.** Через `AskUserQuestion`, с уже сформулированным
предварительным суждением (**рекомендация — первым вариантом**). Что выкинуть, предварительным суждением (**рекомендация — первым вариантом**). Что выкинуть,
под какую цель отнести, какая рамка разведки верна — решение пользователя. Слаг, какая рамка разведки верна, пора ли менять стадию — решение пользователя. Слаг
формулировка, порядок строк в индексе — механика, делаем сами. и формулировка — механика, делаем сами. **Порядок строк механикой не
считается** ни на одной стадии: на стройке он зависимость, на доработке
приоритет, и оба называет человек.
- **Не больше трёх вопросов за раз.** Пачка длиннее трёх тяжела для ответа; - **Не больше трёх вопросов за раз.** Пачка длиннее трёх тяжела для ответа;
решений больше — веди **несколько итераций** диалога по ≤3, а не один решений больше — веди **несколько итераций** диалога по ≤3, а не один
перегруженный запрос. Между итерациями применяй уже решённое. перегруженный запрос. Между итерациями применяй уже решённое.
+51 -44
View File
@@ -1,23 +1,23 @@
# Адаптация каталога задач # Адаптация каталога задач
Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится** Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится**
заполненный каталог задач: цели, задачи, кладбище, индексы. Операция разовая — заполненный каталог задач: задачи, кладбище, индекс. Операция разовая —
после неё проект живёт скиллами `task-track` и `task-groom`. после неё проект живёт скиллами `task-track` и `task-groom`.
**Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл **Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл
`av-dev:doc-canon`; он же зовёт этот сценарий на шаге «каталог задач», потому что `av-dev:canon`; он же зовёт этот сценарий на шаге «каталог задач», потому что
форматом задач владеет `task-track`, а не `doc-canon`. Отдельно сценарий вызывается, форматом задач владеет `task-track`, а не `canon`. Отдельно сценарий вызывается,
когда переводить надо **только** задачи. когда переводить надо **только** задачи.
Вход какой угодно: старая раскладка `av-dev-backlog` (индекс `README.md`, Вход какой угодно: старая раскладка `av-dev-backlog` (индекс `README.md`,
кладбище `CLOSED.md`, приоритеты секциями, транслитные слаги, файлы рядом с кладбище `CLOSED.md`, приоритеты секциями, транслитные слаги, файлы рядом с
индексом), `TODO.md`, россыпь заметок, раздел «планы» в `README.md`, список индексом), `TODO.md`, россыпь заметок, раздел «планы» в `README.md`, список
шагов роадмапа проекта. шагов плана проекта.
## Три правила, из которых всё следует ## Три правила, из которых всё следует
1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, как 1. **Сперва карта, потом файлы.** Человеку показывается, что найдено, в каком
разложилось по целям и **что не разложилось**, — и только после подтверждения порядке разложилось и **что не разложилось**, — и только после подтверждения
пишется хоть один файл. Это то же правило, что у интейка находок ревью: пишется хоть один файл. Это то же правило, что у интейка находок ревью:
массовое заведение записей без подтверждения — самый дорогой отказ, потому массовое заведение записей без подтверждения — самый дорогой отказ, потому
что разгребает его потом переоценка. что разгребает его потом переоценка.
@@ -38,7 +38,7 @@
tk="$CLAUDE_PLUGIN_ROOT/skills/task-track/scripts/tasks.py" tk="$CLAUDE_PLUGIN_ROOT/skills/task-track/scripts/tasks.py"
python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \ python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \
--target tasks --out tasks-adopt-plan.json # только чтение --stage build --target tasks --out tasks-adopt-plan.json # только чтение
python3 $tk adopt apply --plan tasks-adopt-plan.json \ python3 $tk adopt apply --plan tasks-adopt-plan.json \
--refs docs openspec CLAUDE.md README.md # запись --refs docs openspec CLAUDE.md README.md # запись
``` ```
@@ -53,56 +53,61 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
- **английские слаги.** Перевести `taj-brejk-pri-ravnoj-polnote` в - **английские слаги.** Перевести `taj-brejk-pri-ravnoj-polnote` в
`tie-break-equal-completeness` может только тот, кто понимает смысл. `scan` `tie-break-equal-completeness` может только тот, кто понимает смысл. `scan`
честно говорит: проверить надо **все** слаги, признаки транслита — эвристика; честно говорит: проверить надо **все** слаги, признаки транслита — эвристика;
- **цели.** Шаги роадмапа — готовые цели в **`Запланировано`** (очередь и - **стадия.** `--stage` называет, чем этот беклог будет: планом стройки или
обоснование у них уже есть); тематические скопления задач — цели в очередью правок. Машине это не выводится — она видит список пунктов, а не то,
**`Направления`** («прочность слияния», построено приложение или нет;
«журнал и пересборка»). Предлагаешь ты, назначает человек; - **порядок.** Нумерованные шаги источника `scan` сохраняет по номерам, прочие
ставит следом. Дальше порядок — твоё суждение и подтверждение человека: на
стройке это зависимость, на доработке важность;
- **что вообще не задача.** Обоснование порядка шагов, абзац прозой, заголовок - **что вообще не задача.** Обоснование порядка шагов, абзац прозой, заголовок
раздела — это не пункты беклога, и они уходят в «не разложилось» с причиной. раздела — это не пункты беклога, и они уходят в «не разложилось» с причиной.
## Порядок ## Порядок
1. **Осмотрись.** Где лежат задачи, роадмап, заметки. Каталог задач по канону — 1. **Осмотрись и назови стадию.** Где лежат задачи, план, заметки; построено
всегда `tasks`. Секции беклога (`--sections`) — по умолчанию приложение или строится. Каталог задач по канону — всегда `tasks`. Секции
`Ядро,Инфра`; если у проекта деление другое по существу, оно называется беклога (`--sections`) — на стройке ровно одна (умолчание `План`), на
здесь, а не подгоняется под умолчание, и становится **заголовками `##` доработке сколько нужно (умолчание `Ядро,Инфра`); если у проекта деление
индекса** — их единственным домом. В `.av-dev.toml` секции не пишутся: там другое по существу, оно называется здесь, а не подгоняется под умолчание, и
версия формата и имена частей, а второй список секций разошёлся бы с становится **заголовками `##` индекса** — их единственным домом. В
заголовками молча. `.av-dev.toml` секции не пишутся: там версия, стадия и имена частей, а второй
список секций разошёлся бы с заголовками молча.
2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два 2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два
прохода дадут два несогласованных состояния. прохода дадут два несогласованных состояния.
3. **Заполни карту**: `slug` (английский), `section`, `goal` у каждой записи; 3. **Заполни карту**: `slug` (английский), `type` и `section` у каждой записи, и
список `goals` — из шагов роадмапа и из тем. Закрытый шаг целью не **порядок `items`** — он уедет в индекс как есть. Пункт, помеченный
заводится. Пустой `goal` законен у `fix`, `chore` и `research` — они служат закрытым, не переносится вовсе.
работоспособности, а не направлению; у `feature` цель обязательна.
4. **Покажи человеку карту** через `AskUserQuestion`, ≤3 вопроса за итерацию, 4. **Покажи человеку карту** через `AskUserQuestion`, ≤3 вопроса за итерацию,
рекомендация первым вариантом. Показывается: сколько записей, предлагаемые рекомендация первым вариантом. Показывается: сколько записей, предлагаемый
цели (порядок и темы) с обоснованием, спорные отнесения, список «не порядок с обоснованием, спорные отнесения, список «не разложилось». Слаги не
разложилось». Массовые механические решения (слаги, порядок строк) не выносятся — это механика; **порядок выносится всегда**, потому что механикой
выносятся — это механика. он не является ни на одной стадии.
5. **`adopt apply`.** `--refs` перечисляет **всё**, где могут стоять ссылки на 5. **`adopt apply`.** `--refs` перечисляет **всё**, где могут стоять ссылки на
слаги: документация, архив изменений, `CLAUDE.md`, `README.md`. Скрипт слаги: документация, архив изменений, `CLAUDE.md`, `README.md`. Скрипт
посчитает и покажет, сколько ссылок поправлено и по каким слагам. посчитает и покажет, сколько ссылок поправлено и по каким слагам.
6. **`tasks.py check`** и доклад. 6. **`tasks.py check`** и доклад.
`apply` отказывается писать поверх живого каталога и проверяет карту целиком `apply` отказывается писать поверх живого каталога и проверяет карту целиком
**до** первой записи: неверная секция, дубль слага, цель, которой нет в карте — **до** первой записи: неверная секция, дубль слага, неназванный тип, две секции
всё это отказ до того, как на диске появился хотя бы один файл. при стадии `build`всё это отказ до того, как на диске появился хотя бы один
файл.
## Переходное состояние — объявляется, а не заминается ## Переходное состояние — объявляется, а не заминается
Сразу после адаптации задачи в большинстве своём **не готовы к взятию**: у них Сразу после адаптации задачи в большинстве своём **не готовы к взятию**: у них
нет критериев приёмки, а у части может не быть цели. Это нормально, но обязано нет критериев приёмки. Это нормально, но обязано быть названо, иначе следующий
быть названо, иначе следующий агент примет пустой беклог за поломку. агент примет пустой беклог за поломку.
`apply` печатает состояние по факту: сколько задач без цели (это **ошибки** `apply` печатает состояние по факту: сколько задач не собрало разделы своего типа
`check`) и сколько не собрало разделы своего типа (для `check` это не ошибка, а (для `check` это не ошибка, а строка здоровья, но `ready` такую задачу не
строка здоровья, но `ready` такую задачу не пропустит). Закрывается это пропустит). Закрывается это **порциями по 5–8 задач**: превратить «готово,
**порциями груминга** — скилл когда» в критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы». На
`groom`, 5–8 задач за порцию: проставить цели, превратить «готово, когда» в доработке это груминг (скилл `av-dev:task-groom`), на стройке — гигиена полей
критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы». Там же этого скилла: груминга там нет.
беклогу впервые назначается **порядок**: после адаптации его нет вовсе, а
очередь и есть то, ради чего каталог заводят. **Порядок строк проверяется глазами отдельно.** На стройке он выведен из
нумерации источника, и там, где её не было, он случаен. На доработке машина
важности не знает вовсе — очередь расставляется первым же грумингом.
Готовность к первой задаче — не «`check` зелёный», а «`ready` пропускает хотя бы Готовность к первой задаче — не «`check` зелёный», а «`ready` пропускает хотя бы
верхние строки очереди». верхние строки очереди».
@@ -113,18 +118,20 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда. дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда.
- **Не переписывает подписи ссылок.** `[docs/backlog](tasks/BACKLOG.md)` - **Не переписывает подписи ссылок.** `[docs/backlog](tasks/BACKLOG.md)`
цель поправлена, текст остался; это правится глазами, и таких мест немного. цель поправлена, текст остался; это правится глазами, и таких мест немного.
- **Не сочиняет критерии приёмки и не придумывает цели**, которых в материале - **Не сочиняет критерии приёмки** и не додумывает пользы, которой в материале
нет. Придуманная цель хуже отсутствующей: под неё заведут задачи. нет.
- **Не выводит стадию.** Список пунктов одинаково выглядит и планом стройки, и
очередью правок; отвечает `--stage`, а называет его человек.
- **Не трогает историю.** В коммитах старые слаги остаются, и это нормально. - **Не трогает историю.** В коммитах старые слаги остаются, и это нормально.
## Доклад ## Доклад
- Источники и что в каждом распознано (раскладка, индекс, кладбище, секции). - Источники и что в каждом распознано (раскладка, индекс, кладбище, секции).
- Сколько записей перенесено, сколько целей заведено (порядок / темы) и откуда - Стадия и сколько записей перенесено; откуда взялся порядок (нумерация
каждая выведена. источника или суждение).
- **Переименования**: сколько слагов, сколько ссылок поправлено и в скольких - **Переименования**: сколько слагов, сколько ссылок поправлено и в скольких
файлах — числом, а не «поправлены ссылки». файлах — числом, а не «поправлены ссылки».
- **Не разложилось**: поимённо, с причиной. - **Не разложилось**: поимённо, с причиной.
- Переходное состояние: сколько задач без цели, сколько без критериев, чем и за - Переходное состояние: сколько задач без критериев, чем и за сколько порций
сколько порций закрывается. закрывается.
- `tasks.py check` — результат строкой. - `tasks.py check` — результат строкой.
@@ -50,17 +50,12 @@
заведённое и уже выкинутое. Нашлось среди живых — дописываем находку в заведённое и уже выкинутое. Нашлось среди живых — дописываем находку в
существующий файл. Нашлось в `REJECTED.md` — это сигнал: причина отказа могла существующий файл. Нашлось в `REJECTED.md` — это сигнал: причина отказа могла
устареть, выноси пользователю, а не заводи молча заново. устареть, выноси пользователю, а не заводи молча заново.
4. **Разложи по целям — там, где цель нужна.** Большинство находок ревью это 4. **Проставь типы.** Большинство находок ревью это `fix` и `chore`. Находка,
`fix` и `chore`, и **цель им не требуется**: они служат работоспособности, а оказавшаяся **новой возможностью** (`feature`), — отдельный случай: нашлось
не направлению. Придуманная им цель — поведение, которого никто не заказывал, и решение тут не «завести задачу», а
ровно то враньё, от которого спасает тип. «заказать или убрать». Выноси такую пользователю отдельно от прочих.
Цель обязательна у находки, которая оказалась **новой возможностью**
(`feature`): нашлось поведение, которого никто не заказывал, и его надо
либо заказать целью, либо убрать. Подходящей цели нет — заведи её
(`add --type goal --section Направления`) в том же проходе.
5. **Покажи карту до создания файлов.** Кластер → задача / сырьё / строка в 5. **Покажи карту до создания файлов.** Кластер → задача / сырьё / строка в
пакетный файл / уже заведено / отброшено, и под какую цель — пачкой через пакетный файл / уже заведено / отброшено — пачкой через
`AskUserQuestion`. Это тот же барьер, что и «три кандидата» в интейке из `AskUserQuestion`. Это тот же барьер, что и «три кандидата» в интейке из
диалога: массовое заведение файлов без подтверждения — ровно тот отказ, ради диалога: массовое заведение файлов без подтверждения — ровно тот отказ, ради
которого интейк из ревью и выделен. Дешёвая мелочь по явному согласию может которого интейк из ревью и выделен. Дешёвая мелочь по явному согласию может
@@ -88,8 +83,16 @@
[скилле груминга](../../task-groom/SKILL.md#приоритет-как-его-расставляют), и [скилле груминга](../../task-groom/SKILL.md#приоритет-как-его-расставляют), и
серьёзность попадает ровно в один из них. серьёзность попадает ровно в один из них.
- **тяжёлая находка со свидетельством о сломанном сейчас** → задача под ту цель, **Всё это — про доработку.** На стройке порядок строк значит зависимость, и
которой она угрожает, и **первой строкой секции**: `move <слаг> --first `--first` там означает «ни от чего не зависит», а не «важнее всех»: находка,
поднятая наверх, встанет перед собственной зависимостью. Место находке на стройке
называет зависимость — `move --after <шаг, после которого её можно делать>`, — а
серьёзность идёт **причиной в мете** и разбирается ближайшим пересмотром плана
(`task-track`, «Пересмотр плана стройки»). Груминга там нет, и откладывать «до
него» некуда.
- **тяжёлая находка со свидетельством о сломанном сейчас** → задача
**первой строкой секции**: `move <слаг> --first
--reason «сломано сейчас: …»`. Это довод «что сломано сейчас» из перечня --reason «сломано сейчас: …»`. Это довод «что сломано сейчас» из перечня
груминга — единственный, который не требует сравнения с соседями по очереди, груминга — единственный, который не требует сравнения с соседями по очереди,
потому что сломанное дорожает само. Позицию всё равно назначает человек, и потому что сломанное дорожает само. Позицию всё равно назначает человек, и
@@ -124,7 +127,7 @@
## Доклад ## Доклад
- Источник (какое ревью/аудит, сколько находок на входе). - Источник (какое ревью/аудит, сколько находок на входе).
- Свёрнуто в задачи: N кластеров из M находок, со слагами, целями и тегом партии. - Свёрнуто в задачи: N кластеров из M находок, со слагами и тегом партии.
- Что не заведено и почему: починено инлайн, уже заведено, стало сырьём, ушло в - Что не заведено и почему: починено инлайн, уже заведено, стало сырьём, ушло в
`REJECTED.md`. `REJECTED.md`.
- Поимённая сверка: находок на входе N, исход есть у N. - Поимённая сверка: находок на входе N, исход есть у N.
+29 -32
View File
@@ -8,14 +8,19 @@
Задачу можно дробить, только если части удовлетворяют **обоим** условиям: Задачу можно дробить, только если части удовлетворяют **обоим** условиям:
1. **Мерджатся независимо.** Часть Б не требует, чтобы часть А была уже влита. 1. **Каждая мерджится сама по себе.** Часть, после которой дерево не собирается
Есть порядок «сперва А, потом Б, иначе не собрать» → это не декомпозиция, а или поведение сломано до прихода соседней, — не часть, а половина.
план реализации: шаги остаются **внутри одного файла**.
2. **Каждая — самостоятельный шаг.** Часть, осмысленная только в комплекте с 2. **Каждая — самостоятельный шаг.** Часть, осмысленная только в комплекте с
другой, — не задача. Проверяй тестом «готова к взятию» (task-format): какую другой, — не задача. Проверяй тестом «готова к взятию» (task-format): свои
строку «Завершения» цели двигает **именно эта часть** и какие у неё критерии приёмки у неё есть или нет.
собственные критерии приёмки. У операционных частей (`fix`, `chore`,
`research`) цели может не быть — тогда достаточно собственных критериев. **Порядок между частями законен на стройке и подозрителен на доработке**, и это
единственное, что стадия здесь меняет. Беклог стройки **весь** состоит из
упорядоченных зависимостью шагов: «сперва А, потом Б» — не повод не дробить, а
описание того, как этот список устроен, и части просто встают подряд. На
доработке правки независимы, и обнаруженный порядок «иначе не собрать» чаще
всего значит, что перед тобой не декомпозиция, а план реализации: шаги остаются
**внутри одного файла**.
Не проходит хотя бы одно — **не дроби**. Ложная декомпозиция плодит файлы, Не проходит хотя бы одно — **не дроби**. Ложная декомпозиция плодит файлы,
которые нельзя взять поодиночке, и переоценка потом склеивает их обратно. которые нельзя взять поодиночке, и переоценка потом склеивает их обратно.
@@ -45,37 +50,29 @@
гигиена полей снимает. Шов пользуется меткой как **признаком**, что в задаче гигиена полей снимает. Шов пользуется меткой как **признаком**, что в задаче
две разнородные работы; решение о метке остаётся за конвейером. две разнородные работы; решение о метке остаётся за конвейером.
**Цель наследуется.** Все части несут `goal:` родителя: декомпозиция не меняет
того, чему работа служит. Если у части цель другая — это признак, что дробили не
по той границе, либо что часть вообще из другой работы.
## Что делать с родителем ## Что делать с родителем
После разделения родитель **не остаётся** третьей висящей строкой: После разделения родитель **не остаётся** третьей висящей строкой:
- части полностью замещают его → `close <slug> --reason "разложена на a, b"`. части полностью замещают его → `close <slug> --reason "разложена на a, b"`.
`REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись: `REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись:
через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на
наследников, а не археологией git; наследников, а не археологией git.
- родитель осмыслен как **возможность**, а не как шаг → это цель, и **строка
переезжает**: `edit <slug> --type goal --section <часть роадмапа>` снимает её с
`BACKLOG.md` и вставляет в `ROADMAP.md`. Файл в `items/` при этом не двигается —
он и есть запись. Части получают `--goal <слаг родителя>`, а закрывать родителя
нечем и незачем: он не выкинут, он стал целью.
**Промежуточного зонтика между целью и задачей нет.** Тип `epic` упразднён: **Зонтика над частями нет никакого.** Тип `epic` упразднён, цель, игравшая его
роль зонтика играет цель, а слишком крупный шаг дробится на шаги помельче под роль после него, — тоже. Если частям нужен общий заголовок, у них общее место в
той же целью. Если частям нужен общий заголовок — значит у них общая списке: они встают подряд, и соседство и есть тот ответ, ради которого заводили
возможность, и её надо назвать целью, а не заводить временный тип. зонтик.
## Когда декомпозиция случается посреди работы ## Когда декомпозиция случается посреди работы
Задача, которая **оказалась крупнее задачи**, распознаётся до того, как под неё Задача, которая **оказалась крупнее задачи**, распознаётся до того, как под неё
заведено предложение об изменении: иначе его придётся выбрасывать. Она выходит заведено предложение об изменении: иначе его придётся выбрасывать. Она выходит
из работы на декомпозицию, а её строка возвращается в беклог с причиной из работы на декомпозицию, а её строка возвращается в беклог с причиной
(`move … --reason "крупнее задачи"`). Части заводятся сразу под той же целью, и (`move … --reason "крупнее задачи"`). **Место в списке частям назначает
**место в очереди им назначает человек**: машина поставит их в конец секции, а человек**: машина поставит их в конец секции, а на стройке место наследуется от
крупная задача редко распадается на что-то менее срочное, чем была сама. родителя (`move --after`), да и на доработке крупная задача редко распадается на
что-то менее срочное, чем была сама.
## Мозговой штурм сырья ## Мозговой штурм сырья
@@ -96,9 +93,9 @@ Applicative-штурм («перечисли задачи, следующие и
applicative. applicative.
2. **Вынеси формы пользователю** через `AskUserQuestion` с компромиссами. Рамку 2. **Вынеси формы пользователю** через `AskUserQuestion` с компромиссами. Рамку
выбирает он: это продуктовое решение, не механика. выбирает он: это продуктовое решение, не механика.
3. **Назови цель.** Выбранная форма служит цели — существующей или новой. Идея, 3. **Назови пользу.** Выбранная форма отвечает на «что станет наблюдаемо иначе».
для которой цель не находится, скорее всего уезжает в `REJECTED.md`, а не Идея, для которой такого ответа не находится, скорее всего уезжает в
заводится задачей. `REJECTED.md`, а не заводится задачей.
4. **Только выбранную форму** дроби по тесту декомпозиции выше и проставь 4. **Только выбранную форму** дроби по тесту декомпозиции выше и проставь
критерии приёмки: без них наследники останутся идеями под другим именем. критерии приёмки: без них наследники останутся идеями под другим именем.
@@ -109,8 +106,8 @@ Applicative-штурм («перечисли задачи, следующие и
## Доклад ## Доклад
- Идея/задача на входе, выбранная рамка (для штурма), задачи-наследники со - Идея/задача на входе, выбранная рамка (для штурма), задачи-наследники со
слагами, целями и секциями. слагами, секциями и местом в списке.
- Судьба родителя: удалён / стал целью / выкинут с причиной. - Судьба родителя: удалён / выкинут с причиной.
- `tasks.py check` после правок. - `tasks.py check` после правок.
- Границы покрытия: какие постановки рассмотрены и какие сознательно отброшены — - Границы покрытия: какие постановки рассмотрены и какие сознательно отброшены —
чтобы штурм не пришлось повторять с нуля. чтобы штурм не пришлось повторять с нуля.
@@ -14,7 +14,6 @@
| Обязательные разделы | `Затрагивает`, `Критерии приёмки` | | Обязательные разделы | `Затрагивает`, `Критерии приёмки` |
| Допустимые сверх того | `Рамки`, `Вопросы` | | Допустимые сверх того | `Рамки`, `Вопросы` |
| Поле места | **Категория** — полка домена беклога | | Поле места | **Категория** — полка домена беклога |
| Цель (`goal:<слаг>`) | нет: цель — это возможность, а здесь её не появляется |
| Индекс | `BACKLOG.md` | | Индекс | `BACKLOG.md` |
| Берётся в работу | да | | Берётся в работу | да |
@@ -43,7 +42,7 @@
## Алгоритм ## Алгоритм
1. **Проверить, что поведение не меняется.** Меняется — это `feature` или `fix`, 1. **Проверить, что поведение не меняется.** Меняется — это `feature` или `fix`,
и у неё другие требования (цель, воспроизведение). и у последнего другие требования (воспроизведение).
2. **Назвать, что перестанет мешать** — одной фразой, адресуясь разработчику. 2. **Назвать, что перестанет мешать** — одной фразой, адресуясь разработчику.
«Прибраться в модуле X» — не ответ: непонятно, что изменится. «Прибраться в модуле X» — не ответ: непонятно, что изменится.
3. **Назвать границы** в `Затрагивает`. У обслуживания они часто не в коде: 3. **Назвать границы** в `Затрагивает`. У обслуживания они часто не в коде:
@@ -55,9 +54,6 @@
5. **Проверить, что это не «заодно».** Обслуживание любит склеиваться в пачку 5. **Проверить, что это не «заодно».** Обслуживание любит склеиваться в пачку
(«обновить зависимости и переписать сборку и убрать мёртвый код»). Не («обновить зависимости и переписать сборку и убрать мёртвый код»). Не
мерджится порознь — это несколько задач ([split.md](split.md)). мерджится порознь — это несколько задач ([split.md](split.md)).
6. **Цель не проставлять.** `chore` служит работоспособности, а не направлению.
Работа по сопровождению проекта при этом видна в роадмапе — секцией
`Сопровождение`, но целью не становится.
## Кто такую задачу решает ## Кто такую задачу решает
@@ -15,18 +15,13 @@
| Обязательные разделы | `Затрагивает`, `Критерии приёмки` | | Обязательные разделы | `Затрагивает`, `Критерии приёмки` |
| Допустимые сверх того | `Рамки`, `Вопросы` | | Допустимые сверх того | `Рамки`, `Вопросы` |
| Поле места | **Категория** — полка домена беклога | | Поле места | **Категория** — полка домена беклога |
| Цель (`goal:<слаг>`) | **обязательна** |
| Индекс | `BACKLOG.md` | | Индекс | `BACKLOG.md` |
| Берётся в работу | да | | Берётся в работу | да |
**Цель обязательна, и это единственный тип, у которого так.** Новая возможность
и есть содержание цели: подходящей нет — либо она заводится, либо перед тобой не
`feature`. `ready` без цели откажет.
## Алгоритм ## Алгоритм
1. **Найти цель или завести её.** Задача без цели, названная функцией, — самый 1. **Проверить, что возможность и правда новая.** Поведение расходится с уже
частый способ пронести в беклог работу, которой никто не заказывал. заявленным — это `fix`, а не `feature`, и требования у него другие.
2. **Назвать границы** в разделе `Затрагивает`: эндпоинт или команда, таблица и 2. **Назвать границы** в разделе `Затрагивает`: эндпоинт или команда, таблица и
миграция, формат на диске, публичный тип пакета, внешний сервис. Названы миграция, формат на диске, публичный тип пакета, внешний сервис. Названы
**границы, а не замысел**: «переписать хранилище на новый драйвер» — замысел, **границы, а не замысел**: «переписать хранилище на новый драйвер» — замысел,
@@ -35,13 +30,12 @@
3. **Написать критерии приёмки** — 2–5 проверяемых утверждений списком, у 3. **Написать критерии приёмки** — 2–5 проверяемых утверждений списком, у
каждого назван оракул. Не «работает корректно», а «повторный прогон даёт тот каждого назван оракул. Не «работает корректно», а «повторный прогон даёт тот
же отпечаток — оракул: команда сверки». же отпечаток — оракул: команда сверки».
4. **Сказать, какую строку «Завершения» цели задача двигает.** Одной строкой в 4. **Поставить её на место в списке.** На стройке место называет зависимость:
теле. Это защита от задачи «отрефакторить X»: она проваливается не потому, `move <слаг> --after <шаг, без которого нельзя>`. На доработке место в
что невидима снаружи, а потому, что не находит строки, к которой относится. очереди назначает груминг, и конец списка законен.
5. **Проверить, что задача одна.** Отвечается всё, но задача не делается одним 5. **Проверить, что задача одна.** Отвечается всё, но задача не делается одним
заходом и не мерджится целиком — это несколько задач под одной целью, дроби заходом и не мерджится целиком — это несколько задач, дроби сразу
сразу ([split.md](split.md)). Промежуточного зонтика между целью и задачей ([split.md](split.md)) и ставь их в списке подряд.
нет.
6. **Реализация** — дело конвейера проекта, не этого скилла. Закрывается 6. **Реализация** — дело конвейера проекта, не этого скилла. Закрывается
`close <слаг> --implemented`: файл и строка удаляются, суть переезжает в `close <слаг> --implemented`: файл и строка удаляются, суть переезжает в
`openspec/specs/` и документацию. `openspec/specs/` и документацию.
@@ -49,8 +43,8 @@
## Что видит машина, а что человек ## Что видит машина, а что человек
Схему типа судит `ready` на входе в работу: **наличие непустого** раздела Схему типа судит `ready` на входе в работу: **наличие непустого** раздела
`Затрагивает`, **число** критериев (меньше двух — отказ, больше пяти — `Затрагивает` и **число** критериев (меньше двух — отказ, больше пяти —
замечание) и цель. Наличие оракула проверяется **эвристикой** — словом «оракул» замечание). Наличие оракула проверяется **эвристикой** — словом «оракул»
в пункте. `check` этого поимённо не говорит, а считает строкой здоровья в пункте. `check` этого поимённо не говорит, а считает строкой здоровья
(`SKILL.md`, «Что механизировано, а что нет»). (`SKILL.md`, «Что механизировано, а что нет»).
@@ -16,7 +16,6 @@
| Обязательные разделы | **`Воспроизведение`**, `Затрагивает`, `Критерии приёмки` | | Обязательные разделы | **`Воспроизведение`**, `Затрагивает`, `Критерии приёмки` |
| Допустимые сверх того | `Рамки`, `Вопросы` | | Допустимые сверх того | `Рамки`, `Вопросы` |
| Поле места | **Категория** — полка домена беклога | | Поле места | **Категория** — полка домена беклога |
| Цель (`goal:<слаг>`) | необязательна |
| Индекс | `BACKLOG.md` | | Индекс | `BACKLOG.md` |
| Берётся в работу | да | | Берётся в работу | да |
@@ -54,9 +53,7 @@
почти всегда есть парный критерий: **прежнее поведение не сломалось** почти всегда есть парный критерий: **прежнее поведение не сломалось**
(«ввод `а1` принимается по-прежнему»). Без него починка чинит одно и ломает («ввод `а1` принимается по-прежнему»). Без него починка чинит одно и ломает
соседнее. соседнее.
6. **Цель не выдумывать.** `fix` служит работоспособности, а не направлению. 6. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман
Придуманная цель — то же враньё, от которого спасает тип.
7. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман
ревью». Проскочившие — проверочный набор для калибровки конвейера; пойманные с ревью». Проскочившие — проверочный набор для калибровки конвейера; пойманные с
оракулом — лучшая опора для прохода ревью: проектные, воспроизводимые, оракулом — лучшая опора для прохода ревью: проектные, воспроизводимые,
однажды оказавшиеся правдой. однажды оказавшиеся правдой.
@@ -1,4 +1,4 @@
# Формат записей и индексов # Формат записей и индекса
Заголовок, мета-блок и строку индекса ставит `tasks.py add` — руками их не Заголовок, мета-блок и строку индекса ставит `tasks.py add` — руками их не
пишут. Этот файл описывает **общую форму** любой записи и то, что проверяет пишут. Этот файл описывает **общую форму** любой записи и то, что проверяет
@@ -9,7 +9,6 @@
| Тип | Файл | Одной строкой | | Тип | Файл | Одной строкой |
| --- | --- | --- | | --- | --- | --- |
| 🎯 `goal` | [task-goal.md](task-goal.md) | возможность приложения |
| ✨ `feature` | [task-feature.md](task-feature.md) | снаружи появляется то, чего не было | | ✨ `feature` | [task-feature.md](task-feature.md) | снаружи появляется то, чего не было |
| 🐞 `fix` | [task-fix.md](task-fix.md) | поведение расходится с заявленным | | 🐞 `fix` | [task-fix.md](task-fix.md) | поведение расходится с заявленным |
| 🧹 `chore` | [task-chore.md](task-chore.md) | обслуживание, поведение не меняется | | 🧹 `chore` | [task-chore.md](task-chore.md) | обслуживание, поведение не меняется |
@@ -25,7 +24,6 @@
- **Тип:** fix - **Тип:** fix
- **Категория:** Ядро — вернулась из работы: остаток писал нерешённое в журнал - **Категория:** Ядро — вернулась из работы: остаток писал нерешённое в журнал
- **Зачем:** ввод «а1б2» ходит в a1 — игрок не видит, что ошибся, и винит игру - **Зачем:** ввод «а1б2» ходит в a1 — игрок не видит, что ошибся, и винит игру
- **Теги:** goal:merge-robustness
Разбор хода читает первые два символа и молча выбрасывает остаток строки. Разбор хода читает первые два символа и молча выбрасывает остаток строки.
@@ -55,8 +53,8 @@
**эмодзи типа**, и она **производна**: её ставит `add` и чинит `check --fix` **эмодзи типа**, и она **производна**: её ставит `add` и чинит `check --fix`
по полю меты. Второго дома у типа нет — эмодзи это его отображение, как по полю меты. Второго дома у типа нет — эмодзи это его отображение, как
строка индекса это отображение файла. строка индекса это отображение файла.
- **Форма заголовка — по типу.** Цель отвечает на «что приложение будет уметь»; - **Форма заголовка — по типу.** `feature`, `fix` и `chore` отвечают на «что
`feature`, `fix` и `chore` — на «что нужно сделать», глаголом в неопределённой нужно сделать», глаголом в неопределённой
форме, перед ним допускается «не»; `research` называет предмет разведки и форме, перед ним допускается «не»; `research` называет предмет разведки и
формы действия **не несёт намеренно**. Почему так — SKILL.md, «Как написана формы действия **не несёт намеренно**. Почему так — SKILL.md, «Как написана
задача». `check` считает заголовки не в форме действия и печатает число в задача». `check` считает заголовки не в форме действия и печатает число в
@@ -67,9 +65,8 @@
теги необязательны. Нераспознанные поля сохраняются: скрипт правит свои и не теги необязательны. Нераспознанные поля сохраняются: скрипт правит свои и не
трогает чужие. трогает чужие.
- **Тип — первым полем.** Он решает, что у записи вообще может быть: какие - **Тип — первым полем.** Он решает, что у записи вообще может быть: какие
разделы обязательны, нужна ли цель, берётся ли она в работу, — и читается разделы обязательны и берётся ли она в работу, — и читается раньше всего
раньше всего остального. Словарь **закрыт**: `goal` | `feature` | `fix` | остального. Словарь **закрыт**: `feature` | `fix` | `chore` | `research`. Не подходит ни один — это сигнал, что в записи их два и
`chore` | `research`. Не подходит ни один — это сигнал, что в записи их два и
её надо разделить. её надо разделить.
- **«Зачем» отвечает на «зачем нужна эта задача»** — состояние, остаток, боль. - **«Зачем» отвечает на «зачем нужна эта задача»** — состояние, остаток, боль.
Не пересказ задачи: пересказ уже есть по ссылке. Живёт здесь, а не только в Не пересказ задачи: пересказ уже есть по ссылке. Живёт здесь, а не только в
@@ -86,19 +83,16 @@
Тело — не план реализации и не спецификация: принятое и реализованное переезжает Тело — не план реализации и не спецификация: принятое и реализованное переезжает
в документацию проекта, а файл задачи удаляется. в документацию проекта, а файл задачи удаляется.
### Поле места: «Категория» и «Секция» ### Поле места: «Категория»
Поле называет, **где числится строка**, и имя у него **зависит от типа**: Поле называет **секцию беклога, в которой числится строка** — полку домена
(`Ядро`, `Инфра`, …), куда задачу положили и куда вернут, если она уйдёт в работу
и вернётся. **На стройке секция одна**, и поле называет её же: различать ей
нечего, но производность от заголовка индекса сохраняется и там.
| Тип | Поле | Значения | Что это | Прежнее имя поля — **«Секция»**: так оно называлось у целей, указывая на часть
| --- | --- | --- | --- | роадмапа. Разбор его по-прежнему принимает, `check` называет дрейфом, `check
| `goal` | **Секция** | `Запланировано`, `Направления`, `Сопровождение` | часть роадмапа: состояние очереди | --fix` переименовывает.
| прочие | **Категория** | секции беклога проекта (`Ядро`, `Инфра`, …) | полка домена, на которой задача лежит |
Разные имена потому, что это **разные вещи**. У задачи это полка: куда её
положили и куда вернут, если она уйдёт в работу и вернётся. У цели оно называет
не полку, а место в очереди работ. Одно имя на два смысла их и смешивало; `check` называет
несовпадение дрейфом, `check --fix` переименовывает.
Имя самого места принадлежит **заголовку индекса** — файл на него лишь Имя самого места принадлежит **заголовку индекса** — файл на него лишь
ссылается, и принадлежность сверяется по нижнему регистру. ссылается, и принадлежность сверяется по нижнему регистру.
@@ -111,14 +105,18 @@
| --- | --- | | --- | --- |
| префикс `[goal]` / `[idea]` в H1 | поле **Тип** + эмодзи в H1; `[idea]``research` | | префикс `[goal]` / `[idea]` в H1 | поле **Тип** + эмодзи в H1; `[idea]``research` |
| тег `kind:<род>` | поле **Тип** (род работы стал типом) | | тег `kind:<род>` | поле **Тип** (род работы стал типом) |
| поле **Секция** у задачи | поле **Категория** | | поле **Секция** | поле **Категория** |
| теги `goal:<слаг>` и `decomposed` | сняты: целей больше нет |
| поле **Хук** | поле **Зачем** | | поле **Хук** | поле **Зачем** |
| мета одной строкой через `·` | мета списком, поле на строку | | мета одной строкой через `·` | мета списком, поле на строку |
Единственное, чего `--fix` не делает сам, — **проставить тип записи, у которой Чего `--fix` не делает сам — **решает за человека, каким быть типу**. Случаев
его неоткуда взять**: `feature` от `chore` машина не отличает, и подставленное три, и все три уезжают пометкой `НЕОДНОЗНАЧНО`: тип, которого неоткуда взять
наугад значение врало бы ровно там, где по нему принимают решение. Такие записи (`feature` от `chore` машина не отличает); тип вне словаря; и запись типа `goal`
он называет поимённо пометкой `НЕОДНОЗНАЧНО`. — целей больше нет, а во что превращается эта, в задачу или в ничто, машина не
знает. Подставленное наугад значение врало бы ровно там, где по нему принимают
решение. Мёртвые теги и имя поля места при этом снимаются у **любой** записи,
включая ту, чей тип остался неразобранным.
### Затрагивает ### Затрагивает
@@ -147,8 +145,8 @@
забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии: забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии:
оценивать нечем. оценивать нечем.
**У `goal` и `research` раздела нет** — у первой границы называют её задачи, у **У `research` раздела нет** — её границы становятся известны, когда из разведки
второй они становятся известны, когда из разведки родятся задачи. родятся задачи.
### Критерии приёмки ### Критерии приёмки
@@ -166,8 +164,7 @@
что проверено больше проверенного, хуже, чем не проверять вовсе. что проверено больше проверенного, хуже, чем не проверять вовсе.
**У `research` критериев нет** — её приёмка это записанный ответ, и описывается **У `research` критериев нет** — её приёмка это записанный ответ, и описывается
она разделами «Вопрос» и «Куда ляжет ответ». **У `goal` их заменяет она разделами «Вопрос» и «Куда ляжет ответ».
«Завершение».**
**Критерии — пол, но расхождение с ними есть дефект критериев.** Если приёмщик **Критерии — пол, но расхождение с ними есть дефект критериев.** Если приёмщик
видит, что критерии закрыты, а суть задачи не достигнута, он **правит критерии и видит, что критерии закрыты, а суть задачи не достигнута, он **правит критерии и
@@ -210,44 +207,6 @@
на снятый тег при непустом разделе посоветует тег вернуть. Снять тег, не на снятый тег при непустом разделе посоветует тег вернуть. Снять тег, не
опустошив раздел, — значит закольцевать себя между двумя советами. опустошив раздел, — значит закольцевать себя между двумя советами.
## Файл цели
Форма та же, разделы и алгоритм — [task-goal.md](task-goal.md).
```markdown
# 🎯 Исход слияния не зависит от порядка доставки
- **Тип:** goal
- **Секция:** Направления
- **Теги:** decomposed
Ради чего: точки из разных доставок сходятся в один часовой объект, и сегодня
исход столкновения зависит от порядка доставки, а не от содержания.
## Завершение
- повторная доставка тех же точек в другом порядке даёт то же состояние;
- накопительная метрика за сутки не уменьшается после повторной доставки;
- в логе видно, какая из двух точек выиграла и почему.
```
- **Задачи цели здесь не перечисляются.** Перечень даёт
`tasks.py list --goal <слаг>`; хранимый список стал бы третьим индексом и
поехал бы на первой же закрытой задаче.
- **Тег `decomposed`** отличает «цель ещё не разобрана» от «все её задачи
закрыты» — два состояния, у которых снаружи один и тот же признак: задач нет.
Пометка именно **тегом**, а не строкой в теле: только так она проверяется.
`check` напоминает о нём у цели без задач замечанием — неразобранная цель
законна и зелёного прогона не ломает; `check --fix` сам ставит его цели, у
которой задачи есть, а цель с тегом и без задач — прямое приглашение закрыть.
- Цель живёт в `ROADMAP.md` и **никогда** — в `BACKLOG.md`.
- **Достигнутая цель не исчезает.** `close <слаг> --implemented` удаляет файл и
переносит строку в секцию `Готово` с датой:
`- 2026-08-04 \`merge-order\` — Исход слияния не зависит от порядка доставки. …`
Ссылки на файл в ней нет — файл удалён, а битая ссылка это ошибка `check`.
Поведение живёт в спеках проекта; роадмап отвечает, **когда и в каком порядке**
оно появилось.
## Слаг ## Слаг
Латиница и цифры, kebab-case, без ведущих, хвостовых и двойных дефисов Латиница и цифры, kebab-case, без ведущих, хвостовых и двойных дефисов
@@ -261,9 +220,9 @@
проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки, проходом по всем местам, где слаг упомянут, иначе останутся битые ссылки,
которых никто не проверяет. которых никто не проверяет.
## Индексы ## Индекс
Строка везде одной формы: Строка одной формы:
```markdown ```markdown
- [🐞 Заголовок дословно](items/slug.md) — зачем - [🐞 Заголовок дословно](items/slug.md) — зачем
@@ -276,52 +235,47 @@
| Файл | Что отвечает | Секции | | Файл | Что отвечает | Секции |
| --- | --- | --- | | --- | --- | --- |
| `ROADMAP.md` | что приложение уже умеет и чего ещё не умеет | канонические и в этом порядке: `Запланировано`, `Направления`, `Сопровождение`, `Готово` (англ. `Planned`, `Directions`, `Operations`, `Done`) | | `BACKLOG.md` | что **можно взять**, в значимом порядке | называет проект; на стройке ровно одна (умолчание `План`), на доработке сколько нужно (умолчание `Ядро`/`Инфра`) |
| `BACKLOG.md` | что **можно взять** — только задачи, **в порядке очереди** | категории проекта (по умолчанию Ядро/Инфра) |
| `REJECTED.md` | что ушло без реализации и почему | — | | `REJECTED.md` | что ушло без реализации и почему | — |
Секции — **единственные заголовки `##` в индексе**: любой другой `##` в Секции — **единственные заголовки `##` в индексе**: любой другой `##` в
преамбуле проверка сочтёт секцией. преамбуле проверка сочтёт секцией.
**Порядок строк внутри секции беклога значим: это очередь.** Первая строка — то, **Порядок строк внутри секции значим, и стадия решает, что он значит:** на
что делают следующим; назначает порядок человек на груминге, и двигают его стройке зависимость, на доработке важность (SKILL.md, «Две стадии»). Назначает
`move --after` и `move --first`. Одно место из очереди изъято и **производно от его человек — раскладывая шаги или на груминге, — и двигают его `move --after`
типа и заполненности**: **сырьё** (`research` без раздела «Вопрос») стоит в конце и `move --first`. Одно место из очереди изъято и **производно от типа и
своей секции, потому что его не берут, и между берущимся оно каждый раз требует заполненности**: **сырьё** (`research` без раздела «Вопрос») стоит в конце своей
секции, потому что его не берут, и между берущимся оно каждый раз требует
открыть файл, чтобы это понять. Проверяет `check`, переставляет `check --fix`, открыть файл, чтобы это понять. Проверяет `check`, переставляет `check --fix`,
и человек этот порядок не назначает — иначе он был бы приоритетом, которого и человек этот порядок не назначает — иначе он был бы решением, которого здесь
здесь нет. нет.
**Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до **Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до
ответа человека, а следы остаются вопросами в файлах задач. ответа человека, а следы остаются вопросами в файлах задач.
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check` бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check`
говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. В секции говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её.
**`Запланировано`** очередь значима и обосновывается прозой; двигают строку
`move <slug> --section Запланировано --after <другой>`. В секции **`Готово`**
строки не той формы, что у прочих индексов: дата, слаг, заголовок — как в
`REJECTED.md`, и по той же причине (файла уже нет, ссылаться некуда).
**Секции роадмапа закреплены** — состав, полнота, единство языка и **порядок** **Имена секций проект выбирает сам, а количество ограничено стадией:** на
проверяются `check`; категории беклога проект называет сам. Почему так — стройке секция одна, потому что порядок там зависимость, и разложенный по полкам
SKILL.md. Порядок закреплён потому, что `Готово` копится: стоя первым, список перестаёт быть планом. Проверяет `check`; слить секции сам он не берётся —
достигнутое отодвигает за экран то, ради чего роадмап открывают чаще всего. в каком порядке пойдут строки слитых полок, знает только человек.
**Заголовок секции пишется с прописной и отбивается пустой строкой с обеих **Заголовок секции пишется с прописной и отбивается пустой строкой с обеих
сторон** — во всех индексах, включая категории беклога, имена которых выбирает сторон.** Отбивку правит `check --fix`; он же сводит написание места в мете файла
проект. Написание канонических секций и отбивку правит `check --fix`; он же с заголовком индекса.
сводит написание места в мете файла с заголовком индекса.
Индексы **производны**: расходятся с файлом — правим индексы (`check --fix`). Индекс **производен**: расходится с файлом — правим индекс (`check --fix`).
Строку руками не пишут. Строку руками не пишут.
Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё Отсюда же ответ на «а если оборвётся посередине». Мутация сперва проверяет всё
и складывает правки, и только потом пишет: сначала все временные файлы, потом и складывает правки, и только потом пишет: сначала все временные файлы, потом
переименования подряд. Полной транзакции на несколько файлов файловая система не переименования подряд. Полной транзакции на несколько файлов файловая система не
даёт, но окно сжато до цепочки переименований, а **всё, что в нём может даёт, но окно сжато до цепочки переименований, а **всё, что в нём может
разъехаться, — производное**: файлы целы, индексы восстанавливает `check --fix`. разъехаться, — производное**: файлы целы, индекс восстанавливает `check --fix`.
Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при
нетронутых индексах. нетронутом индексе.
## `REJECTED.md` ## `REJECTED.md`
@@ -346,27 +300,24 @@ SKILL.md. Порядок закреплён потому, что `Готово`
Разметка сверх типа. Тип полем, потому что он один и обязателен; теги — потому Разметка сверх типа. Тип полем, потому что он один и обязателен; теги — потому
что их много и `list --tag` уже умеет отбирать по ним порцию разбора. что их много и `list --tag` уже умеет отбирать по ним порцию разбора.
- `goal:<слаг>` — цель, которой служит задача. Обязателен **у `feature`**:
новая возможность и есть содержание цели. У `fix`, `chore` и `research` его
может не быть — они служат работоспособности, а не направлению.
- `question` — в файле есть неразобранный раздел «Вопросы». - `question` — в файле есть неразобранный раздел «Вопросы».
- `decomposed` — на цели: разложена на задачи (см. «Файл цели»).
Тега `kind:<род>` больше нет: род работы стал типом. Оставшийся в файле `check` Тегов `kind:<род>`, `goal:<слаг>` и `decomposed` больше нет: род работы стал
называет дрейфом, а `check --fix` снимает, перенеся значение в поле «Тип». типом, а цели упразднены. Оставшиеся в файле `check` называет дрейфом, а `check
--fix` снимает (значение `kind:` при этом переезжает в поле «Тип»).
Отбор — `list --tag a,b`: перечисленные через запятую теги требуются **все Отбор — `list --tag a,b`: перечисленные через запятую теги требуются **все
сразу** (это И, не ИЛИ). Тег, которого нет ни у одной задачи, `list` называет сразу** (это И, не ИЛИ). Тег, которого нет ни у одной задачи, `list` называет
вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка. вслух: молчаливый ноль читается как «таких задач нет», а чаще это опечатка.
Свои теги проект заводит свободно (партия ревью `review-ГГГГ-ММ-ДД`, тема, Свои теги проект заводит свободно (партия ревью `review-ГГГГ-ММ-ДД`, тема,
источник) — словарь не фиксирован. В индексы теги не выносим: индексы источник) — словарь не фиксирован. В индекс теги не выносим: он
производны, отбор делает `list --tag`, а не глаза. производен, отбор делает `list --tag`, а не глаза.
## Тест «готова к взятию» ## Тест «готова к взятию»
Задача готова, если из файла отвечаются четыре вопроса. Первый и четвёртый Задача готова, если из файла отвечаются три вопроса. Первый общий, второй и
общие, второй и третий у каждого типа свои и перечислены в его файле. третий у каждого типа свои и перечислены в его файле.
1. **Что станет наблюдаемо иначе**, когда она сделана — снаружи: пользователю, 1. **Что станет наблюдаемо иначе**, когда она сделана — снаружи: пользователю,
владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет владельцу сервиса или разработчику. «Отрефакторить X» — не ответ; «перестанет
@@ -379,26 +330,13 @@ SKILL.md. Порядок закреплён потому, что `Готово`
`chore``Затрагивает`. `chore``Затрагивает`.
3. **По чему видно, что закончено** — критерии приёмки с оракулами; 3. **По чему видно, что закончено** — критерии приёмки с оракулами;
у `research` вместо них `Куда ляжет ответ`. у `research` вместо них `Куда ляжет ответ`.
4. **Какую часть «Завершения» своей цели она двигает** — у задачи с целью. Не отвечается любой из трёх → это ещё не задача, а **сырьё**: тип `research` без
Строкой: «двигает пункт 2 «Завершения» — накопительная метрика перестаёт раздела «Вопрос», место — конец секции, работа над ним — штурм.
уменьшаться». Это и есть защита от задачи «отрефакторить X»: она проваливает
тест не потому, что невидима снаружи, а потому, что не находит строки, к
которой относится. Заодно видно обратное — достаточен ли набор задач для
цели: строка «Завершения», к которой не относится ни одна задача, это
незакрытая часть возможности.
**У задачи без цели** (`fix`, `chore`, `research`) вопрос не задаётся: они
служат работоспособности, а не направлению.
Не отвечается первый, второй или третий вопрос → это ещё не задача, а **сырьё**:
тип `research` без раздела «Вопрос», место — конец секции, работа над ним —
штурм. Не отвечается четвёртый у `feature` → либо цель есть и не проставлена,
либо это не новая возможность.
Отвечается всё, но задача не делается одним заходом и не мерджится целиком → Отвечается всё, но задача не делается одним заходом и не мерджится целиком →
это **несколько задач под одной целью**, дроби сразу. Промежуточного зонтика это **несколько задач**, дроби сразу и ставь их в списке подряд. Промежуточного
между целью и задачей нет: тип `[epic]` упразднён, потому что зонтиком стала зонтика между планом и задачей нет: тип `[epic]` упразднён, и цель, ставшая
сама цель. зонтиком после него, упразднена тоже.
Тест применяется при заведении и при переоценке. К старым задачам, которых Тест применяется при заведении и при переоценке. К старым задачам, которых
операция не касается, задним числом не применяется — беклог не переоформляют операция не касается, задним числом не применяется — беклог не переоформляют
@@ -1,93 +0,0 @@
# 🎯 `goal` — возможность приложения
Цель отвечает на **«что приложение будет уметь»**. Не область работ и не имя
подсистемы: не «Работа со слиянием», а «Исход слияния не зависит от порядка
доставки». Свойство поведения — тоже возможность.
Общая форма записи (мета, слаг, строка индекса) — [task-format.md](task-format.md).
Здесь только то, что у этого типа своё.
## Схема
| | |
| --- | --- |
| Заголовок отвечает на | что приложение будет уметь |
| Обязательные разделы | `Завершение` |
| Допустимые сверх того | — |
| Поле места | **Секция** — часть роадмапа |
| Цель (`goal:<слаг>`) | запрещена: цель и есть цель |
| Индекс | `ROADMAP.md`, и никогда `BACKLOG.md` |
| Берётся в работу | нет — берутся её задачи |
Поле места у цели называется **«Секция»**, а не «Категория», и это не разнобой:
у задачи оно называет полку домена, на которой она лежит, а у цели
— часть роадмапа, то есть состояние очереди. Одно имя на два смысла их и
смешивало.
## «Завершение» — списком, а не абзацем
Это признаки того, что приложение **уже умеет**, и на строки этого раздела
ссылаются задачи цели: «двигает пункт 2 «Завершения» — накопительная метрика
перестаёт уменьшаться». Абзацем такая ссылка не берётся, поэтому список.
Отсюда же читается обратное и более полезное: **строка «Завершения», к которой
не относится ни одна задача, — незакрытая часть возможности**. Достаточность
набора задач видна из самой цели, а не из чьей-то памяти.
## Алгоритм
1. **Проверить, что это возможность, а не работа.** Работа, которой держат
проект, на вопрос «что приложение будет уметь» не отвечает; состав перечислен
[в словаре сопровождения](../../../shared/operations.md). Ей отведена секция
`Сопровождение` — там она видна в том же
экране и не читается как обещание продукта. Граница проходит по тому,
**кто наблюдает**:
«приложение сообщает о своём состоянии» — возможность, «дежурный видит
состояние на одном экране» — сопровождение.
2. **Выбрать секцию.** Очередь значима и обоснована прозой — `Запланировано`;
тянется долго и очереди не имеет — `Направления`; про то, чем держат проект,
`Сопровождение`. В `Готово` кладёт сам `close`.
3. **Написать «Завершение»** — 2–5 наблюдаемых признаков списком. Пишутся до
декомпозиции: иначе задачи придумают себе цель задним числом.
4. **Разложить на задачи** и проставить им `goal:<слаг>`. Перечень задач в теле
цели **не хранится** — он был бы третьим индексом и поехал бы на первой же
закрытой задаче; выводит `tasks.py list --goal <слаг>`.
5. **Пометить `decomposed`.** Тег отличает «ещё не разобрана» от «все задачи
закрыты» — два состояния с одним внешним признаком. `check --fix` ставит его
сам цели, у которой задачи есть.
6. **Закрыть достигнутой**`close <слаг> --implemented`, когда не осталось
открытых задач. Файл удаляется, строка с датой переезжает в `Готово`. Скрипт
откажет, если задачи ещё живы.
## Отменённая цель — сперва задачи, потом цель
Замысел бывает неверен, и цель отменяют, не достигнув. Порядок обратный
завершению и держится тем же запретом: цель, закрытая поверх живых задач,
оставила бы их сиротами, и `close` этого не даст.
1. **Разобрать её задачи поштучно.** Задача, теряющая смысл вместе с целью, —
`close <slug> --reason "<почему>"`; задача, переживающая цель, — `edit <slug>
--goal <другая>`. **Причина обязательна и пишется своя каждой:** «цель
отменена» это не причина, а пересказ команды, и в `REJECTED.md` от него нет
пользы через квартал.
2. **Закрыть саму цель**`close <слаг> --reason "<почему замысел отменён>"`.
Файл удаляется, строка с причиной и датой уходит в `REJECTED.md`. В `Готово`
не попадает: `Готово` отвечает «что приложение умеет», а отменённая цель не
умеет ничего.
**Место этому — груминг, а не отдельный заход.** Отмена цели значит
разбор всех её задач, а разбор задач и есть шаг 3 груминга
(скилл `task-groom`, «что перестало быть важным»). Отменять на ходу,
между делом, — верный способ закрыть скопом то, что стоило перевесить.
## Что видит машина, а что человек
`check` считает цели, различает разобранные и пустые, ставит `decomposed`,
запрещает закрыть цель с живыми задачами и держит `Секцию` в согласии с
заголовком роадмапа. **Годность формулировки — не машине**: «возможность это или
область работ» решает [агент вычитки](../SKILL.md#вычитка-два-прохода-а-не-один).
Достигнутая цель **не исчезает**: «что приложение умеет» — половина вопроса, ради
которого роадмап открывают. Вторым домом поведения роадмап при этом не
становится: нормативное поведение живёт в `openspec/specs/`, роадмап отвечает,
**когда и в каком порядке** оно появилось.
@@ -14,7 +14,6 @@
| Обязательные разделы | `Вопрос`, `Куда ляжет ответ` | | Обязательные разделы | `Вопрос`, `Куда ляжет ответ` |
| Допустимые сверх того | `Рамки`, `Вопросы` | | Допустимые сверх того | `Рамки`, `Вопросы` |
| Поле места | **Категория** — полка домена беклога | | Поле места | **Категория** — полка домена беклога |
| Цель (`goal:<слаг>`) | нет |
| Индекс | `BACKLOG.md` | | Индекс | `BACKLOG.md` |
| Берётся в работу | да — **но только с заполненным «Вопросом»** | | Берётся в работу | да — **но только с заполненным «Вопросом»** |
@@ -43,7 +42,8 @@
| Место в секции беклога | **конец**, `check --fix` сносит туда сам | среди прочих | | Место в секции беклога | **конец**, `check --fix` сносит туда сам | среди прочих |
| `tasks.py list --raw` | показывает | нет | | `tasks.py list --raw` | показывает | нет |
Порядок строк в беклоге назначает человек — это приоритет (правило 4 скилла). Порядок строк в беклоге назначает человек, и стадия решает, что он значит:
зависимость на стройке, важность на доработке (правило 4 скилла).
Место сырья **из него изъято**: оно производно от типа и заполненности, а не от Место сырья **из него изъято**: оно производно от типа и заполненности, а не от
чьего-то решения, и потому его проверяет и чинит машина. Приоритетом оно не чьего-то решения, и потому его проверяет и чинит машина. Приоритетом оно не
становится: сырьё не берут вовсе, и место в конце говорит именно это. становится: сырьё не берут вовсе, и место в конце говорит именно это.
File diff suppressed because it is too large Load Diff
+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. Заполненная форма и заполненный смысл — разные вещи, и первая маскирует
вторую.** Файл на месте, валиден, с правильным именем — и пуст по существу: это
худший вид пробела, потому что выглядит он как его отсутствие.

Some files were not shown because too many files have changed in this diff Show More