Compare commits
2
Commits
12b77c3393
...
b8120d3271
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8120d3271
|
||
|
|
863769406f
|
@@ -18,7 +18,7 @@
|
||||
{
|
||||
"name": "av-dev-code",
|
||||
"source": "./av-dev-code",
|
||||
"description": "Решение одной задачи от постановки до закрытия скиллом resolve — полный цикл Spec Driven Development с двумя плановыми остановками: чекпоинт вариантов у исследовательской задачи и чекпоинт с объяснением человеческим языком после ревью дизайна у всякой. Между ними работа идёт без согласований. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом и обязательным триажем. Требует OpenSpec и сам его заводит скиллом openspec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой."
|
||||
"description": "Одна задача от постановки до закрытия скиллом resolve: точка входа одна, сценария два, и выбирает сценарий сам скилл, прочитав постановку. Сценарий решения — полный цикл Spec Driven Development с плановым чекпоинтом после ревью дизайна: объяснение человеческим языком, повод скорректировать ход решения. Сценарий разведки (тип research, сырая идея, мутная постановка) кода не пишет и change не заводит: его чекпоинт вариантов стоит до первого написанного требования, а исход уезжает в документы канона и в задачи; выбранный способ реализуется следующим прогоном, который запускает человек. Смена сценария по ходу — событие с названным исходом, а не тихий поворот. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом и обязательным триажем. OpenSpec нужен сценарию решения, и плагин сам его заводит скиллом openspec; разведка обходится без него. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой."
|
||||
},
|
||||
{
|
||||
"name": "av-dev-git",
|
||||
|
||||
+132
@@ -3647,3 +3647,135 @@ change нет — берём источником актуальные спек
|
||||
которую никто не набрал, может не существовать вовсе — и именно так и было.
|
||||
199. **Два дома у факта расходятся не когда-нибудь, а сразу.** Из четырёх пар
|
||||
описаний плагина совпала одна — та, которую с момента заведения не правили.
|
||||
|
||||
## 60. Служебный файл зовётся по плагину-владельцу; у задач появилась своя версия формата (2026-08-11)
|
||||
|
||||
Файл версии канона звался `docs/.pm.json` — по плагину `av-dev-pm`, который
|
||||
распался на четыре ещё в решении 56 и которого больше нет. Имя пережило
|
||||
владельца на два месяца и указывало в пустоту: читающий его искал плагин, о
|
||||
котором в репозитории не осталось ни строки. Переименован в `docs/.docs.json`
|
||||
записью 13 журнала канона.
|
||||
|
||||
**Правило, которое из этого вынуто и теперь держит все три файла:** имя
|
||||
служебного файла — имя плагина, который его завёл. `.docs.json` — канон,
|
||||
`.tasks.json` — задачи, `openspec/config.yaml` — конвейер. По этому же следу
|
||||
скиллы узнают, что сосед в проекте работал, и правило перестало быть просто
|
||||
перечнем — оно выводимо.
|
||||
|
||||
**Прежнее имя `docs.py` не читает.** Соблазн «прочитать оба и не мешать людям»
|
||||
здесь стоит дороже, чем везде: по этому числу `upgrade` решает, какие записи
|
||||
журнала применять, и два дома для него разъехались бы молча в том самом месте,
|
||||
где расхождение и вредно. Вместо совместимости — узнавание: `check` видит файл
|
||||
под старым именем и печатает готовую команду `git mv`.
|
||||
|
||||
**У каталога задач появилась своя версия формата** — ключ `tasks` в
|
||||
`<каталог задач>/.tasks.json` и свой журнал версий в скилле `av-dev-tasks:tasks`.
|
||||
До сих пор её не было вовсе, хотя `docs.py` в комментарии уверенно ссылался на
|
||||
«свою версию формата» соседа: описание опережало механику ровно так, как описано
|
||||
в решении 195. Формат задач при этом менялся — записями 8, 11 и 12 чужого
|
||||
журнала.
|
||||
|
||||
**Число именно своё, а не копия канонического.** Плагин ставится в одиночку:
|
||||
проект, взявший учёт работ без канона документов, каталога `docs/` не имеет
|
||||
вовсе, а значит не имеет и версии канона — сверять было бы не с чем. Копия
|
||||
чужого числа в `tasks.py` была бы вторым домом одной версии и разъехалась бы при
|
||||
первом же обновлении одного плагина без другого.
|
||||
|
||||
**Переезды, случившиеся до появления числа, задним числом в новый журнал не
|
||||
переписаны.** Версия 1 — это формат на день её появления; что проекту нужно было
|
||||
пройти до неё, названо шагом «догнать формат по журналу канона» с поимёнными
|
||||
признаками отставания (каталог в `docs/tasks/`, живой `SPRINT.md`). Второй
|
||||
перечень тех же шагов разошёлся бы с первым — это ровно та ошибка, из-за которой
|
||||
план однажды повторял записи версий 3, 4 и 5 построчно.
|
||||
|
||||
**Конфиг задач стал обязательным.** Раньше он заводился только ради имён,
|
||||
отличных от умолчания, и проект с умолчаниями жил без файла вовсе. Версия — не
|
||||
настройка, от которой можно отказаться, поэтому `init` и `adopt apply` пишут его
|
||||
всегда, а `check` требует числа.
|
||||
|
||||
Отдельно стоит сказать, чтобы не спутали при чтении журнала: `.docs.json`
|
||||
однажды уже был отвергнут — решением F, но **как указатель путей**. Отвергнут
|
||||
был указатель, а не имя; сегодняшний файл путями проекта не распоряжается, он
|
||||
объявляет версию и называет то немногое, чего из раскладки не вывести.
|
||||
|
||||
### Что из этого следует
|
||||
|
||||
200. **Имя служебного файла — часть границы плагинов, а не деталь.** Оно
|
||||
называет владельца, и по нему же владельца узнают. Пережившее владельца имя
|
||||
врёт дважды: указывает на несуществующее и прячет того, кто файл ведёт на
|
||||
самом деле.
|
||||
201. **Версия нужна каждому формату, который живёт в чужом репозитории.** Без
|
||||
числа «приведён ли проект» не имеет определённого ответа, и отставший
|
||||
каталог выглядит здоровым до первой команды, которая об него споткнётся.
|
||||
202. **Своя версия — у своего плагина, всегда.** Общее число на два плагина
|
||||
переживает ровно до первого проекта, где поставлен один из них.
|
||||
203. **Версию двигают руками, и это не слабость проверки.** Число отвечает
|
||||
на вопрос «по какой записи повышать», а не «сделаны ли шаги по существу».
|
||||
Машина, приписывающая недостающее число сама, объявляет проект приведённым
|
||||
к формату, которого никто не проходил.
|
||||
|
||||
## 61. Разведка и решение — два сценария одного скилла, а не два скилла (2026-08-11)
|
||||
|
||||
Скилл `resolve` вёл обе работы одной цепочкой: у исследовательской задачи были
|
||||
свои три шага и свой чекпоинт вариантов, после которого она **вливалась в общую
|
||||
ветку** и продолжалась кодом. Разведка тем самым была не работой со своим
|
||||
исходом, а прологом к коду: её ответ оседал в `design.md` будущего change, и
|
||||
разведка, кончившаяся знанием, документов проекта не касалась вовсе.
|
||||
|
||||
Сперва я развёл их на два скилла — `resolve` и `research`, с исходом и стопом с
|
||||
обеих сторон. Через час работы стало видно, чем это плохо: **классифицировать
|
||||
задачу приходится человеку до вызова**, а «есть ли у неё очевидный способ
|
||||
решения» видно только после чтения записи. Разделение переносило самое трудное
|
||||
суждение туда, где для него меньше всего данных.
|
||||
|
||||
**Решено: точка входа одна, сценария два, выбирает сценарий скилл.** Оба сценария
|
||||
живут справочниками — `references/solve.md` и `references/research.md`, — а в
|
||||
`SKILL.md` остались вход, развилка и правила, не зависящие от сценария. Тем же
|
||||
приёмом сложен скилл задач: общая часть в `SKILL.md`, алгоритм каждого типа в
|
||||
`references/task-*.md`.
|
||||
|
||||
**Порознь и одинаково — это отдельное решение.** Сперва разведка уехала в
|
||||
справочник, а решение осталось в теле скилла: так вышло само, потому что решение
|
||||
там уже лежало. Асимметрия читается как старшинство — сценарий в теле выглядит
|
||||
основным, а сценарий в справочнике оговоркой, — и удерживает шестисотстрочный
|
||||
файл, который грузится целиком даже ради разведки.
|
||||
|
||||
**Что у разведки появилось своего.** Исход — знание, а не пролог: ответ уезжает в
|
||||
документы канона (`av-dev-docs:docs`), задачи заводятся и уточняются
|
||||
(`av-dev-tasks:tasks`), написанное коммитится, запись закрывается. Кода сценарий
|
||||
не пишет вовсе. OpenSpec ему не нужен — это единственное место скилла, где тот не
|
||||
предпосылка.
|
||||
|
||||
**Переход между сценариями — событие с названным исходом.** Решение, упёршееся в
|
||||
незнание способа, останавливается; разведка, выбравшая способ, доводится до конца
|
||||
и **не переходит в код тем же прогоном** — следующий запускает человек. Причина
|
||||
не в церемонии: разведка только что переписала постановку, и брать её в работу
|
||||
тем же заходом значит решать за человека, стоит ли делать это сейчас, — а это
|
||||
приоритет.
|
||||
|
||||
**Канон пришлось тронуть, и это версия 14.** ADR цитировал только архивный
|
||||
`design.md`. У решения, принятого разведкой, `design.md` нет по построению —
|
||||
change по нему не будет никогда, — и такое решение либо не попадало в `adr/`
|
||||
вовсе, либо попадало сочинённым заново. Теперь источников два, и оба называются в
|
||||
записи.
|
||||
|
||||
### Что из этого следует
|
||||
|
||||
204. **Разделять работы стоит по моменту для человека, а не по роду работы.**
|
||||
У разведки и решения он разный: варианты обсуждают до первого требования,
|
||||
объяснение — после ревью дизайна. Всё остальное различие (пишем код или нет)
|
||||
из этого уже следует.
|
||||
205. **Точку входа не разделяют по признаку, который виден только внутри.**
|
||||
Классификация, требующая прочитать запись, не может быть условием вызова:
|
||||
человек либо ошибётся, либо прочитает запись сам — и тогда скилл ему не
|
||||
нужен.
|
||||
206. **Сценарий в справочнике дешевле скилла.** Скилл стоит описания, границ,
|
||||
копии правил и своего места в графе вызовов; справочник наследует их у
|
||||
хозяина. Заводить второй скилл имеет смысл, когда его зовут отдельно, а не
|
||||
когда он просто длинный.
|
||||
207. **Равные сценарии лежат одинаково.** Оставить один в теле скилла, а второй
|
||||
вынести — значит назначить первому старшинство, которого в замысле нет.
|
||||
Читатель это старшинство считывает, даже когда о нём не сказано ни слова.
|
||||
208. **Работа без своего исхода вырождается в пролог.** Разведка, кончавшаяся
|
||||
переходом к коду, не имела причины писать в документы: её ответ и так уезжал
|
||||
в `design.md`. Дом для исхода — вот что делает работу работой.
|
||||
|
||||
@@ -36,19 +36,28 @@
|
||||
очереди, и живёт он в индексе. Интерактивный: разбирает вопросы пачкой,
|
||||
переоценивает порциями по 5–8, расставляет верх очереди с доводом на
|
||||
каждое движение.
|
||||
- **av-dev-code** — код по задачам: решение одной задачи и его проверка.
|
||||
Владеет `openspec/`. **Требует OpenSpec и сам его заводит.**
|
||||
- **av-dev-code** — работа по задачам: разведка, решение и проверка сделанного.
|
||||
Владеет `openspec/`. **Требует OpenSpec и сам его заводит** — кроме сценария
|
||||
разведки, которому он не нужен.
|
||||
- `openspec` — завести, настроить и **проверить** `openspec/` в проекте:
|
||||
`openspec init`, замена примера в `config.yaml` настройкой канонической
|
||||
формы, скрипт `openspec.py` (форма файла + сверка слепка с живой версией
|
||||
инструмента). Каталог принадлежит конвейеру, а не канону: без конвейера он
|
||||
проекту не нужен, и `docs.py` о нём молчит;
|
||||
- `resolve` — одна задача от постановки до закрытия. Обычная идёт циклом SDD
|
||||
с **чекпоинтом после ревью дизайна**: объяснение человеческим языком, повод
|
||||
скорректировать ход решения. Исследовательская начинается с `opsx:explore` и
|
||||
**чекпоинта вариантов** — способы решить, цена каждого, рекомендация; выбор
|
||||
оседает по адресу, который назвала сама задача. Между чекпоинтами — без
|
||||
согласований;
|
||||
- `resolve` — одна задача от постановки до закрытия. **Точка входа одна, а
|
||||
сценария два, и выбирает сценарий сам скилл, прочитав постановку:**
|
||||
классифицировать задачу до вызова человек всё равно не может — «есть ли
|
||||
очевидный способ решения» видно после чтения записи.
|
||||
**Решение** идёт циклом SDD с чекпоинтом после ревью дизайна: объяснение
|
||||
человеческим языком, повод скорректировать ход.
|
||||
**Разведка** (тип `research`, сырая идея, мутная постановка) кода не пишет
|
||||
вовсе: её чекпоинт — варианты, 2–4 способа решить с ценой каждого, — стоит до
|
||||
первого написанного требования, а исход уезжает в документы канона и в
|
||||
задачи. Выбранный способ реализуется **следующим прогоном**, и запускает его
|
||||
человек: смена сценария по ходу — событие с названным исходом, а не тихий
|
||||
поворот. Оба сценария лежат справочниками и одинаково — `references/solve.md`
|
||||
и `references/research.md`; в самом скилле только вход, развилка и правила,
|
||||
не зависящие от сценария. OpenSpec нужен решению, разведке — нет;
|
||||
- `review` — конвейер ревью **по темам**: документ проекта либо
|
||||
заводит тему проверки, либо питает чужую тему источником, либо процессный и в
|
||||
ревью не читается вовсе. Разметка идёт **один раз на задачу**, сразу после
|
||||
@@ -69,9 +78,9 @@
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph pipe["av-dev-code — исполнение, требует OpenSpec"]
|
||||
subgraph pipe["av-dev-code — исполнение; сценарий решения требует OpenSpec"]
|
||||
direction LR
|
||||
tp["resolve<br/>2 чекпоинта человеку"] --> rp["review<br/>10 агентов-проходов"]
|
||||
tp["resolve<br/>2 сценария: разведка и решение"] --> rp["review<br/>10 агентов-проходов"]
|
||||
osp["openspec<br/>заводит и проверяет openspec/"]
|
||||
end
|
||||
subgraph docsp["av-dev-docs — документация, владеет docs/"]
|
||||
@@ -143,7 +152,16 @@ OpenSpec скиллом `av-dev-code:openspec`, `av-dev-docs:docs` берёт у
|
||||
|
||||
Прийти в старый проект и перевести его на канон — `/av-dev-docs:canon`. Канон
|
||||
версионируется, и проекты повышаются по [журналу
|
||||
версий](av-dev-docs/skills/canon/references/changelog.md).
|
||||
версий](av-dev-docs/skills/canon/references/changelog.md); версия проекта живёт в
|
||||
`docs/.docs.json`.
|
||||
|
||||
**Версий две, и они независимы.** У каталога задач своя — ключ `tasks` в
|
||||
`<каталог задач>/.tasks.json`, свой [журнал
|
||||
версий](av-dev-tasks/skills/tasks/references/changelog.md) и своё повышение
|
||||
скиллом `/av-dev-tasks:tasks`. Плагины ставятся порознь: у проекта, взявшего учёт
|
||||
работ без канона документов, `docs/` нет вовсе, и общее число оказалось бы домом,
|
||||
которого у половины проектов не существует. Имя служебного файла при этом
|
||||
называет владельца — `.docs.json`, `.tasks.json`, `openspec/config.yaml`.
|
||||
|
||||
## Подключение
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
цикл SDD, конвейер ревью, OpenSpec), `av-dev-git`. Общее, что нужно нескольким
|
||||
дословно, живёт домом в `shared/` и уезжает копиями.
|
||||
|
||||
Канон документов — **версия 12**. Живые проекты стоят на 2–3 и на плагине
|
||||
Канон документов — **версия 14**; формат задач — **версия 1**, своя и со своим журналом. Живые проекты стоят на 2–3 и на плагине
|
||||
`av-dev-pm`, которого больше нет.
|
||||
|
||||
Бумажная часть закрыта аудитом четырёх плагинов и четырьмя пропусками правок
|
||||
@@ -42,11 +42,12 @@
|
||||
- [ ] удалить проектные копии: `.claude/skills/healthlog-{task,review}-pipeline`
|
||||
и девять `.claude/agents/healthlog-review-*.md`. Они прошлого поколения и
|
||||
после переезда указывают на документы, которых уже не будет
|
||||
- [ ] `av-dev-docs:canon` в режиме `adopt` — он приведёт проект к канону 12
|
||||
- [ ] `av-dev-docs:canon` в режиме `adopt` — он приведёт проект к канону 14
|
||||
сразу, картой и с подтверждением. Файл-в-файл здесь не расписан: раскладку
|
||||
знает скилл, и второй перечень разошёлся бы с ним
|
||||
- [ ] каталог задач — в `tasks/` корня (канон 11), не в `docs/tasks/`, и без
|
||||
`SPRINT.md` (канон 12). Скилл задач зовётся из `adopt` сам
|
||||
- [ ] каталог задач — в `tasks/` корня (канон 11), не в `docs/tasks/`, без
|
||||
`SPRINT.md` (канон 12) и с версией формата в `tasks/.tasks.json` (журнал
|
||||
задач, версия 1). Скилл задач зовётся из `adopt` сам
|
||||
- [ ] гейт проекта: три шага вместо одного — `docs.py check`, `tasks.py check
|
||||
--dir tasks`, `openspec.py check`. **Второй и третий раньше не были
|
||||
нужны:** согласованность задач тянул за собой `docs.py`, форму `config.yaml`
|
||||
@@ -83,9 +84,14 @@
|
||||
|
||||
## 4. Конвейер: что осталось после `resolve`
|
||||
|
||||
Сам скилл написан (`av-dev-code:resolve`, два чекпоинта, ветка разведки),
|
||||
`task-batch` удалён. Осталось то, что на бумаге не проверяется:
|
||||
Сам скилл написан (`av-dev-code:resolve`, два сценария — разведка и решение,
|
||||
по чекпоинту у каждого), `task-batch` удалён. Осталось то, что на бумаге не
|
||||
проверяется:
|
||||
|
||||
- [ ] прогнать сценарий разведки на живой задаче: он написан целиком на бумаге и
|
||||
не исполнялся ни разу. Самое неизвестное — выбор сценария на входе (не
|
||||
уедет ли всё в решение, потому что «способ вроде понятен») и объём того,
|
||||
что разведка пишет в документы
|
||||
- [ ] перемерить скилл `review` тем же вопросом, что и проект целиком:
|
||||
сколько из его стадий реально смотрятся глазами. Тысяча строк, и весь
|
||||
автоматический участок между чекпоинтами держится на них
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "av-dev-code",
|
||||
"description": "Решение одной задачи от постановки до закрытия скиллом resolve — полный цикл Spec Driven Development с двумя плановыми остановками: чекпоинт вариантов у исследовательской задачи и чекпоинт с объяснением человеческим языком после ревью дизайна у всякой. Между ними работа идёт без согласований. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом и обязательным триажем. Требует OpenSpec и сам его заводит скиллом openspec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой.",
|
||||
"description": "Одна задача от постановки до закрытия скиллом resolve: точка входа одна, сценария два, и выбирает сценарий сам скилл, прочитав постановку. Сценарий решения — полный цикл Spec Driven Development с плановым чекпоинтом после ревью дизайна: объяснение человеческим языком, повод скорректировать ход решения. Сценарий разведки (тип research, сырая идея, мутная постановка) кода не пишет и change не заводит: его чекпоинт вариантов стоит до первого написанного требования, а исход уезжает в документы канона и в задачи; выбранный способ реализуется следующим прогоном, который запускает человек. Смена сценария по ходу — событие с названным исходом, а не тихий поворот. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом и обязательным триажем. OpenSpec нужен сценарию решения, и плагин сам его заводит скиллом openspec; разведка обходится без него. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой.",
|
||||
"author": {
|
||||
"name": "Anton Vakhrushev",
|
||||
"email": "anwinged@gmail.com"
|
||||
|
||||
@@ -117,7 +117,7 @@ color: green
|
||||
|---|---|---|
|
||||
| **тема** | `conventions.*`, `security.*`, `architecture.*`, любой свой документ проекта | заводишь строку темы и назначаешь исполнителя |
|
||||
| **источник темы** | `passport.*`, `database.*` | называешь адресом **внутри** строки чужой темы, своей строки не заводишь |
|
||||
| **процессный** | `tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json` | называешь строкой «процессный», исполнителя нет и не должно быть |
|
||||
| **процессный** | `tasks/`, `review.*`, `adr.*`, `research.*`, `.docs.json` | называешь строкой «процессный», исполнителя нет и не должно быть |
|
||||
|
||||
`docs/review.*` при этом ты читаешь — но как **настройку конвейера**, откуда
|
||||
берутся вопросы по темам и триггеры метки, а не как тему. `adr.*` и `research.*`
|
||||
@@ -128,7 +128,7 @@ color: green
|
||||
**Каждая запись в `docs/` обязана попасть в план строкой своей категории.** Не «я
|
||||
посмотрел и решил» — перечислением. Это и есть проверка твоей работы: план
|
||||
сверяется с `ls docs/` за секунду, и пропущенный документ виден без рассуждения.
|
||||
`docs/.pm.json` — единственное исключение: служебный файл, не документ, в плане
|
||||
`docs/.docs.json` — единственное исключение: служебный файл, не документ, в плане
|
||||
не упоминается.
|
||||
|
||||
**Категории `источник` и `процессный` закрыты — они перечислены выше поимённо.**
|
||||
|
||||
@@ -147,8 +147,10 @@ python3 $os form # слепок формы против жив
|
||||
|
||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||
|
||||
<!-- /копия: граница-плагинов -->
|
||||
|
||||
|
||||
+128
-492
@@ -1,29 +1,42 @@
|
||||
---
|
||||
name: resolve
|
||||
description: "Решить одну задачу от постановки до закрытия. На входе путь к файлу задачи, её слаг или просто текст. Обычная задача идёт циклом Spec Driven Development: opsx propose → разметка → ревью дизайна → чекпоинт с объяснением человеческим языком → opsx apply → ревью кода → archive → синк документации → коммит → закрытие. Исследовательская (тип research, сырая идея, мутная постановка) начинается раньше: opsx explore и чекпоинт вариантов — два-четыре способа решить, с ценой каждого и рекомендацией; выбор оседает по адресу, который назвала сама задача. Между чекпоинтами работа идёт без согласований. Использовать, когда просят взять, сделать или решить задачу, довести идею до реализации, разобраться с записью из беклога."
|
||||
description: "Взять одну задачу и довести её до закрытия. Одна точка входа, два сценария, и выбирает сценарий сам скилл, прочитав постановку. Способ решения известен — сценарий решения: цикл Spec Driven Development (opsx propose → разметка → ревью дизайна → чекпоинт с объяснением человеческим языком → opsx apply → ревью кода → archive → синк документации → коммит → закрытие). Способа нет, постановка мутная, тип research — сценарий разведки: вопрос и рамки → чтение документов, кода и внешних источников (можно opsx:explore) → чекпоинт вариантов: 2–4 способа решить, цена каждого, что становится невозможным, рекомендация → ответ уезжает в документы канона, исход — в задачи → коммит → закрытие. Разведка кода не пишет и change не заводит, а выбранный способ реализуется следующим прогоном. На входе путь к файлу задачи, её слаг или просто текст. Использовать, когда просят взять, сделать или решить задачу, разобраться, изучить, сравнить подходы, проработать сырую идею, ответить на вопрос из беклога."
|
||||
---
|
||||
|
||||
# Решение одной задачи
|
||||
# Работа над одной задачей
|
||||
|
||||
Проводит **одну** задачу от постановки до закрытия. Между плановыми
|
||||
остановками — без согласований: механику не обсуждаем, делаем.
|
||||
Проводит **одну** задачу от постановки до закрытия. Вокруг планового стопа — без
|
||||
согласований: механику не обсуждаем, делаем.
|
||||
|
||||
Тонкая обёртка над каноническими скиллами `opsx:explore` / `opsx:propose` /
|
||||
`opsx:apply` / `opsx:archive` — зови их через Skill, не переизобретай их шаги.
|
||||
Ревью — скилл `av-dev-code:review`; он же держит правило выбора
|
||||
метки, а называет её агент `review-scope` — один раз на задачу, для обеих стадий
|
||||
ревью.
|
||||
**Сценария два, а точка входа одна.** Какой из них идёт, решает **скилл**,
|
||||
прочитав постановку, а не человек до вызова: «есть ли у задачи очевидный способ
|
||||
решения» видно после чтения записи, и требовать этого суждения от вызывающего
|
||||
значит требовать его раньше, чем оно возможно.
|
||||
|
||||
| Сценарий | Когда | Чем кончается |
|
||||
| --- | --- | --- |
|
||||
| **решение** | способ известен, спорно только как | код, ревью, архив, коммит, закрытие |
|
||||
| **разведка** | способа нет: тип `research`, сырая идея, мутная постановка | ответ в документах, задачи, коммит, закрытие |
|
||||
|
||||
Ход каждого сценария живёт своим справочником: **решение** —
|
||||
[references/solve.md](references/solve.md), **разведка** —
|
||||
[references/research.md](references/research.md). Здесь только общее: вход,
|
||||
развилка, правила, которые не зависят от сценария. Сценарии лежат порознь и
|
||||
одинаково, потому что привилегированного среди них нет: тот, что жил бы прямо
|
||||
здесь, читался бы как основной, а второй — как оговорка.
|
||||
|
||||
## Предпосылки
|
||||
|
||||
- **OpenSpec и скиллы `opsx:*` — жёсткая предпосылка, а не опция.** На них стоят
|
||||
шаги 2, 6 и 8, шаг Р2 разведки, проход `review-specs` и ревью дизайна (они
|
||||
завязаны на `openspec/changes/<id>/specs/*/spec.md` и на
|
||||
- **OpenSpec и скиллы `opsx:*` — жёсткая предпосылка сценария решения**, а не
|
||||
опция. На них стоят его шаги 2, 6 и 8, проход `review-specs` и ревью дизайна
|
||||
(они завязаны на `openspec/changes/<id>/specs/*/spec.md` и на
|
||||
`openspec validate --strict`). **Проект без OpenSpec этим скиллом не ведётся** —
|
||||
подключай OpenSpec, а не вырождай цикл; почему ветка деградации здесь не
|
||||
подключай OpenSpec, а не вырождай цикл сценария; почему ветка деградации здесь
|
||||
не
|
||||
пишется, сказано в `av-dev-code:review`, раздел «Предпосылки», и дом у этого
|
||||
довода там. Заводить руками не надо: каталог и настройку в `config.yaml`
|
||||
делает скилл `av-dev-code:openspec`.
|
||||
делает скилл `av-dev-code:openspec`. **Сценарию разведки OpenSpec не нужен** —
|
||||
она не заводит change; `opsx:explore` берётся, если плагин есть.
|
||||
- **Проектные копии этих скиллов и агентов удаляются при установке плагина**
|
||||
(`.claude/skills/` — голые имена `resolve`, `review`, а у проектов прошлого
|
||||
поколения ещё `task-pipeline`, `review-pipeline`, `task-batch`, и с префиксом
|
||||
@@ -57,8 +70,10 @@ description: "Решить одну задачу от постановки до
|
||||
|
||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||
|
||||
<!-- /копия: граница-плагинов -->
|
||||
|
||||
@@ -89,74 +104,87 @@ description: "Решить одну задачу от постановки до
|
||||
когда сверять уже не с чем.
|
||||
|
||||
**`ready` отказал — это исход, а не препятствие для тебя.** Скажи, чего не
|
||||
хватает, и остановись: дописывать чужую запись за автора не твоя работа, а у
|
||||
сырья (`research` без раздела «Вопрос») и дописывать нечего — там сперва нужен
|
||||
вопрос. Исход — «не доведена», с названной причиной.
|
||||
хватает, и остановись: дописывать чужую запись за автора не твоя работа. Исход —
|
||||
«не доведена», с названной причиной.
|
||||
|
||||
**Отказ `ready` сценарий не выбирает.** Запись `research` без раздела «Вопрос»
|
||||
(сырьё) — отказ и здесь: у неё нет вопроса, и разведывать нечего.
|
||||
|
||||
Плагина `av-dev-tasks` в проекте нет или задача пришла текстом — прогонять
|
||||
нечего. Тогда прочитай постановку сам и скажи строкой, что готовность машиной не
|
||||
проверялась; работу при этом не останавливай.
|
||||
|
||||
## Две ветки
|
||||
## Развилка: какой сценарий
|
||||
|
||||
Развилка одна и стоит на входе:
|
||||
Она одна, и стоит до всякой работы: **есть ли у задачи один очевидный способ
|
||||
решения?**
|
||||
|
||||
- **обычная задача** — что делать, понятно; спорно только как. Идёт с шага 1;
|
||||
- **исследовательская** — тип `research`, сырая идея, новое и незнакомое, мутная
|
||||
постановка. Идёт с шага Р1, и там её ждёт **свой** чекпоинт: варианты решения
|
||||
обсуждаются **до** того, как написано первое требование.
|
||||
- **есть** — что делать, понятно; спорно только как. **Сценарий решения** —
|
||||
[references/solve.md](references/solve.md);
|
||||
- **нет** — тип `research`, сырая идея, новое и незнакомое, мутная постановка,
|
||||
два подхода с разной ценой. **Сценарий разведки** —
|
||||
[references/research.md](references/research.md).
|
||||
|
||||
Признак не в объёме работы, а в том, **есть ли у задачи один очевидный способ
|
||||
решения**. Его нет — обсуждать варианты после `propose` поздно: предложение уже
|
||||
воплотило один из них, и разговор пойдёт не о выборе, а о переделке.
|
||||
Признак не в объёме работы. Крупная задача с очевидным способом идёт в решение;
|
||||
маленькая, но незнакомая — в разведку. Тип `research` в разведку идёт всегда: её
|
||||
исход знание, а не изменение системы.
|
||||
|
||||
**Назови выбранный сценарий вслух первой репликой** — одной строкой, с причиной.
|
||||
Молча выбранный сценарий человек обнаруживает по тому, что работа пошла не туда,
|
||||
и обнаруживает поздно.
|
||||
|
||||
### Сценарий выбирается один раз
|
||||
|
||||
**Смена сценария по ходу — событие, а не тихий поворот**, и каждая из двух смен
|
||||
устроена по-своему:
|
||||
|
||||
- **решение → разведка**: обнаружилось, что очевидного способа нет. Это **стоп**
|
||||
с исходом «нужна разведка»: назови, что именно неясно, и не продолжай.
|
||||
Кода к этому моменту не написано, и писать его «пока разбираемся» нельзя;
|
||||
- **разведка → решение**: способ выбран на чекпоинте вариантов. Разведка **всё
|
||||
равно доводится до конца** — ответ записан, задачи уточнены, коммит сделан, —
|
||||
и решение идёт **следующим прогоном**, который запускает человек.
|
||||
|
||||
**Соблазн «разведаю по ходу» живёт именно здесь**, и он дорог тем, что выглядит
|
||||
экономией одного прогона. Разведка внутри решения не имеет своего чекпоинта:
|
||||
выбор делается тем, кто уже начал писать, и человек видит его только в
|
||||
объяснении, где обсуждать выбор поздно. Ровно за это сценарии и разведены — не за
|
||||
то, что это разные работы, а за то, что у них разные моменты для человека.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
in["вход: файл, слаг или текст"]
|
||||
ready["ready: готовность записи<br/>av-dev-tasks:tasks"]
|
||||
fork{"есть очевидный<br/>способ решения?"}
|
||||
r1["Р1. понять вопрос<br/>сырьё без «Вопроса» — отказ"]
|
||||
r2["Р2. opsx:explore — груминг"]
|
||||
r3(["Р3. ЧЕКПОИНТ: варианты<br/>2–4 способа, цена каждого,<br/>рекомендация"])
|
||||
rout["исход без кода:<br/>ответ записан / отказ / родились задачи"]
|
||||
s1["1. прочитать задачу<br/>критерии приёмки выписать сразу"]
|
||||
s2["2. opsx:propose — change, дельта-спеки, tasks.md"]
|
||||
s3["3. разметка — review-scope:<br/>размер, сложность, метка, план тем"]
|
||||
s4["4. ревью дизайна, состав по метке<br/>+ отработка замечаний"]
|
||||
s5(["5. ЧЕКПОИНТ: объяснение<br/>в чём проблема, как решаем,<br/>чем рискуем"])
|
||||
s6["6. opsx:apply — код, гейт,<br/>поведенческая верификация"]
|
||||
s7["7. ревью кода, та же метка<br/>+ отработка замечаний"]
|
||||
s8["8. opsx:archive"]
|
||||
s9["9. синк документации — av-dev-docs:docs"]
|
||||
s10["10. коммит работы — av-dev-git:commit"]
|
||||
s11["11. закрыть задачу — av-dev-tasks:tasks,<br/>вторым коммитом учёта"]
|
||||
solve["сценарий решения<br/>references/solve.md<br/>код, ревью, архив, коммит"]
|
||||
res["сценарий разведки<br/>references/research.md<br/>ответ в документы и задачи"]
|
||||
|
||||
in --> fork
|
||||
fork -->|"да"| s1
|
||||
fork -->|"нет"| r1
|
||||
r1 --> r2 --> r3
|
||||
r3 -->|"выбран способ"| s1
|
||||
r3 -.->|"кода не будет"| rout
|
||||
s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 --> s8 --> s9 --> s10 --> s11
|
||||
s3 -.->|"план задачи: та же метка"| s7
|
||||
s5 -.->|"скорректировать:<br/>меняются дельта-спеки"| s3
|
||||
s7 -.->|"находка отменяет дизайн:<br/>меняются дельта-спеки"| s3
|
||||
in --> ready --> fork
|
||||
fork -->|"да"| solve
|
||||
fork -->|"нет"| res
|
||||
solve -.->|"способа всё же нет:<br/>стоп, кода не написано"| res
|
||||
res -.->|"способ выбран:<br/>следующим прогоном,<br/>зовёт человек"| solve
|
||||
```
|
||||
|
||||
Схема — **сводка**: содержание каждого шага в его разделе ниже, и при
|
||||
расхождении прав текст.
|
||||
Схема — **сводка**: содержание сценариев в их справочниках, и при расхождении
|
||||
прав справочник.
|
||||
|
||||
## Автономность и два плановых стопа
|
||||
## Автономность и плановый стоп
|
||||
|
||||
**Между чекпоинтами умолчание прежнее — делать, а не спрашивать.** Чекпоинты не
|
||||
отменяют автономность, они дают развилкам плановое место, куда копиться.
|
||||
**У каждого сценария ровно один плановый стоп**, и стоят они в разных местах:
|
||||
у решения — объяснение после ревью дизайна, у разведки — варианты до первого
|
||||
написанного требования. Правило вокруг них общее.
|
||||
|
||||
**Вокруг чекпоинта умолчание прежнее — делать, а не спрашивать.** Чекпоинт не
|
||||
отменяет автономность, он даёт развилкам плановое место, куда копиться.
|
||||
|
||||
Разрез простой:
|
||||
|
||||
- развилка найдена **до** ближайшего чекпоинта — она его и ждёт. Не спрашивай
|
||||
отдельно: чекпоинт рядом и стоит дёшево, а три вопроса подряд стоят дороже
|
||||
одного разговора;
|
||||
- развилка найдена **после** последнего чекпоинта — старое правило: **запиши
|
||||
вопрос и доведи остаток**, не останавливаясь.
|
||||
- развилка найдена **до** чекпоинта — она его и ждёт. Не спрашивай отдельно:
|
||||
чекпоинт рядом и стоит дёшево, а три вопроса подряд стоят дороже одного
|
||||
разговора;
|
||||
- развилка найдена **после** чекпоинта — старое правило: **запиши вопрос и доведи
|
||||
остаток**, не останавливаясь.
|
||||
|
||||
Запись вопроса устроена так:
|
||||
|
||||
@@ -169,7 +197,8 @@ flowchart TD
|
||||
2. **Переформулируй задачу на остаток** — то, что делается без этого решения.
|
||||
Назови границу: докуда доводим сейчас.
|
||||
3. Доведи остаток до конца и закоммить. Задача не «висит на вопросе», она сделана
|
||||
в объявленных границах.
|
||||
в объявленных границах. В разведке остаток — это ответ в объявленных рамках:
|
||||
что успели узнать, где остановились и почему.
|
||||
|
||||
**Что остатком не является — правило живёт не здесь.** Канонический текст с обеими
|
||||
оговорками — в плагине `av-dev-tasks`, скилл `av-dev-tasks:groom`, раздел
|
||||
@@ -192,7 +221,7 @@ flowchart TD
|
||||
Нет полезного остатка — задача заканчивается исходом «не доведена», вопрос
|
||||
записан, ничего не коммитится наполовину.
|
||||
|
||||
### Когда спрашивать вне чекпоинтов
|
||||
### Когда спрашивать вне чекпоинта
|
||||
|
||||
По другому основанию — не «сложное решение», а **необратимое действие**:
|
||||
|
||||
@@ -203,453 +232,60 @@ flowchart TD
|
||||
Здесь ошибка не откатывается коммитом, поэтому спрашиваем даже когда решение
|
||||
кажется очевидным.
|
||||
|
||||
Стиль правок — заточка под проект и конвенции, right-size, без золочения.
|
||||
|
||||
## Границы: чем этот скилл не владеет
|
||||
|
||||
- **Беклогом, целями и приоритетами.** Задача приходит извне. Скилл её не
|
||||
выбирает, не приоритизирует, не заводит и не переоценивает.
|
||||
- **Форматом задач.** Индексы руками не правятся, путь к скрипту учёта не
|
||||
выдумывается: этим владеет `av-dev-tasks:tasks` (шаг 11). Закрытие — работа
|
||||
этого скилла, и это осознанное решение с названной ценой: **приёмщик и
|
||||
исполнитель совпали**. Закрытие поэтому **не окончательно** — человек на
|
||||
- **Форматом задач и документов.** Индексы и документы канона руками не правятся,
|
||||
путь к чужому скрипту не выдумывается: этим владеют `av-dev-tasks:tasks` и
|
||||
`av-dev-docs:docs`. Закрытие — работа этого скилла, и это осознанное решение с
|
||||
названной ценой: **приёмщик и исполнитель совпали**. Закрытие поэтому **не окончательно** — человек на
|
||||
груминге (`av-dev-tasks:groom`) возвращает задачу `reopen` с причиной, а доклад
|
||||
по критериям приёмки становится единственным, по чему приёмка вообще возможна.
|
||||
- **Заведением задач из урожая ревью.** Отложенные находки отдаются **списком**;
|
||||
превращать их в задачи — работа `av-dev-tasks:tasks`, у него на этот вход
|
||||
отдельный сценарий «задачи из ревью и аудита». Плагина нет — урожай остаётся
|
||||
списком в докладе, и это говорится строкой.
|
||||
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос этого
|
||||
скилла ни на одном шаге. Чекпоинт спрашивает «так ли решаем», а не «надо ли».
|
||||
скилла ни на одном шаге и ни в одном сценарии. Чекпоинт решения спрашивает «так
|
||||
ли решаем», чекпоинт разведки — «каким из способов», но не «надо ли».
|
||||
|
||||
Что не принадлежит **отдельному сценарию**, названо у него же: урожай ревью и
|
||||
выбор способа — в [solve.md](references/solve.md), код и приоритет — в
|
||||
[research.md](references/research.md).
|
||||
|
||||
## Наблюдаемые исходы
|
||||
|
||||
Ровно четыре, и каждый обязан быть назван в докладе прямо:
|
||||
|
||||
- **сделана** — определение сделанного выполнено целиком;
|
||||
- **не доведена** — с причиной и с записанным вопросом; что именно сделано и до
|
||||
какой границы, названо явно. Сюда же попадает чекпоинт, на котором человек
|
||||
решение не одобрил;
|
||||
- **оказалась крупнее задачи** — распознаётся **до заведения change**, иначе его
|
||||
придётся выбрасывать. Дальше — декомпозиция, и это не работа этого скилла;
|
||||
- **знание вместо изменения** — только у исследовательской ветки: ответ записан
|
||||
по названному адресу, кода задача не потребовала. Это полноправный исход, а не
|
||||
недоведённая работа.
|
||||
|
||||
## Определение сделанного
|
||||
|
||||
Задача сделана, когда верно всё:
|
||||
|
||||
1. гейт проекта зелёный;
|
||||
2. ревью проведено, **план прогона сверен с исходом по каждой теме**, темы без
|
||||
отчёта и без дома названы в границах покрытия;
|
||||
3. решение прошло чекпоинт и не разошлось с одобренным — либо разошлось, и
|
||||
чекпоинт был пройден заново;
|
||||
4. change заархивирован, дельты влиты в актуальные спеки;
|
||||
5. коммит сделан в текущую ветку;
|
||||
6. **критерии приёмки, если проект их дал, выписаны поимённо, и по каждому назван
|
||||
оракул и наблюдаемый исход** — «прогнал вот это, увидел вот то». Это **доклад,
|
||||
а не сертификация: приёмка — не работа этого скилла.** Исполнитель, ставящий
|
||||
себе галочку «принято», проверяет свою работу своим же взглядом — по границе
|
||||
это может делать только приёмщик, разведённый с исполнителем. Критерии приходят
|
||||
снаружи; скилл их не сочиняет и не занижает. Расхождение «по каждому критерию
|
||||
исход есть, а суть задачи не достигнута» — дефект критериев, и о нём
|
||||
сообщается, а не молча дорабатывается.
|
||||
|
||||
У разведки, кончившейся знанием, определение своё и короткое: **ответ записан по
|
||||
адресу, который назвала задача**, и в нём есть провенанс у каждого числа.
|
||||
|
||||
## Исследовательская ветка
|
||||
|
||||
### Р1. Понять вопрос
|
||||
|
||||
Прочитай запись. У типа `research` в ней два обязательных раздела, и оба нужны
|
||||
тебе прямо сейчас: **«Вопрос»** — на что отвечаем, **«Куда ляжет ответ»** — по
|
||||
какому адресу он ляжет. Адрес назван заранее не из аккуратности: ответ, не
|
||||
имеющий дома, остаётся в переписке, и через квартал разведку заказывают заново.
|
||||
|
||||
Вопроса нет — исход «не доведена» с причиной «запись это сырьё»: назови, что
|
||||
нужно дописать, и остановись. Адреса нет, а вопрос есть — назначь адрес сам и
|
||||
скажи об этом строкой: разведка без дома для ответа хуже несделанной.
|
||||
|
||||
Задача не из каталога (пришла текстом) — адреса у неё нет по построению. Тогда
|
||||
дом ответа — `design.md` того change, который родится дальше; кода не будет —
|
||||
`docs/research/`, и это тоже говорится строкой.
|
||||
|
||||
### Р2. Груминг — `opsx:explore`
|
||||
|
||||
Вызови Skill `opsx:explore`. Читай документы проекта, а не только запись:
|
||||
граница домена и «чем проект **не** является» из паспорта отсекают половину
|
||||
вариантов до того, как их начнут сравнивать. **В explore не пишем код.**
|
||||
|
||||
Развилку грумминга **не записывай вопросом** — она и есть предмет следующего
|
||||
шага. Это отличие от обычной ветки: там развилка уходит в запись, здесь она
|
||||
копится в чекпоинт.
|
||||
|
||||
### Р3. Чекпоинт: варианты
|
||||
|
||||
**Остановись и покажи человеку способы решить.** Это первый из двух плановых
|
||||
стопов, и он существует потому, что после `propose` выбор уже сделан
|
||||
предложением.
|
||||
|
||||
Форма — короткая, экран текста:
|
||||
|
||||
- **вопрос**, на который отвечаем, одной фразой (он уже есть в записи);
|
||||
- **2–4 варианта**, не больше. Больше четырёх — это не выбор, а список: человек
|
||||
не сравнит, а признает свою неспособность сравнить и попросит рекомендацию.
|
||||
У каждого варианта: в чём суть простыми словами, **что даёт**, **чего стоит**,
|
||||
**что становится невозможным** (это ловится хуже всего и стоит дороже всего);
|
||||
- **рекомендация с причиной**. Человек чаще соглашается, чем выбирает заново;
|
||||
- что известно **недостоверно** и как это проверить, если проверять дёшево.
|
||||
|
||||
Что нельзя: приносить варианты, различающиеся только реализацией; прятать
|
||||
отвергнутое (отвергнутый вариант с названной причиной — половина будущего ADR);
|
||||
приносить один вариант и называть это выбором.
|
||||
|
||||
**Выбор оседает по адресу.** У `research` это раздел «Куда ляжет ответ». У
|
||||
остальных — `design.md` change, который родится на шаге 2, разделом
|
||||
«рассмотренные варианты». Не в переписку: разговор, из которого ничего не
|
||||
записано, повторяется через месяц целиком.
|
||||
|
||||
Три исхода чекпоинта:
|
||||
|
||||
- **выбран способ** — идёшь на шаг 1 общей ветки;
|
||||
- **ответ и есть исход** — работа кончается знанием: запиши ответ по адресу,
|
||||
доложи исход «знание вместо изменения» и закрой задачу (шаг 11). Провенанс у
|
||||
каждого числа обязателен: число без источника проход ревью обязан читать как
|
||||
условие, а не как замер;
|
||||
- **разведка родила задачи** — исход «оказалась крупнее задачи». Нарезка не твоя
|
||||
работа: отдай список формулировками и остановись.
|
||||
|
||||
## Шаги
|
||||
|
||||
### 1. Прочитать задачу
|
||||
|
||||
Прочитай запись и связанные спеки и черновики.
|
||||
|
||||
Проект даёт задаче **критерии приёмки** — выпиши их сразу: на шаге 2 они уезжают
|
||||
в `tasks.md` change. Файл задачи может быть удалён до коммита, а критерии обязаны
|
||||
его пережить.
|
||||
|
||||
Здесь же проверка на «крупнее задачи»: видно, что одним заходом это не
|
||||
мерджится, — объявляй исход **до** заведения change.
|
||||
|
||||
### 2. Завести change — `opsx:propose`
|
||||
|
||||
Вызови Skill `opsx:propose`. Получаем `proposal.md`, дизайн, дельта-спеки
|
||||
(`ADDED`/`MODIFIED`/`REMOVED Requirements`), `tasks.md`. Каждое `### Requirement`
|
||||
содержит `SHALL`/`MUST`; структурные заголовки английские, сценарии
|
||||
`GIVEN/WHEN/THEN`. Прогони `openspec validate --strict <id>`.
|
||||
|
||||
Критерии приёмки задачи, если они были, копируются в `tasks.md` отдельным блоком.
|
||||
Варианты, разобранные на чекпоинте Р3, — в `design.md`, с причиной отказа по
|
||||
каждому отвергнутому.
|
||||
|
||||
**`proposal.md` пишется так, чтобы его понял человек, не читавший спек.** Это не
|
||||
стилистическое пожелание: из него собирается чекпоинт шага 5, и переписывать его
|
||||
там заново значит завести второй дом для одного объяснения. Требование стоит в
|
||||
`openspec/config.yaml`, `rules.proposal` — то есть применяется в момент
|
||||
порождения артефакта, а не вспоминается после.
|
||||
|
||||
### 3. Разметка задачи — агент `review-scope`
|
||||
|
||||
**Один запуск на всю задачу, и он обслуживает оба чекпоинта ревью.** Запусти
|
||||
агента `review-scope`, дав ему корень проекта, идентификатор change, базу диффа и
|
||||
запись задачи. Кода на этот момент нет, и это условие его работы, а не помеха.
|
||||
|
||||
Он возвращает **план задачи**:
|
||||
|
||||
- **размер** (малое / среднее / крупное) и **сложность** (знакомое /
|
||||
незнакомое), каждое с обоснованием по факту;
|
||||
- **метку** как максимум по двум осям: `small`, `medium` или `large`;
|
||||
- **состав ревью дизайна** — что звать на шаге 4;
|
||||
- **таблицу тем** «тема → дом → глубина → кто закрывает» — для шага 7;
|
||||
- разнесение документов проекта по трём категориям и строку про директивы.
|
||||
|
||||
**Метку выбираешь не ты.** Раньше состав ревью дизайна называл сам оркестратор —
|
||||
то есть тот, кто только что довёл предложение до `propose`. Разведённости с
|
||||
автором в этой точке не было вовсе; теперь есть.
|
||||
|
||||
**План держи в контексте до конца задачи.** На диск он не пишется: файл-план стал
|
||||
бы четвёртым артефактом рядом с `proposal.md`, `tasks.md` и `design.md`, пережил
|
||||
бы задачу и разошёлся бы с ней молча. Прервался прогон — повтори шаг 3, это самый
|
||||
дешёвый его проход.
|
||||
|
||||
**Разметка повторяется ровно в одном случае** — если правки изменили сами
|
||||
**дельта-спеки**: план выведен из них, и план по отменённым требованиям назовёт
|
||||
не те темы. Во всех прочих случаях, включая переделку формы кода на шаге 7,
|
||||
метка остаётся прежней.
|
||||
|
||||
### 4. Ревью дизайна — ДО кода, состав по метке
|
||||
|
||||
Вызови Skill **`av-dev-code:review`**, дав ссылку на change `<id>`,
|
||||
**план разметки с шага 3** и указание, что это ревью дизайна.
|
||||
|
||||
Состав приходит планом, а не решается здесь:
|
||||
|
||||
| Метка | Проходы на предложении |
|
||||
|---|---|
|
||||
| `small` | `specs` |
|
||||
| `medium` | `specs`, `rubric` |
|
||||
| `large` | `specs`, `rubric`, `architecture` + вопрос автору о трёх формах решения |
|
||||
|
||||
`review-specs` в режиме «дизайн ДО кода» идёт **на каждой задаче**: это самый
|
||||
дешёвый проход конвейера, и он ловит то, что на готовом коде уже не чинят.
|
||||
Остальные включаются меткой, потому что стадия стоит на каждой задаче и каждый
|
||||
лишний проход здесь умножается на число задач.
|
||||
|
||||
Смысл стадии: архитектурная находка на готовом коде стоит переписывания и потому
|
||||
игнорируется — та же находка здесь стоит абзаца обсуждения. Если `review-rubric`
|
||||
запускался, перенеси его рубрику в `tasks.md` как приёмочные критерии; там же уже
|
||||
лежат критерии от постановки, если они были.
|
||||
|
||||
**Отработка замечаний, и она идёт до чекпоинта, а не после:**
|
||||
|
||||
- мелочь и явные улучшения — правь сам в спеках и дизайне;
|
||||
- развилки (компромисс, scope, инвариант) — **не в запись, а в чекпоинт**: он
|
||||
следующим шагом, и это ровно то, ради чего он поставлен здесь;
|
||||
- после правок перепрогони `openspec validate --strict <id>`.
|
||||
|
||||
### 5. Чекпоинт: объяснение
|
||||
|
||||
**Остановись и объясни человеку, что происходит.** Второй плановый стоп и
|
||||
единственный обязательный для всех задач.
|
||||
|
||||
Он стоит **после** ревью дизайна намеренно. Человек читает объяснение, уже
|
||||
просеянное машиной: то, что поймал бы `review-specs`, до него не доходит, а
|
||||
внимание — самый дорогой ресурс процесса, и тратить его на выловимое машиной
|
||||
нельзя.
|
||||
|
||||
**Объяснение не сочиняется заново — оно собирается из артефактов**, `proposal.md`
|
||||
и `design.md`. Третий пересказ был бы третьим домом одного и того же и разошёлся
|
||||
бы с обоими. Что показываешь:
|
||||
|
||||
- **в чём проблема** — словами домена из паспорта, без имён модулей и функций;
|
||||
- **как решаем** — суть одним абзацем. Не пересказ дельта-спек: спека нормирует,
|
||||
а здесь объясняют;
|
||||
- **что человек увидит иначе**, когда это будет сделано;
|
||||
- **чего мы намеренно не делаем** и почему — граница scope ловится хуже всего;
|
||||
- **чем рискуем и что осталось нерешённым** — сюда съезжаются развилки,
|
||||
накопленные до этого места, и находки ревью с пометкой `развилка`;
|
||||
- **что дальше**, если возражений нет.
|
||||
|
||||
Проверка на «простой язык» одна и механическая: **в тексте нет `SHALL`, нет имён
|
||||
файлов и функций там, где вещь называется по-русски, и нет слов, которых нет в
|
||||
паспорте проекта.** Не проходит — переписывай, а не объясняй, почему иначе
|
||||
нельзя.
|
||||
|
||||
Длина — экран. Длинное объяснение не читают, а пролистывают, и чекпоинт
|
||||
превращается в ритуал одобрения.
|
||||
|
||||
Три исхода:
|
||||
|
||||
- **согласен** — идёшь на шаг 6;
|
||||
- **скорректировать** — правишь спеки и дизайн по сказанному. Изменились
|
||||
**дельта-спеки** — повтори шаг 3 (разметка выведена из них) и ту часть ревью
|
||||
дизайна, которой касается правка; затем чекпоинт **заново**. Правка внутри
|
||||
дизайна без спек — повтори только чекпоинт;
|
||||
- **не одобрено** — исход «не доведена» с причиной. Change остаётся
|
||||
незаархивированным, задача не закрывается, ничего не коммитится наполовину.
|
||||
|
||||
### 6. Написать код — `opsx:apply`
|
||||
|
||||
Вызови Skill `opsx:apply` для реализации `tasks.md`. Код — по конвенциям проекта
|
||||
(каталог `docs/conventions/`). Меняешь схему — обнови её описание в документации
|
||||
тем же change, если проект этого требует: гейт обычно это проверяет.
|
||||
|
||||
Прогони гейт и добейся зелёного — он же гейт следующего шага.
|
||||
|
||||
**Поведенческая верификация.** Если задача меняет реальное поведение (новый
|
||||
эндпоинт, разбор входа, схема, форма ответа) — зелёных юнит-тестов мало. Подними
|
||||
изменение вживую командой из раздела команд `CLAUDE.md` и прогони сценарий.
|
||||
Пропусти только для чисто внутренних правок без наблюдаемого рантайма.
|
||||
|
||||
**Сервис не оставляем лежать.** Если запуск упал — почини или откати до конца
|
||||
шага.
|
||||
|
||||
### 7. Ревью кода — та же метка
|
||||
|
||||
Вызови Skill **`av-dev-code:review`**, дав ссылку на change `<id>`,
|
||||
базу диффа, **план разметки с шага 3** и режим запуска.
|
||||
|
||||
**Метку ты не выбираешь, и это правило, а не упрощение.** Её назвал
|
||||
`review-scope` ещё на шаге 3 — по размеру и сложности, с обоснованием по каждой
|
||||
оси. Причина в разведённости: ты только что написал этот код, и решать, насколько
|
||||
глубоко его проверять, тебе нельзя — под давлением «я почти закончил» решение
|
||||
известно заранее. Правило выбора живёт в скилле конвейера —
|
||||
`av-dev-code:review`, `references/review-levels.md`; проектные
|
||||
триггеры — в `docs/review.*`, подраздел «Триггеры метки».
|
||||
|
||||
**Метка не пересматривается по факту диффа.** Дифф может выйти крупнее, чем
|
||||
ожидалось при разметке, — это не повод её поднимать: пересмотр означал бы второй
|
||||
запуск разметчика, ровно то, ради устранения чего он и переехал на шаг 3.
|
||||
|
||||
**Считаешь метку заниженной — скажи это в докладе строкой, а не переспорь.**
|
||||
Разметчик вправе и поднять, и понизить; твоё несогласие это факт для человека, а
|
||||
не команда конвейеру. Место, где такое несогласие превращается в изменение
|
||||
правил, — журнал дефектов `docs/review.md`, и только постфактум.
|
||||
|
||||
**Плана нет — ревью кода не запускается.** Триаж требует план обязательным
|
||||
входом: без него он не может сверить, все ли размеченные темы вернули отчёт, а
|
||||
эта сверка — единственная защита от молчащего пропуска. Потерял план (прервалась
|
||||
сессия, ушёл контекст) — повтори шаг 3, а не гони прогон без него.
|
||||
|
||||
**Режим по умолчанию — `по графу`, и обосновывать его не надо.** Конвейер сам
|
||||
знает свои рёбра: гейт открывает опиниативные проходы, проходы с пометкой «держит
|
||||
машину» идут цепочкой (иначе замеры портят друг друга и находка выглядит
|
||||
доказанной), триаж — сток. Просить **`линейно`** нужно только по причине, и она
|
||||
называется строкой: так сказал оператор; машина занята чем-то ещё; идёт разбор
|
||||
самого конвейера.
|
||||
|
||||
Скилл сам гоняет гейт, нужные проходы и обязательный триаж. Возвращает отчёт с
|
||||
потолком 7 пунктов, разметкой `Действие: инлайн | развилка` и секцией границ
|
||||
покрытия.
|
||||
|
||||
**Сверь план прогона с исходом, прежде чем коммитить.** Отчёт начинается планом
|
||||
разметчика — таблицей «тема → дом → глубина → кто закрывает», — и против каждой
|
||||
темы обязан стоять исход. Тема без отчёта и тема без дома — разные вещи, и обе
|
||||
должны быть названы. Реестр короткий (шесть тем ядра плюс свои) — сверка стоит
|
||||
одного взгляда.
|
||||
|
||||
#### Отработка, и здесь появляется одно новое правило
|
||||
|
||||
Помеченное `инлайн` чини сам и не логируй. `развилка` — вопросом в запись (он уже
|
||||
сформулирован триажем, его остаётся перенести). После правок — снова гейт.
|
||||
|
||||
**Находка, отменяющая одобренный дизайн, отменяет и одобрение.** Признак
|
||||
проверяемый: **меняются ли дельта-спеки**.
|
||||
|
||||
- не меняются — находка внутри дизайна, дожимай сам, это обычная отработка;
|
||||
- меняются — решение стало другим, а одобрено было прежнее. Повтори шаг 3
|
||||
(разметка выведена из дельта-спек) и **вернись на чекпоинт шага 5** с тем, что
|
||||
изменилось и почему.
|
||||
|
||||
**Это правило старше правила о развилке.** Находка класса `развилка`, чьё
|
||||
основание — «надо менять спеку», подпадает под оба; побеждает возврат на
|
||||
чекпоинт, а вопрос в запись при нём остаётся, но возврата не заменяет. Иначе
|
||||
одобренный дизайн переделывался бы записанным вопросом, то есть молча.
|
||||
|
||||
Тихо переделать утверждённое нельзя. Чекпоинт, который можно обойти находкой
|
||||
ревью, не значит ничего, а человек при этом уверен, что одобрил именно то, что
|
||||
уехало в коммит.
|
||||
|
||||
**Урожай — списком, не задачами.** Отложенные находки (реальный `major` не для
|
||||
этого мерджа, развилка, решённая «потом», пачка `nit`) собери в секцию доклада
|
||||
`Урожай`: формулировка, оракул, провенанс. Задачи из него **заводит не этот
|
||||
скилл** — их заводит `av-dev-tasks:tasks` своим сценарием «задачи из ревью и
|
||||
аудита»: своя нарезка, свой формат, свои правила дублей. Твоя обязанность — не
|
||||
потерять и передать.
|
||||
|
||||
**Границы покрытия из отчёта не выбрасывай** — они уезжают в финальный доклад
|
||||
сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно»,
|
||||
превращается в ложное ощущение проверенности.
|
||||
|
||||
**Отчёт триажа сохрани вместе с change (`openspec/changes/<id>/review/`; шаг 8
|
||||
унесёт его в архив вместе с change) — это обязательно, а не «если удобно».** По
|
||||
нему потом видно, что было найдено и что из этого осталось в урожае. И это
|
||||
единственный **независимый** артефакт о составе прогона: своей прозе здесь верить
|
||||
нельзя — она написана тем же, кто мог проход и пропустить.
|
||||
|
||||
### 8. Архивировать — `opsx:archive`
|
||||
|
||||
Вызови Skill `opsx:archive`: change уезжает в архив, дельты вливаются в
|
||||
актуальные спеки. Не пропускай `openspec validate --strict` перед этим.
|
||||
|
||||
### 9. Синк документации
|
||||
|
||||
**Вызови Skill `av-dev-docs:docs`**: он владеет содержимым документов канона и
|
||||
ведёт чек-лист синка.
|
||||
|
||||
**Правило одно и оно жёсткое: принуждённое отрицание.** Доклад обязан назвать
|
||||
**каждый** документ канона — либо чем он обновлён, либо «не требуется, потому
|
||||
что…». Нетронутые группируются одной строкой с общей причиной. Список триггеров
|
||||
прозой уже проверен на живом проекте и дал 6 записей ADR на 43 изменения;
|
||||
работает только обязательное отрицание.
|
||||
|
||||
**Список документов и их триггеров здесь не дублируется** — он в чек-листе скилла
|
||||
`av-dev-docs:docs`, и копия уже однажды разошлась с оригиналом, потеряв два
|
||||
триггера.
|
||||
|
||||
**Плагина `av-dev-docs` в проекте нет** — путь в его дерево не разрешится ниоткуда,
|
||||
поэтому за списком иди в **свой** reference:
|
||||
[references/project-facts.md](../review/references/project-facts.md)
|
||||
конвейера ревью перечисляет все документы канона с их предметом. Пройди по этому
|
||||
перечню — каждый документ получает строку, отрицание остаётся обязательным.
|
||||
|
||||
**Двух домов в том перечне нет намеренно, а синку они нужны: `docs/adr/` и
|
||||
`docs/research/`.** Проход ревью их не открывает — процессные, — но ADR как раз
|
||||
**главный выход синка**: решение, принятое по ходу задачи, без этой строки
|
||||
теряется систематически. Добавь их к перечню руками: `adr/` — решение с ценой,
|
||||
принятое в этой задаче; `research/` — записка разведки, если ветка была
|
||||
исследовательской.
|
||||
Триггеры при этом ты знаешь хуже, и это называется в докладе строкой: «синк
|
||||
сделан по перечню документов, без списка триггеров — плагина `av-dev-docs` нет».
|
||||
Канона в проекте тоже нет — назови это исходом и предложи `av-dev-docs:canon`.
|
||||
|
||||
### 10. Коммит
|
||||
|
||||
Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не
|
||||
создавай и не переключай, ничего не пушь.
|
||||
|
||||
Сообщение — по-русски, **скиллом `av-dev-git:commit`**: форму сообщения держит
|
||||
он, и здесь она не пересказывается. Вызов не разрешился — плагина в проекте нет:
|
||||
напиши сообщение сам и скажи строкой доклада, что форму коммита не сверял никто.
|
||||
Одна задача — один осмысленный коммит.
|
||||
|
||||
### 11. Закрыть задачу — **после коммита, не раньше**
|
||||
|
||||
**Вызови Skill `av-dev-tasks:tasks`** и попроси закрыть задачу как реализованную —
|
||||
он владеет форматом и двигает строку индекса сам. Путь к его скрипту не выясняй и
|
||||
индексы руками не правь: мост между плагинами — вызов скилла, а не путь.
|
||||
|
||||
**Порядок обязателен.** Закрытие удаляет файл задачи; сделанное до коммита оно
|
||||
оставило бы задачу закрытой без единого следа работы, если шаг 10 упадёт.
|
||||
|
||||
**Закрытие тоже коммитится — вторым коммитом, тут же.** Удаление файла задачи и
|
||||
правка индексов (их имена знает `av-dev-tasks`, не ты) — это правки в рабочем
|
||||
дереве, и оставить их незакоммиченными нельзя: закрытие, не доехавшее до основной
|
||||
ветки, оставит задачу открытой молча, а опора «индексы под git показывают, что и
|
||||
когда закрыто» без коммита — пустые слова. Сообщение короткое, про учёт, а не про
|
||||
работу: `закрыта задача <slug>`. Это второй коммит осознанно: правило «одна задача
|
||||
— один осмысленный коммит» про работу, а учёт — не работа.
|
||||
|
||||
Разведка, кончившаяся знанием, закрывается так же — но перед этим убедись, что
|
||||
ответ **записан по названному адресу и закоммичен**. Закрытая разведка без
|
||||
записанного ответа не оставляет следа вообще: файл задачи удалён, ответ был в
|
||||
переписке.
|
||||
|
||||
Плагина в проекте нет — вызов не разрешится. Тогда **ничего не выдумывай**: скажи
|
||||
в докладе, что учёт задач остаётся за владельцем, и назови исход.
|
||||
**У каждого сценария их четыре**, и живут они у сценария:
|
||||
[решение](references/solve.md) — сделана, не доведена, оказалась крупнее задачи,
|
||||
нужна разведка; [разведка](references/research.md) — способ выбран, знание
|
||||
записано, отказ, не доведена.
|
||||
|
||||
Общего исхода нет намеренно. «Сделана» у решения и «знание записано» у разведки —
|
||||
разные вещи с разной приёмкой, и слово, накрывающее оба, скрывало бы именно то,
|
||||
чем прогон кончился.
|
||||
|
||||
## Доклад
|
||||
|
||||
Коротко, и в нём обязательно:
|
||||
Ядро общее, и в нём обязательно:
|
||||
|
||||
- **исход** одним из четырёх слов и, если не «сделана», чем ограничен результат;
|
||||
- **какой сценарий шёл** — решение или разведка, — и почему выбран он;
|
||||
- **исход** одним из четырёх слов своего сценария и, если он не благополучный,
|
||||
чем ограничен результат;
|
||||
- что сделано, какие вопросы записаны и куда;
|
||||
- **что было одобрено на чекпоинте и совпало ли с тем, что уехало в коммит** —
|
||||
расхождение здесь называется прямо, даже если оно мелкое;
|
||||
- ссылка на архивный change и хеш коммита;
|
||||
- по каждому критерию приёмки, если они были: **оракул и наблюдаемый исход** —
|
||||
это доклад приёмщику, а не отметка «принято»;
|
||||
- **`Урожай`** — отложенные находки списком (формулировка, оракул, провенанс);
|
||||
- **одна строка границ покрытия**: какая метка и режим гонялись, какие проходы не
|
||||
запускались и что проверить было невозможно. Доклад без неё сообщает
|
||||
«проверено», не сообщая, что именно.
|
||||
- чего проверить или узнать **не удалось**.
|
||||
|
||||
Сверх ядра каждый сценарий добавляет своё: [solve.md](references/solve.md) —
|
||||
чекпоинт, change, критерии приёмки, урожай и границы покрытия;
|
||||
[research.md](references/research.md) — вопрос и ответ, адреса записи, заведённые
|
||||
задачи, рамки.
|
||||
|
||||
## Тонкости
|
||||
|
||||
- **Не завязывайся на основную ветку и корень репозитория.** Скилл работает в
|
||||
текущем worktree и на текущей ветке: не делай `git checkout`/`switch`, не
|
||||
создавай веток, не пушь.
|
||||
- Обычная задача с очевидным решением проходит **один** чекпоинт, и это норма, а
|
||||
не упрощение. Два чекпоинта — цена незнания, а не признак серьёзности задачи.
|
||||
- Гейт блокирует: пока он красный, опиниативные проходы не запускаются. Чинить и
|
||||
перезапускать, а не «посмотреть заодно».
|
||||
- Прогон проходит **один** чекпоинт, и это норма, а не упрощение. Два стопа за
|
||||
одну задачу — цена незнания способа, и платится она двумя прогонами, а не одним
|
||||
длинным.
|
||||
- Держи вызывающего в цикле короткими репликами на переходах фаз, но не проси
|
||||
подтверждать механику: чекпоинты — единственные места, где ждут ответа.
|
||||
- **Занизить метку ревью, пропустить тему или проскочить чекпоинт — самый дешёвый
|
||||
способ «ускориться», и он же самый дорогой по последствиям.** Защита устроена
|
||||
так, что регулятора у тебя нет: метку выбирает разметчик **до того**, как ты
|
||||
написал код, план сверяется по темам, непокрытое называется строкой, а
|
||||
расхождение с одобренным — отдельным пунктом доклада.
|
||||
подтверждать механику: чекпоинт — единственное место, где ждут ответа.
|
||||
- **Сценарий назван вслух — значит, его можно оспорить.** Человек, увидевший в
|
||||
первой реплике «иду разведкой, потому что способа не видно», поправит выбор
|
||||
одной фразой; молча выбранный сценарий он поправит через полчаса работы.
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
# Сценарий «разведка»
|
||||
|
||||
Отвечает на вопрос задачи и записывает ответ туда, где он переживёт переписку.
|
||||
Исход — **знание**: уточнённые документы и уточнённые задачи. **Кода этот
|
||||
сценарий не пишет и change не заводит.**
|
||||
|
||||
Сценарий выбирается развилкой на входе скилла ([SKILL.md](../SKILL.md), раздел
|
||||
«Развилка: какой сценарий») и называется вслух первой репликой. Здесь только его
|
||||
ход; общее для обоих сценариев — вход, обращение к соседним плагинам, правило
|
||||
необратимого, доклад — живёт в SKILL.md и тут не пересказывается.
|
||||
|
||||
**Разведка кончается своим исходом, а не переходом к коду.** Выбранный способ
|
||||
реализует сценарий решения, и запускает его **человек**, следующим прогоном по
|
||||
уточнённой записи. Причина не в церемонии: разведка только что переписала
|
||||
постановку, и брать её в работу тем же заходом значит решать за человека, стоит
|
||||
ли делать это сейчас, — а это приоритет, и он не наш.
|
||||
|
||||
## OpenSpec здесь не предпосылка
|
||||
|
||||
**OpenSpec этому сценарию не нужен**, и это единственное место скилла, где он не
|
||||
предпосылка. Разведка не заводит change и не пишет дельта-спеки: её артефакты —
|
||||
документы канона и записи каталога задач. Скилл `opsx:explore` полезен и зовётся,
|
||||
когда плагин в проекте есть; не разрешился — разведка идёт чтением документов,
|
||||
кода и внешних источников, и это говорится строкой доклада, а не отменяет
|
||||
работу.
|
||||
|
||||
## Кого зовёт этот сценарий
|
||||
|
||||
`av-dev-docs:docs` (ответ уезжает в документы канона), `av-dev-tasks:tasks`
|
||||
(задачи заводятся и уточняются), `av-dev-git:commit`. Правило обращения к соседям
|
||||
и ветка «вызов не разрешился» — общие, они в [SKILL.md](../SKILL.md).
|
||||
|
||||
**Отсутствие канона бьёт по разведке сильнее, чем по решению**, и сказать об этом
|
||||
строкой мало: без документов у ответа нет дома, и знание осядет в переписке.
|
||||
Назови исход и предложи `av-dev-docs:canon`; работу не останавливай, но адрес
|
||||
ответа тогда выбираешь сам и говоришь об этом вслух.
|
||||
|
||||
## Что этот сценарий требует от входа
|
||||
|
||||
Вход общий у обоих сценариев (SKILL.md, раздел «Вход»); своего здесь три условия.
|
||||
|
||||
**Паспорт читается раньше кода.** Граница домена и «чем проект **не** является»
|
||||
отсекают половину вариантов до того, как их начнут сравнивать, — а сравнение
|
||||
вариантов и есть работа этого сценария.
|
||||
|
||||
**Сырьё — `research` без раздела «Вопрос» — не берётся.** Исход «не доведена» с
|
||||
причиной «запись это сырьё»: у неё нет вопроса, а разведка без вопроса
|
||||
превращается в чтение всего подряд с отчётом «интересно, но неприменимо».
|
||||
Дописывать вопрос за автора не твоя работа — этим занят штурм сырья в
|
||||
`av-dev-tasks:tasks`. Назови, чего не хватает, и остановись.
|
||||
|
||||
**Разведка пришла текстом — сформулируй вопрос сам одной фразой и покажи
|
||||
формулировку в первой же реплике.** Разведка, чей вопрос не назван вслух,
|
||||
признаётся удавшейся любым результатом.
|
||||
|
||||
## Ход работы
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
in["вход: файл, слаг или текст"]
|
||||
s1["1. вопрос и рамки<br/>сырьё без «Вопроса» — отказ"]
|
||||
s2["2. разведка: документы, код,<br/>внешние источники, opsx:explore"]
|
||||
s3(["3. ЧЕКПОИНТ: варианты<br/>2–4 способа, цена каждого,<br/>что становится невозможным"])
|
||||
s4["4. ответ в документы канона<br/>av-dev-docs:docs"]
|
||||
s5["5. задачи: завести и уточнить<br/>av-dev-tasks:tasks"]
|
||||
s6["6. гейт проекта, затем коммит<br/>av-dev-git:commit"]
|
||||
s7["7. закрыть разведку — av-dev-tasks:tasks,<br/>вторым коммитом учёта"]
|
||||
out["исход назван: знание, задачи,<br/>отказ или «не доведена»"]
|
||||
|
||||
in --> s1 --> s2 --> s3
|
||||
s3 -->|"выбран способ,<br/>отказ или знание"| s4
|
||||
s3 -.->|"вопрос не тот"| s1
|
||||
s4 --> s5 --> s6 --> s7 --> out
|
||||
```
|
||||
|
||||
Схема — **сводка**: содержание каждого шага в его разделе ниже, и при расхождении
|
||||
прав текст.
|
||||
|
||||
## Плановый стоп сценария
|
||||
|
||||
**До чекпоинта умолчание прежнее — делать, а не спрашивать.** Развилка, найденная
|
||||
по ходу разведки, не задаётся отдельным вопросом: она копится в чекпоинт, который
|
||||
рядом и стоит дёшево.
|
||||
|
||||
Стоп здесь один, и стоит он **перед записью**. Причина в цене: пока варианты
|
||||
живут в контексте, смена решения стоит абзаца; записанный в документы и разложенный
|
||||
на задачи выбор стоит правки документов и разбора беклога. Второго стопа — «покажи,
|
||||
что именно уедет в документы» — нет намеренно: всё, что пишет разведка, лежит в
|
||||
git и читается диффом, а второй стоп на каждой разведке вырождается в ритуал
|
||||
одобрения.
|
||||
|
||||
**Правило необратимого действует и здесь** (SKILL.md, «Когда спрашивать вне
|
||||
чекпоинта»), и разведка обманчива: она кажется безобидной, а замер лезет туда, где
|
||||
живут данные — прогон на боевой базе, запрос к внешнему платному источнику. Здесь
|
||||
ошибка не откатывается правкой текста.
|
||||
|
||||
## Границы: чего разведка не делает
|
||||
|
||||
- **Кодом.** Ни строки, включая «маленький черновик, чтобы проверить». Замер,
|
||||
требующий кода, — это отдельная задача, и её нужно назвать, а не написать по
|
||||
ходу. Исключение ровно одно и оно не про изменение системы: одноразовый
|
||||
**читающий** прогон (запрос, замер, скрипт в песочнице), чей результат уезжает
|
||||
в ответ с провенансом и который ничего не оставляет в репозитории.
|
||||
- **Приоритетом.** Заведённая задача встаёт в конец своей секции; где ей стоять
|
||||
в очереди, решает человек на груминге (`av-dev-tasks:groom`). Разведка, сама
|
||||
ставящая свой исход первым в беклоге, назначает приоритет тому, что только что
|
||||
придумала.
|
||||
- **Форматом задач и документов.** Индексы и документы руками не правятся: их
|
||||
ведут `av-dev-tasks:tasks` и `av-dev-docs:docs`. Твоё — содержание ответа, их —
|
||||
форма и дом.
|
||||
- **Определением ценности.** «Нужно ли это делать вообще» — вопрос человека.
|
||||
Разведка отвечает «как это можно сделать и чего каждый способ стоит».
|
||||
- **Решением задачи.** Выбранный способ реализует сценарий решения, и запускает
|
||||
его человек следующим прогоном. Перейти в него по ходу нельзя — это тот самый
|
||||
переход, ради невозможности которого сценарии и разведены.
|
||||
|
||||
## Наблюдаемые исходы сценария
|
||||
|
||||
Четыре, и каждый обязан быть назван в докладе прямо. Первые три — это те самые
|
||||
«заведены задачи, записано знание, отказ», которыми кончается разведка по
|
||||
определению типа `research`:
|
||||
|
||||
- **способ выбран** — ответ записан, задачи заведены или уточнены, они готовы к
|
||||
взятию. Дальше сценарий решения, следующим прогоном, и запускает его человек;
|
||||
- **знание записано** — вопрос закрыт, задач он не породил: ответ ценен сам по
|
||||
себе (замер, устройство внешнего формата, «так работает и менять не нужно»);
|
||||
- **отказ** — проверили, проблемы нет либо подход отвергнут. **Полноправный
|
||||
исход, а не пустая работа**: «не делаем и вот почему» экономит всю работу,
|
||||
которая иначе была бы сделана. Причина записывается — без неё через квартал
|
||||
разведку закажут заново;
|
||||
- **не доведена** — вопроса нет (сырьё), рамки исчерпаны без ответа, или человек
|
||||
на чекпоинте не одобрил ни одного варианта. Названо, что успели узнать и до
|
||||
какой границы.
|
||||
|
||||
## Определение сделанного для разведки
|
||||
|
||||
У сценария решения оно своё (SKILL.md); здесь — короткое и другое. Разведка
|
||||
сделана, когда верно всё:
|
||||
|
||||
1. **ответ записан по адресу, который назвала задача** — раздел «Куда ляжет
|
||||
ответ». Адреса не было, а вопрос был — ты назначил адрес сам и сказал об этом
|
||||
строкой;
|
||||
2. **у каждого числа провенанс** — команда или условия, которыми оно получено.
|
||||
Число без источника проход ревью обязан читать как условие, а не как замер, и
|
||||
разведка, оставившая голые числа, вредна: по ним будут решать;
|
||||
3. **отвергнутые варианты названы с причиной**. Отвергнутое без причины
|
||||
возвращается на следующей разведке как новая идея;
|
||||
4. задачи, которые исход породил, заведены — или явно сказано, что не породил;
|
||||
5. написанное закоммичено, разведка закрыта.
|
||||
|
||||
## Шаги
|
||||
|
||||
### 1. Вопрос и рамки
|
||||
|
||||
Прочитай запись. У типа `research` два обязательных раздела, и оба нужны тебе
|
||||
прямо сейчас: **«Вопрос»** — на что отвечаем, **«Куда ляжет ответ»** — по какому
|
||||
адресу он ляжет. Адрес назван заранее не из аккуратности: ответ, не имеющий дома,
|
||||
остаётся в переписке, и через квартал разведку заказывают заново.
|
||||
|
||||
**Адрес назначает автор записи, а не ты.** Запись из каталога без него до тебя
|
||||
не доходит: `ready` требует непустыми оба раздела и откажет — это стоп со
|
||||
строкой, чего не хватает, а не повод дописать за автора. Иначе исполнитель сам
|
||||
назначает себе приёмку, а приёмка разведки — это и есть записанный по названному
|
||||
адресу ответ.
|
||||
|
||||
**Адрес назначаешь ты ровно в одном случае** — когда записи нет вовсе: разведка
|
||||
пришла текстом или в проекте нет учёта задач. Тогда скажи об этом строкой, а
|
||||
выбирай по канону, а не по удобству:
|
||||
|
||||
| Что узнали | Дом ответа |
|
||||
| --- | --- |
|
||||
| наблюдение о внешнем мире, замер с провенансом | `docs/research/` |
|
||||
| решение с ценой: намеренный отказ, дорогой откат | `docs/adr/` |
|
||||
| факт об устройстве системы | тема `architecture` (или своя тема проекта) |
|
||||
| граница домена, «чем проект **не** является» | `passport` |
|
||||
| ответ нужен только этой работе | тело самой записи |
|
||||
|
||||
Раздел **«Рамки»**, если он есть, — это граница разведки: сколько копаем, какие
|
||||
источники, что заведомо вне. Рамок нет, а вопрос широкий — **назначь их сам и
|
||||
покажи в первой реплике**. Разведка без рамок утекает: она всегда может узнать
|
||||
ещё немного, и признак «достаточно» изнутри не виден.
|
||||
|
||||
Здесь же проверка на «не тот вопрос»: если из записи видно, что отвечать надо на
|
||||
другое, скажи это сразу, а не после разведки.
|
||||
|
||||
### 2. Разведка
|
||||
|
||||
Порядок чтения — от дешёвого к дорогому, и он не произволен:
|
||||
|
||||
1. **документы канона проекта** — половина вопросов уже отвечена там, и разведка,
|
||||
начатая с кода, переоткрывает написанное;
|
||||
2. **код и его история** — `git log` по узлу отвечает на «почему так» чаще, чем
|
||||
кажется;
|
||||
3. **внешние источники** — документация формата, чужой опыт, спецификации;
|
||||
4. **замер** — если вопрос про числа. Числа снимаются с провенансом, иначе они
|
||||
бесполезны на следующем шаге.
|
||||
|
||||
**Скилл `opsx:explore`** — законный инструмент этого шага, если плагин в проекте
|
||||
есть: он держит форму размышления и не даёт ему растечься. **В explore не пишем
|
||||
код.** Вызов не разрешился — работай чтением, скажи это строкой.
|
||||
|
||||
Развилку разведки **не записывай вопросом** — она и есть предмет следующего шага.
|
||||
|
||||
### 3. Чекпоинт: варианты
|
||||
|
||||
**Остановись и покажи человеку способы решить.** Это плановый стоп сценария и
|
||||
единственное место, где разведка ждёт ответа.
|
||||
|
||||
Форма — короткая, экран текста:
|
||||
|
||||
- **вопрос**, на который отвечаем, одной фразой (он уже есть в записи);
|
||||
- **2–4 варианта**, не больше. Больше четырёх — это не выбор, а список: человек
|
||||
не сравнит, а признает свою неспособность сравнить и попросит рекомендацию.
|
||||
У каждого варианта: в чём суть простыми словами, **что даёт**, **чего стоит**,
|
||||
**что становится невозможным** (это ловится хуже всего и стоит дороже всего);
|
||||
- **рекомендация с причиной**. Человек чаще соглашается, чем выбирает заново;
|
||||
- что известно **недостоверно** и как это проверить, если проверять дёшево;
|
||||
- **что уедет в документы и в задачи**, если возражений нет, — одной строкой.
|
||||
Это не второй стоп, а предупреждение: человек видит объём последствий там же,
|
||||
где принимает решение.
|
||||
|
||||
Что нельзя: приносить варианты, различающиеся только реализацией; прятать
|
||||
отвергнутое (отвергнутый вариант с названной причиной — половина будущего ADR);
|
||||
приносить один вариант и называть это выбором.
|
||||
|
||||
Проверка на простой язык та же, что у чекпоинта решения: **в тексте нет `SHALL`,
|
||||
нет имён файлов и функций там, где вещь называется по-русски, и нет слов, которых
|
||||
нет в паспорте проекта.**
|
||||
|
||||
Исходы чекпоинта:
|
||||
|
||||
- **выбран способ** — идёшь на шаг 4, исход разведки будет «способ выбран». Кода
|
||||
ты по нему не пишешь: сценарий кончается записью и коммитом;
|
||||
- **ответ и есть результат** — идёшь на шаг 4, исход «знание записано» или
|
||||
«отказ»;
|
||||
- **вопрос не тот** — возвращаешься на шаг 1: переформулируй вопрос и скажи, что
|
||||
из разведанного остаётся в силе;
|
||||
- **ни один вариант не одобрен** — исход «не доведена» с причиной. Записывается
|
||||
всё равно то, что узнано (шаг 4): выброшенная разведка будет заказана заново.
|
||||
|
||||
### 4. Ответ в документы канона
|
||||
|
||||
**Вызови Skill `av-dev-docs:docs`**: он владеет содержимым документов канона.
|
||||
Передай ему ответ, адрес из шага 1 и провенанс каждого числа — писать содержание
|
||||
за тебя он не будет, но дом, форму и вычитку держит он.
|
||||
|
||||
**Что именно уезжает:**
|
||||
|
||||
- **ответ на вопрос** — по адресу из шага 1;
|
||||
- **отвергнутые варианты с причинами.** Это половина будущего ADR и единственная
|
||||
защита от повторной разведки того же самого;
|
||||
- **решение с ценой — в ADR**, если оно проходит триггер канона (дорогой откат,
|
||||
намеренный отказ, пересмотр прежнего решения). У разведки, кончившейся без
|
||||
кода, `design.md` не будет никогда, поэтому здесь ADR цитирует **записку
|
||||
разведки**, а не архивный change; канон это допускает прямо, и в записи
|
||||
источник называется.
|
||||
|
||||
**Правило принуждённого отрицания здесь не действует.** Это не синк: разведка
|
||||
трогает те документы, которых коснулся её ответ, и перебирать весь канон ей
|
||||
незачем. Но **названо должно быть каждое место, куда ты писал**, — доклад без
|
||||
перечня адресов неотличим от доклада о ненаписанном.
|
||||
|
||||
**Плагина `av-dev-docs` нет** — путь в его дерево не разрешится ниоткуда, поэтому
|
||||
за перечнем документов иди в **свой** reference:
|
||||
[references/project-facts.md](../../review/references/project-facts.md) конвейера
|
||||
ревью. `docs/adr/` и `docs/research/` в нём отсутствуют намеренно (проход ревью их
|
||||
не открывает), а разведке нужны как раз они — добавь их руками. Пиши сам, скажи
|
||||
строкой: «ответ записан без скилла документации — форму и вычитку не сверял
|
||||
никто».
|
||||
|
||||
### 5. Задачи: завести и уточнить
|
||||
|
||||
**Вызови Skill `av-dev-tasks:tasks`.** Он владеет форматом, дедупом и индексами;
|
||||
путь к его скрипту не выясняй и индексы руками не правь.
|
||||
|
||||
Что просишь сделать:
|
||||
|
||||
- **уточнить саму разведку** — если её вопрос по ходу изменился;
|
||||
- **уточнить существующие задачи** — разведка часто отвечает не «что делать», а
|
||||
«что в поставленном неверно»: постановка, границы в разделе «Затрагивает»,
|
||||
критерии приёмки;
|
||||
- **завести новые задачи**, если исход их породил. Формулировки приноси готовыми:
|
||||
заголовок, «зачем», тип, границы. Нарезку на независимо полезные части и
|
||||
проверку на дубли делает он — у него на это свои правила и свой сценарий.
|
||||
|
||||
**Пачку задач показывай списком, прежде чем заводить.** Разведка — самый лёгкий
|
||||
способ надуть беклог: она порождает идеи быстрее, чем кто-либо успевает их
|
||||
оценивать, а заведённая задача живёт, пока её кто-то не выкинет руками.
|
||||
|
||||
Плагина нет — задачи остаются **списком формулировок в докладе**, и это говорится
|
||||
строкой: учёт работ остаётся за владельцем.
|
||||
|
||||
### 6. Гейт и коммит
|
||||
|
||||
**Сперва гейт проекта** — тот же, что гоняет сценарий решения, и по той же
|
||||
причине: разведка только что правила документы канона и индексы задач, а это
|
||||
ровно то, что машина умеет проверить (`docs.py check`, `tasks.py check --dir`,
|
||||
битые ссылки). Красный гейт чинится здесь, а не оставляется следующему прогону:
|
||||
он придёт за код и получит чужую поломку в наследство.
|
||||
|
||||
Гейта в проекте нет — скажи строкой, что записанное не проверял никто.
|
||||
|
||||
Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не
|
||||
создавай и не переключай, ничего не пушь.
|
||||
|
||||
Сообщение — по-русски, **скиллом `av-dev-git:commit`**: форму сообщения держит
|
||||
он, и здесь она не пересказывается. Вызов не разрешился — напиши сообщение сам и
|
||||
скажи строкой, что форму коммита не сверял никто.
|
||||
|
||||
Одна разведка — один осмысленный коммит: записанный ответ и заведённые задачи
|
||||
уезжают вместе, потому что порознь они полуправда.
|
||||
|
||||
### 7. Закрыть разведку — после коммита, не раньше
|
||||
|
||||
**Вызови Skill `av-dev-tasks:tasks`** и попроси закрыть запись: ответ записан —
|
||||
`close --implemented`, ушла без ответа — `close --reason`, и строка уезжает в
|
||||
кладбище.
|
||||
|
||||
**Порядок обязателен.** Закрытие удаляет файл задачи; сделанное до коммита оно
|
||||
оставило бы разведку закрытой без единого следа работы, если шаг 6 упадёт. У
|
||||
разведки это опаснее, чем у решения: следом работы там служит код, а здесь —
|
||||
только записанный ответ. Закрытая разведка без него не оставляет следа вообще —
|
||||
файл задачи удалён, ответ был в переписке.
|
||||
|
||||
**Закрытие тоже коммитится — вторым коммитом, тут же.** Сообщение короткое, про
|
||||
учёт, а не про работу: `закрыта задача <slug>`. Правило «одна разведка — один
|
||||
коммит» про работу, а учёт — не работа.
|
||||
|
||||
Плагина в проекте нет — **ничего не выдумывай**: скажи в докладе, что учёт задач
|
||||
остаётся за владельцем, и назови исход.
|
||||
|
||||
## Доклад разведки
|
||||
|
||||
Общая форма доклада — в SKILL.md; у разведки он свой, потому что докладывать
|
||||
нечего из того, о чём спрашивают решение (ни метки ревью, ни критериев приёмки,
|
||||
ни архивного change). Коротко, и в нём обязательно:
|
||||
|
||||
- **исход** одним из четырёх слов;
|
||||
- **вопрос и ответ** — по фразе на каждое. Ответ, который не сворачивается во
|
||||
фразу, — признак того, что разведка отвечала не на один вопрос;
|
||||
- **куда записано** — перечнем адресов, а не «документация обновлена»;
|
||||
- **какие задачи заведены и уточнены** — слагами;
|
||||
- **что осталось неизвестным** и чего это стоит: разведка без этой строки
|
||||
сообщает «выяснено», не сообщая, что именно осталось не выяснено;
|
||||
- **рамки**, если они ограничили работу: докуда копали и почему остановились.
|
||||
|
||||
## Тонкости
|
||||
|
||||
- **Ответ «зависит от» — не ответ.** Если разведка кончилась развилкой, её исход
|
||||
— варианты с ценой, а не пересказ обеих сторон без рекомендации.
|
||||
- **Отрицательный результат записывается так же тщательно, как положительный.**
|
||||
Соблазн не писать его велик: кажется, что писать нечего. Через квартал именно
|
||||
этой записи и не хватит.
|
||||
- **Чужую разведку не переоткрывай молча.** Нашёл в `docs/research/` или в
|
||||
`docs/adr/` ответ на свой вопрос — исход «знание записано» со ссылкой, и это
|
||||
лучшая из возможных разведок: она стоила одного чтения.
|
||||
@@ -0,0 +1,411 @@
|
||||
# Сценарий «решение»
|
||||
|
||||
Способ решения известен, спорно только как. Проводит задачу от постановки до
|
||||
закрытия и **пишет код**: цикл Spec Driven Development с одним плановым стопом —
|
||||
объяснением после ревью дизайна.
|
||||
|
||||
Сценарий выбирается развилкой на входе скилла ([SKILL.md](../SKILL.md), раздел
|
||||
«Развилка: какой сценарий») и называется вслух первой репликой. Здесь только его
|
||||
ход; общее для обоих сценариев — вход, обращение к соседним плагинам, правило
|
||||
записанного вопроса, правило необратимого — живёт в SKILL.md и тут не
|
||||
пересказывается.
|
||||
|
||||
**OpenSpec — жёсткая предпосылка именно этого сценария** (SKILL.md,
|
||||
«Предпосылки»): на нём стоят шаги 2, 6 и 8, проход `review-specs` и ревью
|
||||
дизайна.
|
||||
|
||||
Тонкая обёртка над каноническими скиллами `opsx:propose` / `opsx:apply` /
|
||||
`opsx:archive` — зови их через Skill, не переизобретай их шаги. Ревью — скилл
|
||||
`av-dev-code:review`; он же держит правило выбора метки, а называет её агент
|
||||
`review-scope` — один раз на задачу, для обеих стадий ревью.
|
||||
|
||||
## Ход работы
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
in["сценарий выбран: решение"]
|
||||
s1["1. прочитать задачу<br/>критерии приёмки выписать сразу"]
|
||||
s2["2. opsx:propose — change, дельта-спеки, tasks.md"]
|
||||
s3["3. разметка — review-scope:<br/>размер, сложность, метка, план тем"]
|
||||
s4["4. ревью дизайна, состав по метке<br/>+ отработка замечаний"]
|
||||
s5(["5. ЧЕКПОИНТ: объяснение<br/>в чём проблема, как решаем,<br/>чем рискуем"])
|
||||
s6["6. opsx:apply — код, гейт,<br/>поведенческая верификация"]
|
||||
s7["7. ревью кода, та же метка<br/>+ отработка замечаний"]
|
||||
s8["8. opsx:archive"]
|
||||
s9["9. синк документации — av-dev-docs:docs"]
|
||||
s10["10. коммит работы — av-dev-git:commit"]
|
||||
s11["11. закрыть задачу — av-dev-tasks:tasks,<br/>вторым коммитом учёта"]
|
||||
|
||||
in --> s1
|
||||
s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 --> s8 --> s9 --> s10 --> s11
|
||||
s3 -.->|"план задачи: та же метка"| s7
|
||||
s5 -.->|"скорректировать:<br/>меняются дельта-спеки"| s3
|
||||
s7 -.->|"находка отменяет дизайн:<br/>меняются дельта-спеки"| s3
|
||||
```
|
||||
|
||||
Схема — **сводка**: содержание каждого шага в его разделе ниже, и при
|
||||
расхождении прав текст.
|
||||
|
||||
## Наблюдаемые исходы сценария
|
||||
|
||||
Четыре, и каждый обязан быть назван в докладе прямо:
|
||||
|
||||
- **сделана** — определение сделанного выполнено целиком;
|
||||
- **не доведена** — с причиной и с записанным вопросом; что именно сделано и до
|
||||
какой границы, названо явно. Сюда же попадает чекпоинт, на котором человек
|
||||
решение не одобрил;
|
||||
- **оказалась крупнее задачи** — распознаётся **до заведения change**, иначе его
|
||||
придётся выбрасывать. Дальше — декомпозиция, и это не работа этого скилла;
|
||||
- **нужна разведка** — очевидного способа решения нет, и это выяснилось уже в
|
||||
работе. Стоп с названной причиной; кода не написано ни строки **намеренно**.
|
||||
Разведка идёт следующим прогоном.
|
||||
|
||||
## Определение сделанного
|
||||
|
||||
Задача сделана, когда верно всё:
|
||||
|
||||
1. гейт проекта зелёный;
|
||||
2. ревью проведено, **план прогона сверен с исходом по каждой теме**, темы без
|
||||
отчёта и без дома названы в границах покрытия;
|
||||
3. решение прошло чекпоинт и не разошлось с одобренным — либо разошлось, и
|
||||
чекпоинт был пройден заново;
|
||||
4. change заархивирован, дельты влиты в актуальные спеки;
|
||||
5. коммит сделан в текущую ветку;
|
||||
6. **критерии приёмки, если проект их дал, выписаны поимённо, и по каждому назван
|
||||
оракул и наблюдаемый исход** — «прогнал вот это, увидел вот то». Это **доклад,
|
||||
а не сертификация: приёмка — не работа этого скилла.** Исполнитель, ставящий
|
||||
себе галочку «принято», проверяет свою работу своим же взглядом — по границе
|
||||
это может делать только приёмщик, разведённый с исполнителем. Критерии приходят
|
||||
снаружи; скилл их не сочиняет и не занижает. Расхождение «по каждому критерию
|
||||
исход есть, а суть задачи не достигнута» — дефект критериев, и о нём
|
||||
сообщается, а не молча дорабатывается.
|
||||
|
||||
## Шаги
|
||||
|
||||
### 1. Прочитать задачу
|
||||
|
||||
Прочитай запись и связанные спеки и черновики.
|
||||
|
||||
Проект даёт задаче **критерии приёмки** — выпиши их сразу: на шаге 2 они уезжают
|
||||
в `tasks.md` change. Файл задачи может быть удалён до коммита, а критерии обязаны
|
||||
его пережить.
|
||||
|
||||
Здесь же проверка на «крупнее задачи»: видно, что одним заходом это не
|
||||
мерджится, — объявляй исход **до** заведения change.
|
||||
|
||||
### 2. Завести change — `opsx:propose`
|
||||
|
||||
Вызови Skill `opsx:propose`. Получаем `proposal.md`, дизайн, дельта-спеки
|
||||
(`ADDED`/`MODIFIED`/`REMOVED Requirements`), `tasks.md`. Каждое `### Requirement`
|
||||
содержит `SHALL`/`MUST`; структурные заголовки английские, сценарии
|
||||
`GIVEN/WHEN/THEN`. Прогони `openspec validate --strict <id>`.
|
||||
|
||||
Критерии приёмки задачи, если они были, копируются в `tasks.md` отдельным блоком.
|
||||
Задаче предшествовала разведка — её записка и отвергнутые варианты **уже
|
||||
записаны** в документах канона (`docs/research/`, `docs/adr/`): сошлись на них из
|
||||
`design.md`, а не переписывай второй раз. Варианты, разобранные без разведки
|
||||
(способ был очевиден, но у него оказались оттенки), — в `design.md`, с причиной
|
||||
отказа по каждому отвергнутому.
|
||||
|
||||
**`proposal.md` пишется так, чтобы его понял человек, не читавший спек.** Это не
|
||||
стилистическое пожелание: из него собирается чекпоинт шага 5, и переписывать его
|
||||
там заново значит завести второй дом для одного объяснения. Требование стоит в
|
||||
`openspec/config.yaml`, `rules.proposal` — то есть применяется в момент
|
||||
порождения артефакта, а не вспоминается после.
|
||||
|
||||
### 3. Разметка задачи — агент `review-scope`
|
||||
|
||||
**Один запуск на всю задачу, и он обслуживает обе стадии ревью.** Запусти
|
||||
агента `review-scope`, дав ему корень проекта, идентификатор change, базу диффа и
|
||||
запись задачи. Кода на этот момент нет, и это условие его работы, а не помеха.
|
||||
|
||||
Он возвращает **план задачи**:
|
||||
|
||||
- **размер** (малое / среднее / крупное) и **сложность** (знакомое /
|
||||
незнакомое), каждое с обоснованием по факту;
|
||||
- **метку** как максимум по двум осям: `small`, `medium` или `large`;
|
||||
- **состав ревью дизайна** — что звать на шаге 4;
|
||||
- **таблицу тем** «тема → дом → глубина → кто закрывает» — для шага 7;
|
||||
- разнесение документов проекта по трём категориям и строку про директивы.
|
||||
|
||||
**Метку выбираешь не ты.** Раньше состав ревью дизайна называл сам оркестратор —
|
||||
то есть тот, кто только что довёл предложение до `propose`. Разведённости с
|
||||
автором в этой точке не было вовсе; теперь есть.
|
||||
|
||||
**План держи в контексте до конца задачи.** На диск он не пишется: файл-план стал
|
||||
бы четвёртым артефактом рядом с `proposal.md`, `tasks.md` и `design.md`, пережил
|
||||
бы задачу и разошёлся бы с ней молча. Прервался прогон — повтори шаг 3, это самый
|
||||
дешёвый его проход.
|
||||
|
||||
**Разметка повторяется ровно в одном случае** — если правки изменили сами
|
||||
**дельта-спеки**: план выведен из них, и план по отменённым требованиям назовёт
|
||||
не те темы. Во всех прочих случаях, включая переделку формы кода на шаге 7,
|
||||
метка остаётся прежней.
|
||||
|
||||
### 4. Ревью дизайна — ДО кода, состав по метке
|
||||
|
||||
Вызови Skill **`av-dev-code:review`**, дав ссылку на change `<id>`,
|
||||
**план разметки с шага 3** и указание, что это ревью дизайна.
|
||||
|
||||
Состав приходит планом, а не решается здесь:
|
||||
|
||||
| Метка | Проходы на предложении |
|
||||
|---|---|
|
||||
| `small` | `specs` |
|
||||
| `medium` | `specs`, `rubric` |
|
||||
| `large` | `specs`, `rubric`, `architecture` + вопрос автору о трёх формах решения |
|
||||
|
||||
`review-specs` в режиме «дизайн ДО кода» идёт **на каждой задаче**: это самый
|
||||
дешёвый проход конвейера, и он ловит то, что на готовом коде уже не чинят.
|
||||
Остальные включаются меткой, потому что стадия стоит на каждой задаче и каждый
|
||||
лишний проход здесь умножается на число задач.
|
||||
|
||||
Смысл стадии: архитектурная находка на готовом коде стоит переписывания и потому
|
||||
игнорируется — та же находка здесь стоит абзаца обсуждения. Если `review-rubric`
|
||||
запускался, перенеси его рубрику в `tasks.md` как приёмочные критерии; там же уже
|
||||
лежат критерии от постановки, если они были.
|
||||
|
||||
**Отработка замечаний, и она идёт до чекпоинта, а не после:**
|
||||
|
||||
- мелочь и явные улучшения — правь сам в спеках и дизайне;
|
||||
- развилки (компромисс, scope, инвариант) — **не в запись, а в чекпоинт**: он
|
||||
следующим шагом, и это ровно то, ради чего он поставлен здесь;
|
||||
- после правок перепрогони `openspec validate --strict <id>`.
|
||||
|
||||
### 5. Чекпоинт: объяснение
|
||||
|
||||
**Остановись и объясни человеку, что происходит.** Единственный плановый стоп
|
||||
этого сценария, и он обязателен для всякой задачи.
|
||||
|
||||
Он стоит **после** ревью дизайна намеренно. Человек читает объяснение, уже
|
||||
просеянное машиной: то, что поймал бы `review-specs`, до него не доходит, а
|
||||
внимание — самый дорогой ресурс процесса, и тратить его на выловимое машиной
|
||||
нельзя.
|
||||
|
||||
**Объяснение не сочиняется заново — оно собирается из артефактов**, `proposal.md`
|
||||
и `design.md`. Третий пересказ был бы третьим домом одного и того же и разошёлся
|
||||
бы с обоими. Что показываешь:
|
||||
|
||||
- **в чём проблема** — словами домена из паспорта, без имён модулей и функций;
|
||||
- **как решаем** — суть одним абзацем. Не пересказ дельта-спек: спека нормирует,
|
||||
а здесь объясняют;
|
||||
- **что человек увидит иначе**, когда это будет сделано;
|
||||
- **чего мы намеренно не делаем** и почему — граница scope ловится хуже всего;
|
||||
- **чем рискуем и что осталось нерешённым** — сюда съезжаются развилки,
|
||||
накопленные до этого места, и находки ревью с пометкой `развилка`;
|
||||
- **что дальше**, если возражений нет.
|
||||
|
||||
Проверка на «простой язык» одна и механическая: **в тексте нет `SHALL`, нет имён
|
||||
файлов и функций там, где вещь называется по-русски, и нет слов, которых нет в
|
||||
паспорте проекта.** Не проходит — переписывай, а не объясняй, почему иначе
|
||||
нельзя.
|
||||
|
||||
Длина — экран. Длинное объяснение не читают, а пролистывают, и чекпоинт
|
||||
превращается в ритуал одобрения.
|
||||
|
||||
Три исхода:
|
||||
|
||||
- **согласен** — идёшь на шаг 6;
|
||||
- **скорректировать** — правишь спеки и дизайн по сказанному. Изменились
|
||||
**дельта-спеки** — повтори шаг 3 (разметка выведена из них) и ту часть ревью
|
||||
дизайна, которой касается правка; затем чекпоинт **заново**. Правка внутри
|
||||
дизайна без спек — повтори только чекпоинт;
|
||||
- **не одобрено** — исход «не доведена» с причиной. Change остаётся
|
||||
незаархивированным, задача не закрывается, ничего не коммитится наполовину.
|
||||
|
||||
### 6. Написать код — `opsx:apply`
|
||||
|
||||
Вызови Skill `opsx:apply` для реализации `tasks.md`. Код — по конвенциям проекта
|
||||
(каталог `docs/conventions/`). Меняешь схему — обнови её описание в документации
|
||||
тем же change, если проект этого требует: гейт обычно это проверяет.
|
||||
|
||||
Прогони гейт и добейся зелёного — он же гейт следующего шага.
|
||||
|
||||
**Поведенческая верификация.** Если задача меняет реальное поведение (новый
|
||||
эндпоинт, разбор входа, схема, форма ответа) — зелёных юнит-тестов мало. Подними
|
||||
изменение вживую командой из раздела команд `CLAUDE.md` и прогони сценарий.
|
||||
Пропусти только для чисто внутренних правок без наблюдаемого рантайма.
|
||||
|
||||
**Сервис не оставляем лежать.** Если запуск упал — почини или откати до конца
|
||||
шага.
|
||||
|
||||
### 7. Ревью кода — та же метка
|
||||
|
||||
Вызови Skill **`av-dev-code:review`**, дав ссылку на change `<id>`,
|
||||
базу диффа, **план разметки с шага 3** и режим запуска.
|
||||
|
||||
**Метку ты не выбираешь, и это правило, а не упрощение.** Её назвал
|
||||
`review-scope` ещё на шаге 3 — по размеру и сложности, с обоснованием по каждой
|
||||
оси. Причина в разведённости: ты только что написал этот код, и решать, насколько
|
||||
глубоко его проверять, тебе нельзя — под давлением «я почти закончил» решение
|
||||
известно заранее. Правило выбора живёт в скилле конвейера —
|
||||
`av-dev-code:review`, `references/review-levels.md`; проектные
|
||||
триггеры — в `docs/review.*`, подраздел «Триггеры метки».
|
||||
|
||||
**Метка не пересматривается по факту диффа.** Дифф может выйти крупнее, чем
|
||||
ожидалось при разметке, — это не повод её поднимать: пересмотр означал бы второй
|
||||
запуск разметчика, ровно то, ради устранения чего он и переехал на шаг 3.
|
||||
|
||||
**Считаешь метку заниженной — скажи это в докладе строкой, а не переспорь.**
|
||||
Разметчик вправе и поднять, и понизить; твоё несогласие это факт для человека, а
|
||||
не команда конвейеру. Место, где такое несогласие превращается в изменение
|
||||
правил, — журнал дефектов `docs/review.md`, и только постфактум.
|
||||
|
||||
**Плана нет — ревью кода не запускается.** Триаж требует план обязательным
|
||||
входом: без него он не может сверить, все ли размеченные темы вернули отчёт, а
|
||||
эта сверка — единственная защита от молчащего пропуска. Потерял план (прервалась
|
||||
сессия, ушёл контекст) — повтори шаг 3, а не гони прогон без него.
|
||||
|
||||
**Режим по умолчанию — `по графу`, и обосновывать его не надо.** Конвейер сам
|
||||
знает свои рёбра: гейт открывает опиниативные проходы, проходы с пометкой «держит
|
||||
машину» идут цепочкой (иначе замеры портят друг друга и находка выглядит
|
||||
доказанной), триаж — сток. Просить **`линейно`** нужно только по причине, и она
|
||||
называется строкой: так сказал оператор; машина занята чем-то ещё; идёт разбор
|
||||
самого конвейера.
|
||||
|
||||
Скилл сам гоняет гейт, нужные проходы и обязательный триаж. Возвращает отчёт с
|
||||
потолком 7 пунктов, разметкой `Действие: инлайн | развилка` и секцией границ
|
||||
покрытия.
|
||||
|
||||
**Сверь план прогона с исходом, прежде чем коммитить.** Отчёт начинается планом
|
||||
разметчика — таблицей «тема → дом → глубина → кто закрывает», — и против каждой
|
||||
темы обязан стоять исход. Тема без отчёта и тема без дома — разные вещи, и обе
|
||||
должны быть названы. Реестр короткий (шесть тем ядра плюс свои) — сверка стоит
|
||||
одного взгляда.
|
||||
|
||||
#### Отработка, и здесь появляется одно новое правило
|
||||
|
||||
Помеченное `инлайн` чини сам и не логируй. `развилка` — вопросом в запись (он уже
|
||||
сформулирован триажем, его остаётся перенести). После правок — снова гейт.
|
||||
|
||||
**Находка, отменяющая одобренный дизайн, отменяет и одобрение.** Признак
|
||||
проверяемый: **меняются ли дельта-спеки**.
|
||||
|
||||
- не меняются — находка внутри дизайна, дожимай сам, это обычная отработка;
|
||||
- меняются — решение стало другим, а одобрено было прежнее. Повтори шаг 3
|
||||
(разметка выведена из дельта-спек) и **вернись на чекпоинт шага 5** с тем, что
|
||||
изменилось и почему.
|
||||
|
||||
**Это правило старше правила о развилке.** Находка класса `развилка`, чьё
|
||||
основание — «надо менять спеку», подпадает под оба; побеждает возврат на
|
||||
чекпоинт, а вопрос в запись при нём остаётся, но возврата не заменяет. Иначе
|
||||
одобренный дизайн переделывался бы записанным вопросом, то есть молча.
|
||||
|
||||
Тихо переделать утверждённое нельзя. Чекпоинт, который можно обойти находкой
|
||||
ревью, не значит ничего, а человек при этом уверен, что одобрил именно то, что
|
||||
уехало в коммит.
|
||||
|
||||
**Урожай — списком, не задачами.** Отложенные находки (реальный `major` не для
|
||||
этого мерджа, развилка, решённая «потом», пачка `nit`) собери в секцию доклада
|
||||
`Урожай`: формулировка, оракул, провенанс. Задачи из него **заводит не этот
|
||||
скилл** — их заводит `av-dev-tasks:tasks` своим сценарием «задачи из ревью и
|
||||
аудита»: своя нарезка, свой формат, свои правила дублей. Твоя обязанность — не
|
||||
потерять и передать.
|
||||
|
||||
**Границы покрытия из отчёта не выбрасывай** — они уезжают в финальный доклад
|
||||
сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно»,
|
||||
превращается в ложное ощущение проверенности.
|
||||
|
||||
**Отчёт триажа сохрани вместе с change (`openspec/changes/<id>/review/`; шаг 8
|
||||
унесёт его в архив вместе с change) — это обязательно, а не «если удобно».** По
|
||||
нему потом видно, что было найдено и что из этого осталось в урожае. И это
|
||||
единственный **независимый** артефакт о составе прогона: своей прозе здесь верить
|
||||
нельзя — она написана тем же, кто мог проход и пропустить.
|
||||
|
||||
### 8. Архивировать — `opsx:archive`
|
||||
|
||||
Вызови Skill `opsx:archive`: change уезжает в архив, дельты вливаются в
|
||||
актуальные спеки. Не пропускай `openspec validate --strict` перед этим.
|
||||
|
||||
### 9. Синк документации
|
||||
|
||||
**Вызови Skill `av-dev-docs:docs`**: он владеет содержимым документов канона и
|
||||
ведёт чек-лист синка.
|
||||
|
||||
**Правило одно и оно жёсткое: принуждённое отрицание.** Доклад обязан назвать
|
||||
**каждый** документ канона — либо чем он обновлён, либо «не требуется, потому
|
||||
что…». Нетронутые группируются одной строкой с общей причиной. Список триггеров
|
||||
прозой уже проверен на живом проекте и дал 6 записей ADR на 43 изменения;
|
||||
работает только обязательное отрицание.
|
||||
|
||||
**Список документов и их триггеров здесь не дублируется** — он в чек-листе скилла
|
||||
`av-dev-docs:docs`, и копия уже однажды разошлась с оригиналом, потеряв два
|
||||
триггера.
|
||||
|
||||
**Плагина `av-dev-docs` в проекте нет** — путь в его дерево не разрешится ниоткуда,
|
||||
поэтому за списком иди в **свой** reference:
|
||||
[references/project-facts.md](../../review/references/project-facts.md)
|
||||
конвейера ревью перечисляет все документы канона с их предметом. Пройди по этому
|
||||
перечню — каждый документ получает строку, отрицание остаётся обязательным.
|
||||
|
||||
**Двух домов в том перечне нет намеренно, а синку они нужны: `docs/adr/` и
|
||||
`docs/research/`.** Проход ревью их не открывает — процессные, — но ADR как раз
|
||||
**главный выход синка**: решение, принятое по ходу задачи, без этой строки
|
||||
теряется систематически. Добавь их к перечню руками: `adr/` — решение с ценой,
|
||||
принятое в этой задаче; `research/` — если по ходу узналось новое о внешнем мире
|
||||
(записку разведки, предшествовавшей задаче, пишет не этот сценарий).
|
||||
Триггеры при этом ты знаешь хуже, и это называется в докладе строкой: «синк
|
||||
сделан по перечню документов, без списка триггеров — плагина `av-dev-docs` нет».
|
||||
Канона в проекте тоже нет — назови это исходом и предложи `av-dev-docs:canon`.
|
||||
|
||||
### 10. Коммит
|
||||
|
||||
Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не
|
||||
создавай и не переключай, ничего не пушь.
|
||||
|
||||
Сообщение — по-русски, **скиллом `av-dev-git:commit`**: форму сообщения держит
|
||||
он, и здесь она не пересказывается. Вызов не разрешился — плагина в проекте нет:
|
||||
напиши сообщение сам и скажи строкой доклада, что форму коммита не сверял никто.
|
||||
Одна задача — один осмысленный коммит.
|
||||
|
||||
### 11. Закрыть задачу — **после коммита, не раньше**
|
||||
|
||||
**Вызови Skill `av-dev-tasks:tasks`** и попроси закрыть задачу как реализованную —
|
||||
он владеет форматом и двигает строку индекса сам. Путь к его скрипту не выясняй и
|
||||
индексы руками не правь: мост между плагинами — вызов скилла, а не путь.
|
||||
|
||||
**Порядок обязателен.** Закрытие удаляет файл задачи; сделанное до коммита оно
|
||||
оставило бы задачу закрытой без единого следа работы, если шаг 10 упадёт.
|
||||
|
||||
**Закрытие тоже коммитится — вторым коммитом, тут же.** Удаление файла задачи и
|
||||
правка индексов (их имена знает `av-dev-tasks`, не ты) — это правки в рабочем
|
||||
дереве, и оставить их незакоммиченными нельзя: закрытие, не доехавшее до основной
|
||||
ветки, оставит задачу открытой молча, а опора «индексы под git показывают, что и
|
||||
когда закрыто» без коммита — пустые слова. Сообщение короткое, про учёт, а не про
|
||||
работу: `закрыта задача <slug>`. Это второй коммит осознанно: правило «одна задача
|
||||
— один осмысленный коммит» про работу, а учёт — не работа.
|
||||
|
||||
Плагина в проекте нет — вызов не разрешится. Тогда **ничего не выдумывай**: скажи
|
||||
в докладе, что учёт задач остаётся за владельцем, и назови исход.
|
||||
|
||||
## Доклад решения
|
||||
|
||||
Общее ядро доклада — в SKILL.md; сверх него сценарий решения обязан назвать:
|
||||
|
||||
- **что было одобрено на чекпоинте и совпало ли с тем, что уехало в коммит** —
|
||||
расхождение здесь называется прямо, даже если оно мелкое;
|
||||
- ссылка на архивный change и хеш коммита;
|
||||
- по каждому критерию приёмки, если они были: **оракул и наблюдаемый исход** —
|
||||
это доклад приёмщику, а не отметка «принято»;
|
||||
- **`Урожай`** — отложенные находки списком (формулировка, оракул, провенанс);
|
||||
- **одна строка границ покрытия**: какая метка и режим гонялись, какие проходы не
|
||||
запускались и что проверить было невозможно. Доклад без неё сообщает
|
||||
«проверено», не сообщая, что именно.
|
||||
|
||||
## Тонкости сценария
|
||||
|
||||
- Гейт блокирует: пока он красный, опиниативные проходы не запускаются. Чинить и
|
||||
перезапускать, а не «посмотреть заодно».
|
||||
- Стиль правок — заточка под проект и конвенции, right-size, без золочения.
|
||||
- **Занизить метку ревью, пропустить тему или проскочить чекпоинт — самый дешёвый
|
||||
способ «ускориться», и он же самый дорогой по последствиям.** Защита устроена
|
||||
так, что регулятора у тебя нет: метку выбирает разметчик **до того**, как ты
|
||||
написал код, план сверяется по темам, непокрытое называется строкой, а
|
||||
расхождение с одобренным — отдельным пунктом доклада.
|
||||
- **Заведение задач из урожая ревью — не твоя работа.** Отложенные находки
|
||||
отдаются **списком**; превращает их в задачи `av-dev-tasks:tasks`, у него на
|
||||
этот вход отдельный сценарий «задачи из ревью и аудита». Плагина нет — урожай
|
||||
остаётся списком в докладе, и это говорится строкой.
|
||||
- **Способ решения ты не выбираешь.** Он приходит известным: из постановки, из
|
||||
разведки, от человека. Выбор между двумя подходами с разной ценой делается в
|
||||
разведке, у своего чекпоинта, — не по ходу этого сценария.
|
||||
@@ -88,8 +88,10 @@ description: "Конвейер ревью изменения, устроенны
|
||||
|
||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||
|
||||
<!-- /копия: граница-плагинов -->
|
||||
|
||||
@@ -113,7 +115,7 @@ description: "Конвейер ревью изменения, устроенны
|
||||
|---|---|---|
|
||||
| **тема** | заводит направление проверки и требует исполнителя | `conventions.*`, `security.*`, `architecture.*`, свои документы проекта |
|
||||
| **источник темы** | читает как материал чужой темы, своей не порождает | `passport.*`, `database.*`, `CLAUDE.md`/`AGENTS.md`, `openspec/specs/` |
|
||||
| **процессный документ** | не судит по нему изменение | `tasks/`, `docs/review.*`, `docs/adr.*`, `docs/research.*`, `docs/.pm.json` |
|
||||
| **процессный документ** | не судит по нему изменение | `tasks/`, `docs/review.*`, `docs/adr.*`, `docs/research.*`, `docs/.docs.json` |
|
||||
|
||||
**Одна процессная запись всё же читается — `docs/review.*`.** В ней лежит
|
||||
настройка самого конвейера: вопросы по темам, журнал дефектов, типовые узлы,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: doc-code-drift
|
||||
description: "Сверка документов канона с кодом по закрытому перечню проверяемых фактов: имя основной ветки и команды из CLAUDE.md, запреты с путями, testdata и временный каталог, путь миграций из .pm.json, внешние зависимости поимённо в architecture.md против манифеста, настройки с числовым значением в database.md против конфига и кода, единые точки проекта против реального числа реализаций, capability против существующих модулей. Отвечает на «этот факт ещё верен», а не «эта архитектура правильная». Читает весь репозиторий, гоняет только читающие команды. Отдаёт готовые формулировки и ничего не правит сам. Согласованность документов между собой смотрит агент doc-consistency. Зовётся скиллом av-dev-docs:healthcheck — на весь канон разом; он же зовётся шагом adopt и шагом upgrade. Только чтение."
|
||||
description: "Сверка документов канона с кодом по закрытому перечню проверяемых фактов: имя основной ветки и команды из CLAUDE.md, запреты с путями, testdata и временный каталог, путь миграций из .docs.json, внешние зависимости поимённо в architecture.md против манифеста, настройки с числовым значением в database.md против конфига и кода, единые точки проекта против реального числа реализаций, capability против существующих модулей. Отвечает на «этот факт ещё верен», а не «эта архитектура правильная». Читает весь репозиторий, гоняет только читающие команды. Отдаёт готовые формулировки и ничего не правит сам. Согласованность документов между собой смотрит агент doc-consistency. Зовётся скиллом av-dev-docs:healthcheck — на весь канон разом; он же зовётся шагом adopt и шагом upgrade. Только чтение."
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: sonnet
|
||||
color: green
|
||||
@@ -37,7 +37,7 @@ color: green
|
||||
|
||||
## Что тебе дают
|
||||
|
||||
Корень проекта. Читаешь `CLAUDE.md`, `docs/**`, `docs/.pm.json`,
|
||||
Корень проекта. Читаешь `CLAUDE.md`, `docs/**`, `docs/.docs.json`,
|
||||
`openspec/specs/**` — и репозиторий: манифесты зависимостей, конфиги, файлы
|
||||
сборки и CI, дерево пакетов.
|
||||
|
||||
@@ -62,7 +62,7 @@ color: green
|
||||
держит прежнее имя.
|
||||
|
||||
3. **Пути** — все, которые канон обязывает называть: `migrations` из
|
||||
`docs/.pm.json`, `testdata`, временный каталог, пути в запретах `CLAUDE.md`.
|
||||
`docs/.docs.json`, `testdata`, временный каталог, пути в запретах `CLAUDE.md`.
|
||||
Проверка: существует ли. Путь в запрете, которого нет, — находка **особого
|
||||
рода**: запрет, который не на что наложить, читается как соблюдённый, а на
|
||||
деле охраняет пустоту, пока настоящий каталог зовётся иначе.
|
||||
@@ -147,7 +147,7 @@ color: green
|
||||
```
|
||||
факт источник проверено чем итог
|
||||
имя основной ветки CLAUDE.md git branch сошлось
|
||||
путь миграций docs/.pm.json ls РАЗОШЛОСЬ
|
||||
путь миграций docs/.docs.json ls РАЗОШЛОСЬ
|
||||
внешние зависимости architecture.md go.mod 2 не названы
|
||||
единые точки: парсер входа architecture.md grep по формату сошлось
|
||||
настройки БД database.md — не проверено
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: doc-consistency
|
||||
description: "Сверка документов канона между собой и с openspec: один факт, живущий в двух домах, прямое противоречие между документами (периметр, зависимости, обратимость), поведение системы, осевшее в architecture.md вместо спек, capability без обзора или с пересказом требований, число без провенанса в research, ADR без ссылки на архивный design.md и без парного статуса при замене, заглушка вместо честной строки в пустом слоте. Читает docs/ и openspec/, кода не читает. Отдаёт готовые формулировки и ничего не правит сам. Соответствие документов коду смотрит агент doc-code-drift, язык — doc-wording. Зовётся скиллом av-dev-docs:healthcheck — на весь канон разом; он же зовётся шагом adopt и шагом upgrade. На отдельной задаче и на синке документации не звать. Только чтение."
|
||||
description: "Сверка документов канона между собой и с openspec: один факт, живущий в двух домах, прямое противоречие между документами (периметр, зависимости, обратимость), поведение системы, осевшее в architecture.md вместо спек, capability без обзора или с пересказом требований, число без провенанса в research, ADR без ссылки на источник (архивный design.md либо записка разведки) и без парного статуса при замене, заглушка вместо честной строки в пустом слоте. Читает docs/ и openspec/, кода не читает. Отдаёт готовые формулировки и ничего не правит сам. Соответствие документов коду смотрит агент doc-code-drift, язык — doc-wording. Зовётся скиллом av-dev-docs:healthcheck — на весь канон разом; он же зовётся шагом adopt и шагом upgrade. На отдельной задаче и на синке документации не звать. Только чтение."
|
||||
tools: Read, Grep, Glob
|
||||
model: opus
|
||||
color: yellow
|
||||
@@ -23,7 +23,7 @@ color: yellow
|
||||
| Факт | Дом |
|
||||
| --- | --- |
|
||||
| поведение системы | `openspec/specs/<capability>/spec.md` |
|
||||
| почему решено так | `adr/`, источник — архивный `design.md` |
|
||||
| почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки |
|
||||
| граница домена, «чем не является» | `passport.md` |
|
||||
| инвариант и его severity | `CLAUDE.md` |
|
||||
| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` |
|
||||
@@ -50,7 +50,10 @@ color: yellow
|
||||
`docs/tasks/` на непереехавшем проекте), принадлежит другому плагину и ведётся
|
||||
своим скриптом; не открывай его ни в той форме, ни в другой. Плюс
|
||||
`openspec/changes/archive/`, когда проверяешь ADR: там лежат `design.md`, из
|
||||
которых записи промоутятся.
|
||||
которых записи промоутятся. **Источник у ADR бывает и второй — записка
|
||||
разведки**: решение, принятое без изменения (намеренный отказ, выбор подхода),
|
||||
`design.md` не имеет по построению. Запись без ссылки **на любой из двух** —
|
||||
находка; запись со ссылкой на записку — нет.
|
||||
|
||||
**Кода ты не читаешь.** Разошёлся ли документ с кодом — вопрос агента
|
||||
`doc-code-drift`, и у него для этого другой вход и другая цена.
|
||||
|
||||
@@ -116,8 +116,10 @@ capability: незаполненный канон это переходное с
|
||||
|
||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||
|
||||
<!-- /копия: граница-плагинов -->
|
||||
|
||||
@@ -176,7 +178,7 @@ capability), `openspec/config.yaml`.
|
||||
|
||||
Порядок важен — он минимизирует окно, в котором ссылки битые:
|
||||
|
||||
1. `docs/.pm.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
|
||||
1. `docs/.docs.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
|
||||
2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**:
|
||||
незаполненное — одной честной информативной строкой, а не «TBD»;
|
||||
3. **OpenSpec, если его нет или `config.yaml` остался примером** — **вызови
|
||||
@@ -265,7 +267,7 @@ capability), `openspec/config.yaml`.
|
||||
3. Иначе иди по [changelog.md](references/changelog.md) снизу вверх от версии
|
||||
проекта до текущей и делай названное в каждой записи. Записи независимы и
|
||||
применяются по порядку.
|
||||
4. Подними `canon` в `docs/.pm.json` до текущей.
|
||||
4. Подними `canon` в `docs/.docs.json` до текущей.
|
||||
5. `docs.py check`.
|
||||
6. **Позови судей** — Skill `av-dev-docs:healthcheck`.
|
||||
7. **Позови вычитку** — агент `doc-wording`, но **только по тем документам,
|
||||
@@ -276,7 +278,15 @@ capability), `openspec/config.yaml`.
|
||||
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
|
||||
это дефект журнала, и о нём надо сказать, а не догадываться.
|
||||
|
||||
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.pm.json` с
|
||||
**Каталог задач повышается своим журналом, а не этим.** У него своя версия
|
||||
формата — ключ `tasks` в `<каталог задач>/.tasks.json`, — и двигает её плагин
|
||||
`av-dev-tasks`. Запись канона вправе сказать «позови соседа», но не вправе
|
||||
двигать чужое число: две версии, которые ходят по одному журналу, разъезжаются
|
||||
на первом же проекте, поставившем один плагин без другого. Отстал каталог
|
||||
задач — это скажет `tasks.py check` своей строкой гейта, а повысит скилл
|
||||
`av-dev-tasks:tasks`.
|
||||
|
||||
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.docs.json` с
|
||||
версией скрипта — и только его. Применена ли запись журнала **по существу**, он
|
||||
не знает: проект несёт `"canon": 6` и может не иметь того, чего требовала любая
|
||||
из пройденных версий. Записи применяются руками (переименовать секцию, проставить
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
# Канон документов проекта
|
||||
|
||||
**Версия 12.**
|
||||
**Номер версии здесь не стоит намеренно.** Этот файл описывает канон таким, какой
|
||||
он сейчас, а число живёт в двух домах, которые не расходятся: константа в
|
||||
`docs.py` (её печатает `docs.py version`) и верхняя запись
|
||||
[журнала](changelog.md). Литерал в шапке был третьим и отстал на первом же
|
||||
повышении — версию 13 он пережил, объявляя канон двенадцатым.
|
||||
|
||||
Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs`
|
||||
читают его, а не пересказывают: три описания одной раскладки разъедутся, и
|
||||
@@ -65,7 +69,7 @@ CLAUDE.md памятка агенту: что это, ст
|
||||
severity, команды, семантика гейта, запреты
|
||||
AGENTS.md необязателен, лежит рядом; читается теми же
|
||||
docs/
|
||||
.pm.json версия канона и пути, нужные проверкам
|
||||
.docs.json версия канона и пути, нужные проверкам
|
||||
passport.md | passport/ зачем и для кого; чем НЕ является; сценарии
|
||||
architecture.md | architecture/ как сложено — обзор; окружение и эксплуатация
|
||||
database.md | database/ схема хранилища; представление данных и настройки
|
||||
@@ -119,7 +123,7 @@ openspec/
|
||||
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
|
||||
| `adr.*` | процессный | — |
|
||||
| `research.*` | процессный | — |
|
||||
| `.pm.json` | процессный | — (служебный файл, не документ) |
|
||||
| `.docs.json` | процессный | — (служебный файл, не документ) |
|
||||
|
||||
**Список тем открытый, и это не послабление, а механизм.** Категории
|
||||
`источник` и `процессный` **закрыты** — они перечислены здесь поимённо и
|
||||
@@ -141,7 +145,7 @@ openspec/
|
||||
|
||||
**Процессный документ — не документ второго сорта.** `adr/` и `research/`
|
||||
проверяются наравне с остальными, но **сверкой документации**, а не прогоном
|
||||
ревью: ADR без ссылки на архивный `design.md`, замена без парного статуса, число
|
||||
ревью: ADR без ссылки на источник, замена без парного статуса, число
|
||||
без провенанса — это работа агентов `doc-consistency` и `doc-code-drift`, и она
|
||||
осталась там же, где была. Изменилось одно: прогон ревью не открывает их как
|
||||
критерий и не судит по ним изменение.
|
||||
@@ -291,8 +295,17 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
|
||||
### `adr/`
|
||||
|
||||
**ADR — промоут поверх архивных `design.md`, а не второе сочинение.** Запись
|
||||
цитирует решение и ссылается на `openspec/changes/archive/<id>/design.md`.
|
||||
**ADR — промоут поверх уже написанного, а не второе сочинение.** Запись цитирует
|
||||
решение и ссылается на источник. Источников два, и оба законны:
|
||||
|
||||
- **архивный `design.md`** — решение принято по ходу изменения:
|
||||
`openspec/changes/archive/<id>/design.md`. Обычный случай;
|
||||
- **записка разведки** — решение принято разведкой, и change по нему не будет
|
||||
никогда: намеренный отказ, выбор подхода, «проверили и не делаем». У такой
|
||||
работы нет `design.md` по построению, и без второго источника её решение либо
|
||||
не попадало в `adr/` вовсе, либо попадало сочинённым заново.
|
||||
|
||||
Источник называется в записи всегда — по нему видно, чем решение подтверждено.
|
||||
|
||||
Заводится, когда верно одно из трёх:
|
||||
|
||||
@@ -354,8 +367,10 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
### `tasks/`
|
||||
|
||||
**Каталог задач канону не принадлежит.** Его ведёт отдельный плагин
|
||||
`av-dev-tasks` — своим скриптом, своим конфигом `<каталог>/.tasks.json` и своей
|
||||
версией формата. Канон **резервирует место** в `docs/` и внутрь не смотрит:
|
||||
`av-dev-tasks` — своим скриптом, своим конфигом `<каталог>/.tasks.json`, своей
|
||||
версией формата в нём же и своим журналом версий. Канон о том числе не
|
||||
высказывается и его не двигает: повышает каталог задач тот, кто его ведёт.
|
||||
Канон **резервирует место** в `docs/` и внутрь не смотрит:
|
||||
`docs.py` каталог не открывает, его отсутствия не считает дрейфом и согласованность
|
||||
задач не проверяет. Проект, поставивший только канон документов, задач не ведёт
|
||||
вовсе, и отказом это быть не может.
|
||||
@@ -456,7 +471,7 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
| Факт | Дом |
|
||||
| --- | --- |
|
||||
| поведение системы | `openspec/specs/<capability>/spec.md` |
|
||||
| почему решено так | `adr/`, источник — архивный `design.md` |
|
||||
| почему решено так | `adr/`, источник — архивный `design.md` либо записка разведки |
|
||||
| граница домена, «чем не является» | `passport.md` |
|
||||
| инвариант и его severity | `CLAUDE.md` |
|
||||
| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` |
|
||||
@@ -511,7 +526,7 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
| имя файла не kebab-case латиницей; форма имени ADR | транслит в имени — сверх эвристики | `doc-wording` |
|
||||
| битые относительные ссылки | прямое противоречие между документами | `doc-consistency` |
|
||||
| версия канона и её отставание | достаточность честной строки в пустом слоте | `doc-consistency` |
|
||||
| нетронутый плейсхолдер шаблона | ADR без ссылки на `design.md`, замена без парного статуса | `doc-consistency` |
|
||||
| нетронутый плейсхолдер шаблона | ADR без ссылки на источник, замена без парного статуса | `doc-consistency` |
|
||||
| маркеры долга — числом | **протухший факт, разошедшийся с кодом** | `doc-code-drift` |
|
||||
| миграция изменена, а `database.md` нет | зависимость в манифесте, не названная в обзоре | `doc-code-drift` |
|
||||
| capability без упоминания в `architecture.md` | второй способ там, где обзор обещал единственный | `doc-code-drift` |
|
||||
@@ -546,7 +561,7 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
архитектуру с кодом» задача без дна, и агент, которому её поставили, выдаёт
|
||||
правдоподобную труху вместо находок.
|
||||
|
||||
## `docs/.pm.json`
|
||||
## `docs/.docs.json`
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -562,12 +577,23 @@ kebab-case.** Причина не эстетическая: имя файла с
|
||||
`migrations` — путь каталога миграций, если БД есть; по нему `docs.py` делает
|
||||
сверку с `database.md`.
|
||||
|
||||
**Имя файла — имя плагина, который его завёл.** Канон документов ведёт
|
||||
`av-dev-docs`, поэтому `.docs.json`; у каталога задач по тому же правилу
|
||||
`.tasks.json`, у конвейера — `openspec/config.yaml`. До версии 13 файл звался
|
||||
`.pm.json` — по плагину `av-dev-pm`, который распался на четыре и которого
|
||||
больше нет; имя пережило владельца и указывало в пустоту. Прежнее имя `docs.py`
|
||||
не читает: два дома для одной версии канона расходятся молча, а переименование
|
||||
стоит одну команду (версия 13 журнала, и `check` называет её сам, когда видит
|
||||
старый файл).
|
||||
|
||||
**Ключа `tasks` здесь больше нет.** Настройки каталога задач вернулись в свой
|
||||
файл `<каталог задач>/.tasks.json`, потому что ведёт их другой плагин: конфиг,
|
||||
лежащий в `docs/`, был бы домом, которого нет у проекта, поставившего учёт работ
|
||||
без канона документов. Состав ключей описывает тот плагин, а не канон. Прежний
|
||||
ключ читается, пока живы непереехавшие проекты, и `tasks.py` говорит о нём
|
||||
замечанием на каждом прогоне — версия 8 журнала просит его убрать.
|
||||
без канона документов. Состав ключей описывает тот плагин, а не канон. Там же —
|
||||
**версия формата задач**, и она своя: у проекта без `docs/` версии канона нет
|
||||
вовсе, сверять её было бы не с чем. Прежний ключ читается, пока живы
|
||||
непереехавшие проекты, и `tasks.py` говорит о нём замечанием на каждом
|
||||
прогоне — версия 8 журнала просит его убрать.
|
||||
|
||||
Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py`
|
||||
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
# Журнал версий канона
|
||||
|
||||
Одна запись на версию. Проект знает свою версию из `docs/.pm.json`; `canon
|
||||
Одна запись на версию. Проект знает свою версию из `docs/.docs.json`; `canon
|
||||
upgrade` идёт по записям снизу вверх от версии проекта до текущей и делает то,
|
||||
что в них названо.
|
||||
что в них названо. Записи ниже версии 13 зовут этот файл прежним именем,
|
||||
`docs/.pm.json`, — так и было на день записи, и переписывать историю мы не
|
||||
станем; переименование делает запись 13.
|
||||
|
||||
**Каталог задач этим журналом не повышается.** У него своя версия формата и свой
|
||||
журнал — `references/changelog.md` скилла `av-dev-tasks:tasks`. Записи 8, 11 и 12
|
||||
трогали его в те времена, когда своего числа у него не было; впредь запись канона
|
||||
вправе позвать соседа, но не двигать его версию.
|
||||
|
||||
Правило записи: **что добавилось, что переехало, что удалено, что сделать
|
||||
проекту**. Без последнего пункта запись бесполезна — по ней и работает
|
||||
@@ -13,6 +20,89 @@ upgrade` идёт по записям снизу вверх от версии п
|
||||
|
||||
---
|
||||
|
||||
## Версия 14 — 2026-08-11
|
||||
|
||||
У ADR стало два законных источника. Прежде запись цитировала только архивный
|
||||
`design.md`, то есть решение, принятое по ходу изменения. Решение, принятое
|
||||
**разведкой** — намеренный отказ, выбор подхода, «проверили и не делаем», — не
|
||||
имеет `design.md` по построению: change по нему не заводится никогда. Триггер
|
||||
канона такое решение ловит («намеренный отказ от очевидного подхода»), а дома у
|
||||
него не было, и оно оседало в записке разведки или в переписке.
|
||||
|
||||
**Что изменилось.** `adr/` принимает второй источник — записку разведки. Правило
|
||||
«промоут, а не второе сочинение» не тронуто: запись по-прежнему цитирует уже
|
||||
написанное и **называет источник**, изменилось только то, что источников два.
|
||||
Следом сказали то же самое: карта домов, разрез проверки `doc-consistency`, вход
|
||||
и устав самого агента, скелеты `docs/adr/README.md` и `docs/adr/template.md`.
|
||||
|
||||
**Почему это версия, а не правка текста.** Два следствия уезжают в репозиторий
|
||||
проекта. По карте домов судит агент согласованности — прежняя редакция читала ADR
|
||||
со ссылкой на записку разведки как нарушение; а скелеты `adr/` лежат в проекте
|
||||
файлами и говорят там от имени канона.
|
||||
|
||||
**Что сделать проекту.**
|
||||
|
||||
1. Ничего с существующими записями: прежние ADR ссылаются на `design.md`, и это
|
||||
по-прежнему верно.
|
||||
2. **Поднять шапку `docs/adr/README.md`**: «промоут поверх архивного `design.md`»
|
||||
→ «промоут поверх уже написанного», с обоими источниками. Точный текст — в
|
||||
[skeletons.md](skeletons.md), раздел `docs/adr/README.md`.
|
||||
3. **Поднять `docs/adr/template.md`**: строка `- **Источник:**` называет два
|
||||
возможных источника.
|
||||
4. `docs/.docs.json`: `"canon": 14`.
|
||||
|
||||
**Чего делать не надо.** Заводить ADR задним числом по старым разведкам. Запись
|
||||
заводится, когда решение принимается, а не когда о нём вспомнили: сочинённое
|
||||
через полгода обоснование — ровно то «второе сочинение», против которого правило
|
||||
и написано.
|
||||
|
||||
---
|
||||
|
||||
## Версия 13 — 2026-08-11
|
||||
|
||||
Служебный файл канона переименован: `docs/.pm.json` → `docs/.docs.json`. Имя
|
||||
досталось от плагина `av-dev-pm`, который распался на четыре и которого больше
|
||||
нет: файл пережил владельца и указывал в пустоту. Правило простое и теперь
|
||||
соблюдается всеми тремя: **имя служебного файла — имя плагина, который его
|
||||
завёл**, `.docs.json` — канон, `.tasks.json` — задачи, `openspec/config.yaml` —
|
||||
конвейер.
|
||||
|
||||
**Что изменилось.** `docs.py` читает только новое имя. Прежнее он не читает
|
||||
намеренно: два дома для одной версии канона расходятся молча, а тут расхождение
|
||||
стоило бы дорого — по этому числу `upgrade` решает, какие записи применять.
|
||||
Файл под старым именем `check` узнаёт и называет отдельной строкой с готовой
|
||||
командой, а не жалуется на пропажу.
|
||||
|
||||
**Что появилось у соседа.** У каталога задач теперь есть **своя версия
|
||||
формата** — ключ `tasks` в `<каталог задач>/.tasks.json`, — и свой журнал версий
|
||||
в скилле `av-dev-tasks:tasks`. До сих пор её не было вовсе: формат задач менялся
|
||||
записями этого журнала (8, 11, 12), хотя каталог принадлежит другому плагину и
|
||||
ставится без канона документов. Канон это число не двигает.
|
||||
|
||||
**Что сделать проекту.**
|
||||
|
||||
1. `git mv docs/.pm.json docs/.docs.json` — одним коммитом с шагом 2. Содержимое
|
||||
не меняется: ключи те же.
|
||||
2. **Поправить упоминания прежнего имени** в своих файлах — `CLAUDE.md`, гейт,
|
||||
`README.md`, `docs/**`. Битой ссылкой это чаще всего не выглядит (файл
|
||||
служебный, на него ссылаются прозой), поэтому `docs.py check` таких упоминаний
|
||||
не ловит: ищи `grep -rn '\.pm\.json'` по репозиторию.
|
||||
3. **Объявить версию формата задач**, если каталог задач в проекте есть:
|
||||
`<каталог задач>/.tasks.json` с ключом `"tasks": <версия>`. Файла нет вовсе —
|
||||
заведи, он теперь обязателен: версия не настройка, от которой можно
|
||||
отказаться. Какое число ставить и что сделать перед этим, говорит журнал
|
||||
владельца — **позови скилл `av-dev-tasks:tasks`**, здесь этих шагов нет
|
||||
намеренно: второй перечень чужих шагов разошёлся бы с первым.
|
||||
4. Гейт не меняется: шаги те же, версию задач сторожит `tasks.py check`, который
|
||||
в нём уже стоит.
|
||||
5. `docs/.docs.json`: `"canon": 13`.
|
||||
|
||||
**Чего делать не надо.** Ключи в файле не трогаются, документы не переезжают,
|
||||
записи задач не меняются: версия 13 — про имена служебных файлов и про то, кто
|
||||
чью версию двигает.
|
||||
|
||||
---
|
||||
|
||||
## Версия 12 — 2026-08-09
|
||||
|
||||
Спринты отменены. Работа идёт задача за задачей, и замороженный набор перестал
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
со строкой «запись лежит сжатой и распаковывается целиком».
|
||||
```
|
||||
|
||||
Нет БД — файла нет, и в `docs/.pm.json` нет ключа `migrations`.
|
||||
Нет БД — файла нет, и в `docs/.docs.json` нет ключа `migrations`.
|
||||
|
||||
## `docs/security.md`
|
||||
|
||||
@@ -200,9 +200,10 @@
|
||||
```markdown
|
||||
# Журнал решений
|
||||
|
||||
Одна запись — одно решение. **ADR это промоут поверх архивного `design.md`**,
|
||||
а не второе сочинение: запись цитирует решение и ссылается на
|
||||
`openspec/changes/archive/<id>/design.md`.
|
||||
Одна запись — одно решение. **ADR это промоут поверх уже написанного**, а не
|
||||
второе сочинение: запись цитирует решение и ссылается на источник —
|
||||
`openspec/changes/archive/<id>/design.md`, а у решения, принятого разведкой без
|
||||
изменения, на её записку.
|
||||
|
||||
## Когда заводить
|
||||
|
||||
@@ -241,7 +242,8 @@
|
||||
# Краткий заголовок решения
|
||||
|
||||
- **Дата:** ГГГГ-ММ-ДД
|
||||
- **Источник:** openspec/changes/archive/<id>/design.md
|
||||
- **Источник:** openspec/changes/archive/<id>/design.md — либо записка разведки,
|
||||
если решение принято без изменения
|
||||
|
||||
Статус ставится тем же полем и только при пересмотре:
|
||||
`- **Статус:** заменено на ADR-…` либо `- **Статус:** устарело`.
|
||||
@@ -438,7 +440,7 @@ severity стоит здесь, а не выводится каждым прох
|
||||
говорит (единственный дом, а не форма) — [canon.md](canon.md), раздел
|
||||
`openspec/config.yaml`.
|
||||
|
||||
## `docs/.pm.json`
|
||||
## `docs/.docs.json`
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -452,5 +454,10 @@ severity стоит здесь, а не выводится каждым прох
|
||||
плейсхолдер ломает разбор JSON громко, а отставшее число дало бы дрейф молча.
|
||||
|
||||
Плюс `"migrations": "<путь>"`, если есть БД. Ключа `"tasks"` здесь **нет**:
|
||||
настройки каталога задач переехали в свой файл `<каталог задач>/.tasks.json`,
|
||||
потому что ведёт их другой плагин. Состав ключей — [canon.md](canon.md).
|
||||
настройки каталога задач и версия их формата переехали в свой файл `<каталог
|
||||
задач>/.tasks.json`, потому что ведёт их другой плагин. Состав ключей —
|
||||
[canon.md](canon.md).
|
||||
|
||||
Имя файла — по плагину-владельцу, `av-dev-docs`. До версии 13 он звался
|
||||
`.pm.json`, по распавшемуся `av-dev-pm`; проект с прежним именем `docs.py check`
|
||||
называет отдельной строкой и зовёт переименовать.
|
||||
|
||||
@@ -25,10 +25,19 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
CANON_VERSION = 12
|
||||
CANON_VERSION = 14
|
||||
|
||||
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||
|
||||
# Дом версии канона и путей, нужных проверкам. Имя — от плагина, который файл
|
||||
# завёл: настройки канона документов ведёт `av-dev-docs`, и файл называется по
|
||||
# нему. Прежнее имя досталось от `av-dev-pm` — плагина, который распался на
|
||||
# четыре и которого больше нет; читать его скрипт не умеет намеренно, потому что
|
||||
# два дома для версии канона расходятся молча, а переименование стоит одну
|
||||
# команду и названо записью 13 журнала.
|
||||
CONFIG = "docs/.docs.json"
|
||||
LEGACY_CONFIG = "docs/.pm.json"
|
||||
|
||||
# --- Раскладка канона -------------------------------------------------------
|
||||
|
||||
# Документ канона: имя → (категория, на какой вопрос отвечает).
|
||||
@@ -59,7 +68,7 @@ DOCS = {
|
||||
"review": ("процессный", "настройка конвейера + журнал дефектов"),
|
||||
}
|
||||
|
||||
# Документ, обязательный только при условии: имя → (ключ .pm.json, категория,
|
||||
# Документ, обязательный только при условии: имя → (ключ .docs.json, категория,
|
||||
# пояснение).
|
||||
CONDITIONAL_DOCS = {
|
||||
"database": ("migrations", "источник", "схема хранилища и настройки"),
|
||||
@@ -68,7 +77,7 @@ CONDITIONAL_DOCS = {
|
||||
# Обязательные файлы вне раскладки docs/.
|
||||
REQUIRED = {
|
||||
"CLAUDE.md": "памятка агенту: инварианты с severity, команды, семантика гейта",
|
||||
"docs/.pm.json": "версия канона и пути, нужные проверкам",
|
||||
CONFIG: "версия канона и пути, нужные проверкам",
|
||||
}
|
||||
|
||||
# Файлы, которые документ-каталог обязан держать сверх README.md.
|
||||
@@ -77,15 +86,19 @@ DOC_EXTRA = {
|
||||
}
|
||||
|
||||
# Служебное в docs/ и каталог задач, оставшийся там от прежней раскладки. Формы
|
||||
# у них скрипт не проверяет, и по разным причинам: `.pm.json` не markdown, а
|
||||
# у них скрипт не проверяет, и по разным причинам: `.docs.json` не markdown, а
|
||||
# задачи **принадлежат другому плагину** — `av-dev-tasks`, со своим скриптом,
|
||||
# своим конфигом и своей версией формата.
|
||||
# своим конфигом и своей версией формата (её сторожит `tasks.py check`).
|
||||
#
|
||||
# Дом задач с версии 11 — `tasks/` в корне репозитория, то есть вне `docs/`
|
||||
# вовсе. `docs/tasks/` здесь терпится потому, что непереехавший проект не должен
|
||||
# получать «файл вне канона» вдобавок к записи журнала, которая и так велит ему
|
||||
# переехать. Внутрь скрипт не смотрит ни в том, ни в другом случае.
|
||||
NOT_DOCS = {".pm.json", "tasks"}
|
||||
#
|
||||
# Прежнее имя конфига терпится ровно за тем же: про переименование проект
|
||||
# слышит одну строку — от `check_required`, — а не две, из которых вторая ещё и
|
||||
# зовёт файл лишним.
|
||||
NOT_DOCS = {".docs.json", ".pm.json", "tasks"}
|
||||
|
||||
# Слоты, которых в каноне нет, — с адресом, куда уезжает содержимое. Имена,
|
||||
# совпадающие с темой, отсюда убраны намеренно: `docs/conventions.md` и
|
||||
@@ -251,15 +264,15 @@ def fail(code: int, msg: str) -> NoReturn:
|
||||
|
||||
|
||||
def read_config(root: Path, rep: Report) -> dict:
|
||||
path = root / "docs" / ".pm.json"
|
||||
path = root / CONFIG
|
||||
if not path.exists():
|
||||
return {}
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
except json.JSONDecodeError as exc:
|
||||
fail(ENV, f"docs/.pm.json не разбирается: {exc}")
|
||||
fail(ENV, f"{CONFIG} не разбирается: {exc}")
|
||||
if not isinstance(data, dict):
|
||||
fail(ENV, "docs/.pm.json должен быть объектом")
|
||||
fail(ENV, f"{CONFIG} должен быть объектом")
|
||||
return data
|
||||
|
||||
|
||||
@@ -267,14 +280,14 @@ def read_config(root: Path, rep: Report) -> dict:
|
||||
|
||||
|
||||
def check_version(root: Path, cfg: dict, rep: Report) -> None:
|
||||
if not (root / "docs" / ".pm.json").exists():
|
||||
if not (root / CONFIG).exists():
|
||||
return # об отсутствии файла скажет check_required, второй раз не нужно
|
||||
if "canon" not in cfg:
|
||||
rep.error("в docs/.pm.json нет ключа canon — версия канона не объявлена")
|
||||
rep.error(f"в {CONFIG} нет ключа canon — версия канона не объявлена")
|
||||
return
|
||||
got = cfg["canon"]
|
||||
if not isinstance(got, int):
|
||||
rep.error(f"canon в docs/.pm.json должен быть целым числом, а не {got!r}")
|
||||
rep.error(f"canon в {CONFIG} должен быть целым числом, а не {got!r}")
|
||||
return
|
||||
if got < CANON_VERSION:
|
||||
rep.error(
|
||||
@@ -316,7 +329,20 @@ def doc_home(root: Path, name: str) -> tuple[Path | None, str | None]:
|
||||
|
||||
def check_required(root: Path, cfg: dict, rep: Report) -> None:
|
||||
for rel, what in REQUIRED.items():
|
||||
if not (root / rel).exists():
|
||||
if (root / rel).exists():
|
||||
continue
|
||||
# Файл под прежним именем — это не «нет файла», а незаконченный переезд,
|
||||
# и чинится он одной командой. Без этой ветки проект услышал бы «нет
|
||||
# версии канона» и пошёл заводить второй файл рядом с первым.
|
||||
if rel == CONFIG and (root / LEGACY_CONFIG).exists():
|
||||
rep.error(
|
||||
f"нет {rel} — {what}. Настройки лежат под прежним именем"
|
||||
f" {LEGACY_CONFIG} (от плагина av-dev-pm, которого больше нет):"
|
||||
f" `git mv {LEGACY_CONFIG} {rel}` — журнал канона, версия 13."
|
||||
f" Прежнее имя не читается, поэтому в этом прогоне всё"
|
||||
f" остальное проверено так, будто настроек нет вовсе"
|
||||
)
|
||||
continue
|
||||
rep.error(f"нет {rel} — {what}")
|
||||
|
||||
for name, (kind, what) in DOCS.items():
|
||||
@@ -342,10 +368,10 @@ def check_required(root: Path, cfg: dict, rep: Report) -> None:
|
||||
rep.error(
|
||||
f"нет документа {name} (docs/{name}.md или docs/{name}/),"
|
||||
f" категория «{kind}» — {what}"
|
||||
f" (обязателен: в .pm.json объявлен {key})"
|
||||
f" (обязателен: в .docs.json объявлен {key})"
|
||||
)
|
||||
elif key not in cfg and home is None:
|
||||
rep.skip(f"{name} — в .pm.json нет ключа {key}, проверка неприменима")
|
||||
rep.skip(f"{name} — в .docs.json нет ключа {key}, проверка неприменима")
|
||||
|
||||
|
||||
def check_stray(root: Path, rep: Report) -> None:
|
||||
@@ -527,7 +553,7 @@ def changed_files(root: Path, base: str, rep: Report) -> list[str] | None:
|
||||
def check_migrations(root: Path, cfg: dict, base: str | None, rep: Report) -> None:
|
||||
migrations = cfg.get("migrations")
|
||||
if not migrations:
|
||||
rep.skip("в .pm.json нет ключа migrations — сверка со схемой неприменима")
|
||||
rep.skip("в .docs.json нет ключа migrations — сверка со схемой неприменима")
|
||||
return
|
||||
if not base:
|
||||
rep.skip("база диффа не названа (--base) — сверка миграций со схемой не гонялась")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: docs
|
||||
description: Вести содержимое документов канона по ходу разработки — синк после сделанной задачи с построчным отчётом по каждому документу, заведение ADR промоутом из архивного design.md, запись наблюдения в research, запись дефекта и настройки конвейера в review.md, чистка architecture.md от поведения с маркерами долга. Использовать, когда задача сделана и надо обновить документацию, когда просят завести ADR или записать решение, занести находку о внешних данных, записать проскочивший дефект, разгрузить разросшуюся архитектуру. Раскладку и соответствие канону проверяет скилл canon.
|
||||
description: Вести содержимое документов канона по ходу разработки — синк после сделанной задачи с построчным отчётом по каждому документу, заведение ADR промоутом из архивного design.md или из записки разведки, запись наблюдения в research, запись дефекта и настройки конвейера в review.md, чистка architecture.md от поведения с маркерами долга. Использовать, когда задача сделана и надо обновить документацию, когда просят завести ADR или записать решение, занести находку о внешних данных, записать проскочивший дефект, разгрузить разросшуюся архитектуру. Раскладку и соответствие канону проверяет скилл canon.
|
||||
---
|
||||
|
||||
# Ведение содержимого канона
|
||||
@@ -97,6 +97,13 @@ description: Вести содержимое документов канона
|
||||
**ADR цитирует решение оттуда и ссылается на источник.** Не пересказывает и не
|
||||
сочиняет заново.
|
||||
|
||||
**Второй законный источник — записка разведки**, и приходит он от скилла
|
||||
`av-dev-code:research`: решение, принятое разведкой (намеренный отказ, выбор
|
||||
подхода, «проверили и не делаем»), `design.md` не имеет по построению — change по
|
||||
нему не будет никогда. Промоут при этом тот же: цитата и ссылка, но на записку.
|
||||
Перечень источников закрыт и живёт в [каноне](../canon/references/canon.md),
|
||||
раздел `adr/`.
|
||||
|
||||
**Триггер заведения, форма имени и правило замены — в
|
||||
[каноне](../canon/references/canon.md), раздел `adr/`.** Здесь они не
|
||||
повторяются: копия правила расходится с оригиналом на первой же смене версии
|
||||
@@ -106,9 +113,9 @@ description: Вести содержимое документов канона
|
||||
нет**. Строка «adr/ — не требуется: решение рутинное» и есть то, ради чего
|
||||
чек-лист существует; её отсутствие неотличимо от «забыл посмотреть».
|
||||
|
||||
Порядок работы: открой архивный `design.md` change, найди в `Decisions` то, что
|
||||
проходит триггер, процитируй решение и его причину, сошлись на источник, добавь
|
||||
строку в индекс `docs/adr/README.md` сверху.
|
||||
Порядок работы: открой источник — архивный `design.md` change либо записку
|
||||
разведки, — найди в нём решение, проходящее триггер, процитируй его и причину,
|
||||
сошлись на источник, добавь строку в индекс `docs/adr/README.md` сверху.
|
||||
|
||||
## Чистка `architecture.md`
|
||||
|
||||
@@ -159,8 +166,10 @@ description: Вести содержимое документов канона
|
||||
|
||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||
|
||||
<!-- /копия: граница-плагинов -->
|
||||
|
||||
|
||||
@@ -60,8 +60,10 @@ check` и его скрипт; здесь начинается там, где к
|
||||
|
||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||
|
||||
<!-- /копия: граница-плагинов -->
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ description: "Завести новый проект — сессия вопро
|
||||
| `passport.md` | `architecture.md` |
|
||||
| `CLAUDE.md` | `database.md` |
|
||||
| `security.md` | `conventions/` |
|
||||
| `docs/.pm.json` | `research/`, `adr/` |
|
||||
| `docs/.docs.json` | `research/`, `adr/` |
|
||||
| | `review.md` — журнал пуст, настройка появится с первым ревью |
|
||||
|
||||
Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет,
|
||||
@@ -97,8 +97,10 @@ description: "Завести новый проект — сессия вопро
|
||||
|
||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||
|
||||
<!-- /копия: граница-плагинов -->
|
||||
|
||||
@@ -117,7 +119,8 @@ description: "Завести новый проект — сессия вопро
|
||||
|
||||
**Вызов не разрешился** — проект без конвейера живёт без OpenSpec законно:
|
||||
строка доклада, и дальше; `docs.py check` о каталоге тоже промолчит.
|
||||
4. Заведи `docs/.pm.json` с текущей версией канона.
|
||||
4. Заведи `docs/.docs.json` с текущей версией канона — число берётся из
|
||||
`docs.py version`, а не из памяти.
|
||||
5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и
|
||||
отдельным файлом не остаётся: два дома для одного замысла разойдутся на
|
||||
первом же уточнении.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: tasks
|
||||
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Расстановка приоритетов и разбор накопившегося — скилл groom. Не реализует задачи — этим занимается скилл решения задачи.
|
||||
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Он же повышает каталог до текущей версии формата по своему журналу версий, когда tasks.py check говорит, что каталог отстал. Расстановка приоритетов и разбор накопившегося — скилл groom. Не реализует задачи — этим занимается скилл решения задачи.
|
||||
---
|
||||
|
||||
# Задачи
|
||||
@@ -399,7 +399,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
||||
| 0 | сошлось / сделано | дальше по сценарию |
|
||||
| 1 | **только `check`:** найден дрейф индексов и файлов | `check --fix`, остаток разобрать |
|
||||
| 2 | ошибка употребления: аргументы или нарушенное правило | читать сообщение, это отказ по существу |
|
||||
| 3 | окружение: каталог не найден, конфиг битый или мимо диска | чинится путём или `docs/.pm.json`, повтор не поможет |
|
||||
| 3 | окружение: каталог не найден, конфиг битый или мимо диска | чинится путём или `<каталог задач>/.tasks.json`, повтор не поможет |
|
||||
| 4 | внутренний сбой | дефект скрипта, доложить |
|
||||
|
||||
Различать 1 и 3 обязательно: «дрейф в беклоге» — рабочая ситуация, «каталога
|
||||
@@ -486,6 +486,39 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
||||
[fix](references/task-fix.md) · [chore](references/task-chore.md) ·
|
||||
[research](references/task-research.md).
|
||||
|
||||
## Версия формата
|
||||
|
||||
Формат каталога задач меняется, и проект должен знать, к какой его версии
|
||||
приведён. Число живёт ключом `tasks` в `<каталог задач>/.tasks.json`, журнал
|
||||
версий — [references/changelog.md](references/changelog.md), сверяет их
|
||||
`tasks.py check`: отстало — строка расхождения, ушло вперёд — устарел плагин.
|
||||
|
||||
**Версия своя, а не канона документов.** Плагин ставится в одиночку: проект,
|
||||
взявший учёт работ без `av-dev-docs`, каталога `docs/` не имеет вовсе, а значит
|
||||
не имеет и версии канона — сверять было бы не с чем. Обратной совместимости у
|
||||
формата нет: есть «приведён» и «не приведён».
|
||||
|
||||
**`upgrade` — повысить каталог до текущего формата:**
|
||||
|
||||
1. `python3 $tk check --dir D` — первая же строка расхождений называет версию
|
||||
проекта и версию скрипта. Проект новее скрипта — **обнови маркетплейс**, а не
|
||||
проект: это отстал плагин.
|
||||
2. Иди по [журналу](references/changelog.md) снизу вверх от версии проекта до
|
||||
текущей и делай названное в каждой записи. Записи независимы и применяются по
|
||||
порядку.
|
||||
3. Подними `tasks` в `.tasks.json` до текущей — руками, последним шагом. Раньше
|
||||
времени поднятое число объявляет каталог приведённым к формату, шагов
|
||||
которого никто не делал; `check --fix` этого не пишет намеренно.
|
||||
4. `check --dir D` ещё раз — до отсутствия расхождений.
|
||||
|
||||
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
|
||||
это дефект журнала, и о нём надо сказать, а не догадываться.
|
||||
|
||||
**Канон документов сюда не вмешивается.** Его журнал двигает своё число в
|
||||
`docs/.docs.json` и вправе сказать «позови этот скилл», но не двигать версию
|
||||
формата задач: две версии, ходящие по одному журналу, разъедутся на первом же
|
||||
проекте, где стоит один плагин без другого.
|
||||
|
||||
## Сценарии
|
||||
|
||||
### Завести запись из диалога
|
||||
@@ -647,17 +680,19 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
||||
действительно новый, а перевод чужой раскладки делает `av-dev-docs:canon`.
|
||||
У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и
|
||||
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
|
||||
- **Настройки живут в `<каталог задач>/.tasks.json`** — свой файл у своего
|
||||
плагина: **имена** файлов и заголовков, и только если они отличаются от
|
||||
умолчания. Неизвестный ключ — код 3 на любой команде, так что лишнее слово в
|
||||
этом объекте останавливает работу с задачами целиком.
|
||||
- **Версия формата и настройки живут в `<каталог задач>/.tasks.json`** — свой
|
||||
файл у своего плагина: ключ `tasks` с версией формата плюс **имена** файлов и
|
||||
заголовков, и последние — только если отличаются от умолчания. Неизвестный
|
||||
ключ — код 3 на любой команде, так что лишнее слово в этом объекте
|
||||
останавливает работу с задачами целиком.
|
||||
|
||||
Дом именно свой, а не `docs/.pm.json`, потому что `docs/` принадлежит плагину
|
||||
канона: проект, поставивший учёт работ без него, каталога `docs/` не имеет
|
||||
вовсе. Прежний ключ `tasks` в `docs/.pm.json` читается, **только когда своего
|
||||
файла нет** — для проектов, заведённых до раскола плагинов; скрипт при этом
|
||||
говорит замечанием, куда его перенести. Есть оба — побеждает свой, и об этом
|
||||
тоже говорится вслух: молча выбранный из двух конфиг это дрейф.
|
||||
Дом именно свой, а не `docs/.docs.json`, потому что `docs/` принадлежит
|
||||
плагину канона: проект, поставивший учёт работ без него, каталога `docs/` не
|
||||
имеет вовсе. Прежний ключ `tasks` в `docs/.pm.json` читается, **только когда
|
||||
своего файла нет** — для проектов, заведённых до раскола плагинов; скрипт при
|
||||
этом говорит замечанием, куда его перенести. Есть оба — побеждает свой, и об
|
||||
этом тоже говорится вслух: молча выбранный из двух конфиг это дрейф. Версию
|
||||
прежний дом не знает и знать не может — она читается только из своего файла.
|
||||
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
||||
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
|
||||
второй список разошёлся бы с заголовками молча.
|
||||
|
||||
@@ -66,7 +66,9 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
||||
всегда `tasks`. Секции беклога (`--sections`) — по умолчанию
|
||||
`Ядро,Инфра`; если у проекта деление другое по существу, оно называется
|
||||
здесь, а не подгоняется под умолчание, и становится **заголовками `##`
|
||||
индекса** — их единственным домом. В `docs/.pm.json` секции не пишутся.
|
||||
индекса** — их единственным домом. В `.tasks.json` секции не пишутся: там
|
||||
версия формата и имена частей, а второй список секций разошёлся бы с
|
||||
заголовками молча.
|
||||
2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два
|
||||
прохода дадут два несогласованных состояния.
|
||||
3. **Заполни карту**: `slug` (английский), `section`, `goal` у каждой записи;
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# Журнал версий формата задач
|
||||
|
||||
Одна запись на версию. Проект знает свою версию из ключа `tasks` в `<каталог
|
||||
задач>/.tasks.json`; повышение (`upgrade` в [SKILL.md](../SKILL.md), раздел
|
||||
«Версия формата») идёт по записям снизу вверх от версии проекта до текущей и
|
||||
делает то, что в них названо.
|
||||
|
||||
Правило записи: **что добавилось, что переехало, что удалено, что сделать
|
||||
проекту**. Без последнего пункта запись бесполезна — по ней и работает
|
||||
повышение.
|
||||
|
||||
Версия — целое число. Обратной совместимости у формата нет: есть «приведён» и «не
|
||||
приведён».
|
||||
|
||||
**Это журнал формата задач, а не канона документов.** Числа у них разные и
|
||||
двигаются порознь: плагин `av-dev-tasks` ставится в одиночку, и у проекта без
|
||||
`av-dev-docs` версии канона нет вовсе. Журнал канона —
|
||||
`references/changelog.md` скилла `av-dev-docs:canon`.
|
||||
|
||||
---
|
||||
|
||||
## Версия 1 — 2026-08-11
|
||||
|
||||
Первая объявленная версия формата. До неё каталог задач версии не имел вовсе:
|
||||
формат менялся, а сказать, к какому его состоянию приведён конкретный проект,
|
||||
было нечем — `tasks.py` о расхождении молчал, и отставший каталог выглядел
|
||||
здоровым ровно до первой команды, которая об него спотыкалась.
|
||||
|
||||
**Что появилось.** Ключ `tasks` в `<каталог задач>/.tasks.json` — целое число,
|
||||
версия формата. Сам файл стал **обязательным**: до сих пор он заводился только
|
||||
ради имён, отличных от умолчания, и проект с умолчаниями жил без него. Версия —
|
||||
не настройка, от которой можно отказаться, поэтому `init` и `adopt apply` теперь
|
||||
пишут файл всегда, а `check` требует числа и сверяет его со своим.
|
||||
|
||||
**Что версия значит, а что нет.** Она отвечает на один вопрос — «по какой записи
|
||||
журнала повышать каталог». Что записи применены **по существу**, из числа не
|
||||
следует: двигают его руками, и соврать им так же легко, как любой другой
|
||||
строкой. `check --fix` недостающее число не приписывает намеренно — это было бы
|
||||
объявлением каталога приведённым к формату, шагов которого никто не делал.
|
||||
|
||||
**Чего в этой записи нет.** Переезды, случившиеся до появления числа, — каталог
|
||||
из `docs/` в корень (канон 11) и отмена спринтов (канон 12) — задним числом сюда
|
||||
не переписаны. Они уже названы журналом канона, и второй перечень тех же шагов
|
||||
разошёлся бы с первым. Версия 1 — это формат на день её появления, что бы
|
||||
проекту ни пришлось пройти до неё.
|
||||
|
||||
**Что сделать проекту.**
|
||||
|
||||
1. **Догнать формат по журналу канона, если каталог отстал.** Признаки известны
|
||||
поимённо: каталог лежит в `docs/tasks/` (канон 11 велит `git mv docs/tasks
|
||||
tasks` и починку относительных ссылок внутри записей), в нём есть `SPRINT.md`
|
||||
или теги `sprint:<слаг>` (канон 12 велит снести файл, вернуть строки в беклог
|
||||
через `check --fix` и расставить порядок грумингом). Ничего из этого нет —
|
||||
каталог уже в сегодняшнем формате, и шаг пропускается.
|
||||
2. **Завести `<каталог задач>/.tasks.json`**, если его нет. Имена частей в него
|
||||
не переписываются: там только то, что отличается от умолчания.
|
||||
3. **Записать версию**: `"tasks": 1` первым ключом.
|
||||
4. `tasks.py check --dir <каталог задач>` — до отсутствия расхождений.
|
||||
|
||||
**Что при этом не трогается.** Записи в `items/`, индексы и `REJECTED.md` не
|
||||
меняются ни строкой: версия 1 объявляет то, что уже есть, а не переделывает его.
|
||||
@@ -65,7 +65,10 @@
|
||||
источники, что заведомо вне.
|
||||
4. **Провести разведку** и **записать ответ по названному адресу**. Числа — с
|
||||
провенансом: с командой или условиями, которыми получены. Число без источника
|
||||
проход ревью обязан читать как условие, а не как замер.
|
||||
проход ревью обязан читать как условие, а не как замер. Проводит её конвейер
|
||||
проекта — в плагине `av-dev-code` это скилл `resolve`, сценарий разведки;
|
||||
плагина нет — разведка ведётся как проект привык, а этот скилл её только
|
||||
заводит и закрывает.
|
||||
5. **Разложить исход на задачи** — если он их родил. Разведка кончается одним из
|
||||
трёх: заведены задачи, записано знание, отказ. **Отказ — полноправный исход**:
|
||||
«проверили, не проблема» экономит работу.
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
Раскладка. Путь каталога — `tasks/` в корне репозитория, жёстко. Каталог
|
||||
принадлежит этому плагину, а не канону документов: `docs/` ведёт другой плагин, и
|
||||
проект, поставивший учёт работ без него, каталога `docs/` не имеет вовсе. Имена
|
||||
внутри настраиваются через `tasks/.tasks.json`.
|
||||
внутри и **версия формата** живут в `tasks/.tasks.json`; журнал версий —
|
||||
references/changelog.md рядом со скриптом.
|
||||
|
||||
tasks/
|
||||
items/ задачи и цели файлами, <slug>.md
|
||||
@@ -107,8 +108,26 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
CONFIG_NAME = ".tasks.json" # дом настроек: свой файл в каталоге задач
|
||||
PM_CONFIG_REL = "../.pm.json" # прежний дом: docs/.pm.json, ключ "tasks"
|
||||
CONFIG_NAME = ".tasks.json" # дом настроек и версии: свой файл в каталоге
|
||||
PM_CONFIG_REL = "../.pm.json" # прежний дом настроек: docs/.pm.json, ключ "tasks"
|
||||
|
||||
# Версия формата задач — **своя, а не канона документов**. Число живёт ключом
|
||||
# `tasks` в `.tasks.json`, журнал версий — references/changelog.md рядом со
|
||||
# скриптом, повышает его операция `upgrade` скилла `av-dev-tasks:tasks`.
|
||||
#
|
||||
# Число именно своё, потому что плагин ставится в одиночку: проект, взявший учёт
|
||||
# работ без канона документов, каталога `docs/` не имеет вовсе, а значит не имеет
|
||||
# и версии канона — сверять было бы не с чем. Копия чужого числа в этом скрипте
|
||||
# была бы вторым домом для одной версии и разъехалась бы молча при обновлении
|
||||
# одного плагина без другого.
|
||||
#
|
||||
# Переезды каталога задач, случившиеся до появления этого числа (в корень —
|
||||
# канон 11, отмена спринтов — канон 12), задним числом сюда не переписаны: они
|
||||
# уже названы журналом канона, и второй перечень тех же шагов разошёлся бы с
|
||||
# первым. Версия 1 — формат на день её появления, что бы проекту ни пришлось
|
||||
# пройти до неё.
|
||||
FORMAT_VERSION = 1
|
||||
VERSION_KEY = "tasks"
|
||||
|
||||
EXIT_OK = 0
|
||||
EXIT_DRIFT = 1
|
||||
@@ -433,7 +452,7 @@ class Layout:
|
||||
|
||||
|
||||
def load_config(root: Path) -> dict:
|
||||
"""Настройки каталога задач.
|
||||
"""Настройки каталога задач и версия его формата.
|
||||
|
||||
Дом — `<каталог задач>/.tasks.json`: **свой файл у своего плагина**. Ключ
|
||||
`tasks` в `docs/.pm.json` читается, пока живы проекты, заведённые до раскола
|
||||
@@ -445,6 +464,10 @@ def load_config(root: Path) -> dict:
|
||||
плагину. Проект, поставивший учёт задач без канона документов, каталога
|
||||
`docs/` не имеет вовсе, и дом настроек, лежащий в чужом дереве, был бы домом,
|
||||
которого у половины проектов нет.
|
||||
|
||||
Версия формата (ключ `tasks`) читается **только из своего файла**: прежний
|
||||
дом её не знал и знать не может, и молча выведенная из его отсутствия версия
|
||||
была бы догадкой о том, что чинится одной строкой.
|
||||
"""
|
||||
path = root / CONFIG_NAME
|
||||
pm = (root / PM_CONFIG_REL).resolve()
|
||||
@@ -485,7 +508,7 @@ def _read_json(path: Path) -> dict:
|
||||
|
||||
|
||||
def _validate_config(data: dict, path: Path) -> dict:
|
||||
unknown = set(data) - set(DEFAULTS)
|
||||
unknown = set(data) - set(DEFAULTS) - {VERSION_KEY}
|
||||
# Ключ «plan» был домом оглавления целей до того, как файл стал ROADMAP.md.
|
||||
# Без этой ветки проект со старым конфигом получал бы «неизвестный ключ» и
|
||||
# искал опечатку там, где на самом деле переименование канона.
|
||||
@@ -495,9 +518,19 @@ def _validate_config(data: dict, path: Path) -> dict:
|
||||
f" av-dev-docs:canon (upgrade), а не правь ключ в одиночку:"
|
||||
f" файл и ссылки на него переезжают вместе с ним")
|
||||
if unknown:
|
||||
known = sorted({*DEFAULTS, VERSION_KEY})
|
||||
raise Env(f"{path}: неизвестные ключи: {', '.join(sorted(unknown))}"
|
||||
f" (известны: {', '.join(sorted(DEFAULTS))})")
|
||||
f" (известны: {', '.join(known)})")
|
||||
# Версия — единственный ключ-число: остальные это имена файлов и заголовков.
|
||||
# Битое число тут останавливает работу целиком (код 3), а не идёт дрейфом,
|
||||
# потому что «на какой версии формата каталог» решает, чему верить дальше.
|
||||
got = data.get(VERSION_KEY)
|
||||
if VERSION_KEY in data and (isinstance(got, bool) or not isinstance(got, int)):
|
||||
raise Env(f"{path}: ключ «{VERSION_KEY}» — версия формата задач,"
|
||||
f" ожидалось целое число, а не {got!r}")
|
||||
for key, value in data.items():
|
||||
if key == VERSION_KEY:
|
||||
continue
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
raise Env(f"{path}: ключ «{key}» — ожидалась непустая строка")
|
||||
if key in PATH_KEYS and (value.startswith("/") or ".." in Path(value).parts):
|
||||
@@ -536,10 +569,46 @@ def config_problems(lay: Layout) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def version_problems(lay: Layout) -> list[str]:
|
||||
"""Версия формата задач: объявлена ли и та ли, которую знает скрипт.
|
||||
|
||||
Отвечает на один вопрос — «по какой записи журнала повышать каталог», — и
|
||||
ни на какой другой. Что запись оформлена по правилам своей версии, отсюда не
|
||||
следует: число двигает тот, кто прошёл шаги, и соврать им так же легко, как
|
||||
любой другой строкой. Цена вранья при этом низкая, а польза от вопроса есть
|
||||
ровно там, где формат поменялся, а каталог остался прежним.
|
||||
|
||||
`check --fix` этого не чинит намеренно: приписать недостающее число значило
|
||||
бы объявить каталог приведённым к формату, шагов которого никто не делал.
|
||||
Заводит число `init`, двигает — операция `upgrade` скилла.
|
||||
"""
|
||||
path = lay.root / CONFIG_NAME
|
||||
# Прежний дом (`docs/.pm.json`) версии не знает, поэтому спрашиваем строго
|
||||
# свой файл: «конфиг нашёлся» и «версия объявлена» это разные события.
|
||||
if not path.is_file():
|
||||
return [f"нет {path} — версия формата задач не объявлена."
|
||||
f" Заведи файл с «{VERSION_KEY}»: {FORMAT_VERSION} (журнал версий —"
|
||||
f" references/changelog.md скилла av-dev-tasks:tasks)"]
|
||||
# Что число целое, уже проверил `_validate_config` — иначе сюда не дошли бы
|
||||
# вовсе (код 3). Здесь `isinstance` значит ровно «ключ есть».
|
||||
got = lay.cfg.get(VERSION_KEY)
|
||||
if not isinstance(got, int):
|
||||
return [f"{path}: нет ключа «{VERSION_KEY}» — версия формата не объявлена,"
|
||||
f" текущая {FORMAT_VERSION}"]
|
||||
if got < FORMAT_VERSION:
|
||||
return [f"каталог приведён к формату версии {got}, текущая —"
|
||||
f" {FORMAT_VERSION}: нужно повышение по журналу"
|
||||
f" (скилл av-dev-tasks:tasks, операция upgrade)"]
|
||||
if got > FORMAT_VERSION:
|
||||
return [f"каталог приведён к формату версии {got}, а скрипт знает"
|
||||
f" {FORMAT_VERSION}: устарел плагин, обнови маркетплейс"]
|
||||
return []
|
||||
|
||||
|
||||
def looks_like_tasks(p: Path) -> bool:
|
||||
if (p / CONFIG_NAME).is_file():
|
||||
return True
|
||||
try: # индекс мог быть переименован через docs/.pm.json
|
||||
try: # индекс мог быть переименован через конфиг
|
||||
name = load_config(p).get("backlog", DEFAULTS["backlog"])
|
||||
except Env:
|
||||
name = DEFAULTS["backlog"]
|
||||
@@ -1027,7 +1096,10 @@ def check(lay: Layout, fix: bool = False) -> int:
|
||||
entries = {k: v[0] for k, v in idx.items()}
|
||||
sections = {k: v[1] for k, v in idx.items()}
|
||||
tasks = tasks_of(lay)
|
||||
errors: list[str] = []
|
||||
# Версия формата идёт первой строкой расхождений: остальные находки читаются
|
||||
# иначе, когда каталог отстал от формата, — часть из них тогда не дрейф, а
|
||||
# непройденный шаг журнала.
|
||||
errors: list[str] = version_problems(lay)
|
||||
notes: list[str] = []
|
||||
label = {k: lay.name(k) for k in lay.indexes}
|
||||
|
||||
@@ -2524,11 +2596,13 @@ def apply_fixes(lay: Layout) -> tuple[list[str], list[str]]:
|
||||
def init_files(lay: Layout, sections: list[str], roadmap_sections: list[str],
|
||||
cfg: dict) -> dict[Path, str]:
|
||||
out: dict[Path, str] = {}
|
||||
if cfg:
|
||||
# Пишем всегда в свой `.tasks.json`, даже когда рядом живёт
|
||||
# `docs/.pm.json`: дом настроек принадлежит этому плагину, а `docs/` —
|
||||
# другому, и его в проекте может не быть. load_config читает свой файл
|
||||
# первым, так что записанное сюда и прочитается отсюда.
|
||||
# Файл заводится всегда, даже когда все имена умолчательные: в нём живёт
|
||||
# версия формата, а версия — не настройка, от которой можно отказаться.
|
||||
#
|
||||
# Пишем всегда в свой `.tasks.json`, даже когда рядом живёт `docs/.pm.json`:
|
||||
# дом настроек принадлежит этому плагину, а `docs/` — другому, и его в
|
||||
# проекте может не быть. load_config читает свой файл первым, так что
|
||||
# записанное сюда и прочитается отсюда.
|
||||
out[lay.root / CONFIG_NAME] = json.dumps(cfg, ensure_ascii=False,
|
||||
indent=2) + "\n"
|
||||
out[lay.index("backlog")] = (
|
||||
@@ -2589,9 +2663,15 @@ def uniq_sections(raw: str) -> list[str]:
|
||||
def cmd_init(root: Path, a: argparse.Namespace) -> int:
|
||||
if not dir_within_cwd(root):
|
||||
raise Usage(f"--dir вне рабочего каталога: {root}")
|
||||
cfg = {k: v for k, v in (("items", a.items), ("backlog", a.backlog), ("roadmap", a.roadmap),
|
||||
("rejected", a.rejected)) if v}
|
||||
lay = Layout(root, cfg)
|
||||
names = {k: v for k, v in (("items", a.items), ("backlog", a.backlog),
|
||||
("roadmap", a.roadmap), ("rejected", a.rejected)) if v}
|
||||
# Версия формата — первым ключом и всегда: каталог, заведённый сегодня,
|
||||
# приведён к сегодняшнему формату, и объявить это должен тот, кто его завёл.
|
||||
# Имена частей — следом и только те, что названы явно: умолчание, записанное
|
||||
# в файл, стало бы вторым домом для того же имени. В раскладку версия не
|
||||
# идёт — `Layout` про имена, и число среди имён там ничего не значит.
|
||||
cfg = {VERSION_KEY: FORMAT_VERSION, **names}
|
||||
lay = Layout(root, names)
|
||||
if lay.index("backlog").exists():
|
||||
raise Usage(f"{lay.index('backlog')} уже есть — каталог задач заведён")
|
||||
|
||||
@@ -2615,8 +2695,9 @@ def cmd_init(root: Path, a: argparse.Namespace) -> int:
|
||||
print(f"каталог задач заведён: {root}")
|
||||
print(f" секции беклога: {', '.join(sections)};"
|
||||
f" секции роадмапа канонические: {', '.join(roadmap_sections)}")
|
||||
if cfg:
|
||||
print(f" имена частей записаны в {root / CONFIG_NAME}")
|
||||
what = ("версия формата и имена частей записаны" if names
|
||||
else "версия формата записана")
|
||||
print(f" {what} в {root / CONFIG_NAME}: формат {FORMAT_VERSION}")
|
||||
return EXIT_OK
|
||||
|
||||
|
||||
@@ -2974,7 +3055,11 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int:
|
||||
|
||||
# --- план записи ---
|
||||
wr = Plan()
|
||||
for path, text in init_files(lay, sections, roadmap_sections, {}).items():
|
||||
# Версия та же, что у `init`: каталог выводится из чужой раскладки сегодня и
|
||||
# сегодняшним форматом, сколько бы лет ни было тому, из чего он выведен.
|
||||
# Имён частей здесь нет — адаптация раскладывает всё по умолчаниям.
|
||||
for path, text in init_files(lay, sections, roadmap_sections,
|
||||
{VERSION_KEY: FORMAT_VERSION}).items():
|
||||
wr.file(path, text)
|
||||
backlog_lines = init_files(lay, sections, roadmap_sections, {})[lay.index("backlog")].splitlines()
|
||||
roadmap_lines = init_files(lay, sections, roadmap_sections, {})[lay.index("roadmap")].splitlines()
|
||||
|
||||
@@ -57,6 +57,7 @@ OWNERS = {
|
||||
# Адрес, верный на момент записи, здесь останется навсегда, и это не дрейф.
|
||||
JOURNALS = {
|
||||
"av-dev-docs/skills/canon/references/changelog.md": "журнал версий канона",
|
||||
"av-dev-tasks/skills/tasks/references/changelog.md": "журнал версий формата задач",
|
||||
"DECISIONS.md": "журнал решений",
|
||||
"HISTORY.md": "журнал работ",
|
||||
"NOTES.md": "рабочие заметки",
|
||||
@@ -102,7 +103,7 @@ def stem(name: str) -> str:
|
||||
"""Имя документа без формы: файл, каталог и `.*` — один и тот же адрес.
|
||||
|
||||
Форму дома канон оставляет проекту: `docs/security.md` и `docs/security/`
|
||||
называют одно. Скрытые имена (`.pm.json`) остаются как есть — точка в них
|
||||
называют одно. Скрытые имена (`.docs.json`) остаются как есть — точка в них
|
||||
не расширение.
|
||||
"""
|
||||
name = name.rstrip(".")
|
||||
@@ -119,7 +120,7 @@ def vocabularies(root: Path) -> tuple[dict[str, set[str]], dict[str, str]]:
|
||||
docs_names = {stem(n) for n in docs.DOCS}
|
||||
docs_names |= {stem(n) for n in docs.CONDITIONAL_DOCS}
|
||||
docs_names |= {stem(n) for n in docs.NOT_DOCS}
|
||||
# `docs/.pm.json` объявлен обязательным файлом вне раскладки.
|
||||
# `docs/.docs.json` объявлен обязательным файлом вне раскладки.
|
||||
docs_names |= {stem(Path(p).name) for p in docs.REQUIRED if p.startswith("docs/")}
|
||||
|
||||
tasks_names = {stem(tasks.DEFAULTS[k]) for k in tasks.PATH_KEYS}
|
||||
@@ -201,7 +202,7 @@ def main() -> int:
|
||||
if own_themes:
|
||||
print(f"Имён вне перечня {len(own_themes)}, и они **не судятся** —"
|
||||
f" список тем открытый: {', '.join(sorted(own_themes))}.")
|
||||
print(f"Не проверялось: журналы ({len(JOURNALS)} файла — они описывают"
|
||||
print(f"Не проверялось: журналы ({len(JOURNALS)} шт. — они описывают"
|
||||
f" прошлые состояния), адреса `openspec/*` (раскладка чужого"
|
||||
f" инструмента, у нас владельца нет), упоминания в комментариях"
|
||||
f" скриптов — сверяется только markdown.")
|
||||
|
||||
@@ -51,7 +51,9 @@
|
||||
|
||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||
|
||||
<!-- /дом: граница-плагинов -->
|
||||
|
||||
Reference in New Issue
Block a user