Compare commits
27
Commits
6ff12fedd5
...
12b77c3393
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12b77c3393
|
||
|
|
12882911a9
|
||
|
|
63ba36d71d
|
||
|
|
15dba79993
|
||
|
|
c1890d9e71
|
||
|
|
4354cc4146
|
||
|
|
df5af47dc3
|
||
|
|
4a56753f0b
|
||
|
|
3653c5cff5
|
||
|
|
a73eedb893
|
||
|
|
1bce854535
|
||
|
|
ee53ef8af8
|
||
|
|
53cf6baedf
|
||
|
|
c5e6883461
|
||
|
|
c3828b3713
|
||
|
|
872732989a
|
||
|
|
c6be879831
|
||
|
|
c91492e3f0
|
||
|
|
e408c51ac1
|
||
|
|
f40e0cd7bb
|
||
|
|
fdadfb65ac
|
||
|
|
9453a218d1
|
||
|
|
f0dd8f70c1
|
||
|
|
1f31ac6afd
|
||
|
|
00ddfb0dde
|
||
|
|
86e22d932c
|
||
|
|
c669215fc8
|
@@ -6,14 +6,19 @@
|
|||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "av-dev-pm",
|
"name": "av-dev-docs",
|
||||||
"source": "./av-dev-pm",
|
"source": "./av-dev-docs",
|
||||||
"description": "Управление продуктом: канон документов проекта, задачи и цели вместо приоритетов, спринт под одну цель с заморозкой набора, старт проекта интервью по брифу и приведение существующего к канону. Ничего не выполняет сам и никакого пайплайна не требует: задача выполняется чем угодно, а канон описывает документы, из которых конвейер ревью берёт проектную конкретику."
|
"description": "Документация проекта: канон раскладки (CLAUDE.md плюс docs/ — паспорт, архитектура, схема БД, безопасность, конвенции, разведка, ADR, журнал ревью), роли документов и правило единственного дома. Три операции одной машиной сравнения — check, adopt, upgrade — со скриптом docs.py; заведение нового проекта интервью по брифу; ведение содержимого по ходу разработки: синк после сделанной задачи, ADR промоутом из архивного design.md, записка разведки, запись дефекта в журнал ревью. Смысловое, чего скрипт не видит, судит скилл healthcheck двумя агентами разом — doc-consistency (документы между собой и с openspec) и doc-code-drift (факты против кода); язык документов вычитывает агент doc-wording. Задач не ведёт — это плагин av-dev-tasks, и он опционален."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "av-dev-pipeline",
|
"name": "av-dev-tasks",
|
||||||
"source": "./av-dev-pipeline",
|
"source": "./av-dev-tasks",
|
||||||
"description": "Проведение задачи через цикл SDD и конвейер ревью с обязательным триажем, плюс прогон нескольких задач разом. Требует OpenSpec. Задача принимается и обычным текстом; плагин av-dev-pm опционален — он даёт документы канона для проходов ревью и учёт задач, без него прогон деградирует поразрядно и говорит об этом."
|
"description": "Задачи и цели каталогом markdown-файлов: одна запись — файл в items/ плюс строка ровно в одном индексе, у записи тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение из диалога с фильтром и дедупом, разбор находок ревью в задачи, декомпозиция на независимо полезные части, гигиена полей, согласованность индексов скриптом tasks.py. Приоритет — явный порядок строк в беклоге, и расставляет его скилл groom: интерактивный разбор на два вопроса, что сейчас самое важное и что перестало быть важным. Готовность записи к работе проверяет команда ready. Записи вычитывают два прохода: task-form (форма записи) и task-wording (язык). Канон документов ведёт плагин av-dev-docs, он опционален. Задач не выполняет — этим занимается конвейер проекта."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "av-dev-code",
|
||||||
|
"source": "./av-dev-code",
|
||||||
|
"description": "Решение одной задачи от постановки до закрытия скиллом resolve — полный цикл Spec Driven Development с двумя плановыми остановками: чекпоинт вариантов у исследовательской задачи и чекпоинт с объяснением человеческим языком после ревью дизайна у всякой. Между ними работа идёт без согласований. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом и обязательным триажем. Требует OpenSpec и сам его заводит скиллом openspec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "av-dev-git",
|
"name": "av-dev-git",
|
||||||
|
|||||||
+520
@@ -3127,3 +3127,523 @@ JJJ): у профиля обязан быть один правильный от
|
|||||||
точно говорит, могла ли измениться дорогая величина. Так дорогая проверка
|
точно говорит, могла ли измениться дорогая величина. Так дорогая проверка
|
||||||
остаётся редкой и при этом не забытой.
|
остаётся редкой и при этом не забытой.
|
||||||
|
|
||||||
|
|
||||||
|
## 49. Дом общего правила вышел из плагина (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКА. Язык уехал в `shared/`, потому что общее правило не может принадлежать
|
||||||
|
половине.** Дом языка лежал в `av-dev-pm/skills/canon/references/language.md` —
|
||||||
|
внутри одного скилла одного плагина. Пока плагин был один, это читалось как «дом
|
||||||
|
рядом с главным потребителем». Разделение на самодостаточные `docs` и `tasks`
|
||||||
|
превращает то же место в утверждение, что язык принадлежит канону: плагин задач,
|
||||||
|
поставленный без канона, потерял бы правила письма вместе с ним. Дом переехал в
|
||||||
|
`shared/` и не принадлежит ни одному плагину, а плагины везут дословные копии.
|
||||||
|
Самодостаточность держится **копией, а не ссылкой**: `shared/` нужен этому
|
||||||
|
репозиторию, а не установленному плагину.
|
||||||
|
|
||||||
|
**АЕАКБ. Устав вычитки стал копией целиком, а не четырьмя таблицами из десяти.**
|
||||||
|
`doc-wording` копировал из дома англицизмы, словарь, жаргон и порог правки —
|
||||||
|
четыре блока; девять правил он излагал своими словами, и эти слова с домом никто
|
||||||
|
не сверял. Там дрейф и копился молча: в доме правило «одна мысль — одно
|
||||||
|
предложение» требовало выносить придаточное, в уставе — не резать причинную
|
||||||
|
связь, и каждая версия выглядела полной. Теперь блок один, `язык-правила`, и
|
||||||
|
берётся он целиком. Условие переезда: текст правил написан безлично, а всё,
|
||||||
|
обращённое к проходу («пиши так-то», «про это молчи»), вынесено из блока в свой
|
||||||
|
раздел устава. **Правило принадлежит дому, способ доложить о нём — уставу.**
|
||||||
|
|
||||||
|
**АЕАКВ. `порог-правки` остался отдельным блоком, и это следствие разметки, а не
|
||||||
|
вкуса.** Его берёт `task-form`, который правил языка не проверяет вовсе.
|
||||||
|
Вложенных блоков `copies.py` не знает — лежи порог внутри `язык-правила`, забрать
|
||||||
|
его отдельно было бы нечем, и `task-form` вёз бы весь устав чужого прохода.
|
||||||
|
Разрез домов идёт **по потребителю, а не по теме**: три блока вместо одного
|
||||||
|
стоят двух лишних маркеров и снимают ложную зависимость.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
171. **Общее правило не хранится внутри одного из тех, кто им пользуется.** Пока
|
||||||
|
пользователь один, дом рядом с ним выглядит удобством; со вторым
|
||||||
|
пользователем то же место начинает утверждать, что правило принадлежит
|
||||||
|
первому.
|
||||||
|
172. **Пересказ своими словами — это копия, которую никто не сверяет.** Блок,
|
||||||
|
взятый целиком, читается дороже, но расхождение в нём ловит машина;
|
||||||
|
сокращённое изложение экономит строки и платит молчаливым дрейфом.
|
||||||
|
|
||||||
|
|
||||||
|
## 50. Вычитка раздвоилась по плагину, а не по правилу (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКГ. Решение ППП отменено, и отменено не по своей оси.** ППП говорило: агент
|
||||||
|
называется `doc-wording`, а не `task-wording`, потому что правила языка относятся
|
||||||
|
ко всем проектным текстам — документам канона, решениям ADR, запискам разведки,
|
||||||
|
— а не к одним задачам. Утверждение верно и сегодня; оно и есть причина, по
|
||||||
|
которой правила уехали в `shared/`. Но из общности **правила** не следует
|
||||||
|
общность **прохода**: `docs` и `tasks` расходятся самодостаточными плагинами, а
|
||||||
|
самодостаточный плагин не может зависеть от агента соседа. Проходов теперь два,
|
||||||
|
`doc-wording` и `task-wording`, и разведены они **по охвату** — впервые в этом
|
||||||
|
репозитории: и `task-form` против вычитки, и `doc-consistency` против
|
||||||
|
`doc-code-drift` разведены по глубине.
|
||||||
|
|
||||||
|
**АЕАКД. Разрез по охвату дублирует устав, и потому весь общий текст стал
|
||||||
|
домом.** Два прохода судят по одним и тем же девяти правилам; отличаются они
|
||||||
|
входом, соседями по границе и тем, чем подставляется находка — командой `edit` у
|
||||||
|
задач, редактором у документов. Написать уставы порознь значило бы завести ровно
|
||||||
|
тот дрейф, который днём раньше нашёлся внутри самого `doc-wording`. Общими
|
||||||
|
домами стали `язык-правила`, `порог-правки` и новый `вычитка-доклад` — форма
|
||||||
|
находки и границы покрытия. Копий в каждом уставе 151 строка, своего непустого
|
||||||
|
текста — 61 у `doc-wording` и 75 у `task-wording`, и это ровно то, чем проходы
|
||||||
|
отличаются: вход, соседи, машинная проверка, способ подстановки.
|
||||||
|
|
||||||
|
**АЕАКЕ. `вычитка-доклад` — контракт прохода, а не правило языка, и лежит он всё
|
||||||
|
равно в `shared/language.md`.** Заводить под пятнадцать строк отдельный файл
|
||||||
|
дороже, чем назвать раздел честно. Признак дома здесь не тема, а **число
|
||||||
|
потребителей больше одного при обязательной дословности**: разойдись два прохода
|
||||||
|
формой доклада, зовущий скилл разбирал бы два формата вместо одного.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
173. **Общность правила и общность исполнителя — разные оси.** Правило бывает
|
||||||
|
одно на всех и при этом требует по исполнителю на упаковку: правило
|
||||||
|
принадлежит предметной области, исполнитель — тому, кто его поставляет.
|
||||||
|
174. **Разрез по охвату обязан быть оплачен домом.** Разделение по глубине даёт
|
||||||
|
два разных текста и держится само; разделение по охвату даёт два
|
||||||
|
одинаковых, и без помеченной копии они разъезжаются — тем вернее, что
|
||||||
|
каждый по отдельности выглядит осмысленным.
|
||||||
|
|
||||||
|
|
||||||
|
## 51. av-dev-pm расколот: владение пошло по тому, что ставится порознь (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКЖ. Один плагин владел двумя вещами, и это мешало обеим.** `av-dev-pm` держал
|
||||||
|
документацию проекта и учёт работ. Пока владелец был один, сцепки выглядели
|
||||||
|
удобством: `docs.py` требовал `docs/tasks/` и звал внутрь `tasks.py`
|
||||||
|
подпроцессом, настройки задач лежали ключом в `docs/.pm.json`, язык проектных
|
||||||
|
текстов — внутри скилла `canon`. Каждая из трёх на расколе оказалась не
|
||||||
|
удобством, а утверждением, что половина принадлежит другой половине. Теперь
|
||||||
|
плагина два, `av-dev-docs` и `av-dev-tasks`, и каждый ставится сам по себе.
|
||||||
|
|
||||||
|
**АЕАКЗ. Самодостаточность держится копией, а не ссылкой.** Ссылка в дерево
|
||||||
|
соседнего плагина работает ровно до того момента, когда сосед не установлен, — а
|
||||||
|
это и есть тот случай, ради которого раскол делался. Поэтому все относительные
|
||||||
|
ссылки, пересекшие границу, сняты: вместо них имя скилла через пространство имён
|
||||||
|
и оговорка, что вызов может не разрешиться. То, что нужно обоим **дословно**,
|
||||||
|
стало общим домом в `shared/`: язык проектных текстов и словарь «Сопровождение и
|
||||||
|
эксплуатация». Второй выбран не по теме, а по числу владельцев — его делят
|
||||||
|
роадмап, `architecture.md` и тема ревью `operations`, то есть три плагина, и ни
|
||||||
|
один им не владеет. Три перечня «чем держат проект» уже разъезжались молча.
|
||||||
|
|
||||||
|
**АЕАКИ. OpenSpec отдан тому, кто им работает, а не тому, кто о нём написал.**
|
||||||
|
Версия 7 канона объявила `openspec/` своим слотом, и разрез вышел не по владению:
|
||||||
|
без каталога не запускается конвейер, а не канон. Заведение и форма файла уехали
|
||||||
|
в скилл `av-dev-pipeline:openspec`, отсутствие каталога стало для `docs.py`
|
||||||
|
неприменимостью вместо отказа. **Остаток назван, а не замолчан:** проверка формы и
|
||||||
|
сторож версии пока остались в скрипте канона, потому что своего скрипта у
|
||||||
|
конвейера нет ни одного, — то есть у файла сейчас два плагина, один заводит,
|
||||||
|
другой проверяет. Это записано и в журнале версий как временное состояние.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
175. **Сцепка внутри одного владельца не видна, пока владелец один.** Она
|
||||||
|
выглядит удобством ровно до раскола и обнаруживается не рассуждением, а
|
||||||
|
попыткой поставить половину отдельно. Отсюда и порядок работ: сперва
|
||||||
|
разнести, потом чинить то, что перестало сходиться.
|
||||||
|
176. **Разрез владения идёт по тому, кто инструментом пользуется, а не по тому,
|
||||||
|
кто о нём написал.** Канон описывал OpenSpec подробнее всех и потому казался
|
||||||
|
его владельцем; работает по нему конвейер, и слот принадлежит конвейеру.
|
||||||
|
|
||||||
|
|
||||||
|
## 52. Валидатор поехал за файлом: у конвейера появился свой скрипт (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКК. Названный остаток закрыт, и закрыт он ценой первого скрипта в
|
||||||
|
конвейере.** Решение 51 отдало OpenSpec конвейеру и честно оставило хвост:
|
||||||
|
проверка формы `config.yaml` и сторож версии остались в `docs.py`, потому что
|
||||||
|
своего скрипта у пайплайна не было ни одного. Хвост оказался не косметическим —
|
||||||
|
это ровно то состояние, против которого написан весь канон: **у файла два
|
||||||
|
владельца, один заводит, другой проверяет**, и разойтись они могут молча. 252
|
||||||
|
строки переехали в `av-dev-pipeline/skills/openspec/scripts/openspec.py`; в
|
||||||
|
`docs.py` от темы не осталось ни константы.
|
||||||
|
|
||||||
|
**Переезд оплатился сразу, и не тем, чего ждали.** Прежняя проверка требовала,
|
||||||
|
чтобы `context` называл `docs/passport.md` и `CLAUDE.md`, **безусловно** — то есть
|
||||||
|
на проекте без канона документов требовала ссылку на несуществующий файл. Пока
|
||||||
|
проверка жила в скрипте канона, допущение «канон есть» было незаметным: скрипт
|
||||||
|
канона запускают там, где канон есть. В скрипте конвейера то же допущение стало
|
||||||
|
видно на первом же прогоне. Теперь адрес требуется только к документу, который в
|
||||||
|
проекте есть, а его отсутствие идёт строкой «не проверялось» с названной ценой.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
177. **Неявное допущение видно из другого дома, а не изнутри своего.** «Канон
|
||||||
|
есть» было верно всюду, где код лежал, и потому не читалось как допущение
|
||||||
|
вовсе. Переезд — самый дешёвый способ его обнаружить: не разбор, а смена
|
||||||
|
места, из которого на код смотрят.
|
||||||
|
|
||||||
|
|
||||||
|
## 53. `canon` и `docs` остаются двумя скиллами (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКЛ. Слияние отклонено, и довод у него не про объём.** Оба скилла лежат в
|
||||||
|
одном плагине, и слить их казалось естественным завершением раскола. Мешает
|
||||||
|
`description`: это не аннотация, а **триггер** — по нему загрузчик решает, звать
|
||||||
|
ли скилл вообще, и ровно ради его сохранности заведён `frontmatter.py`. Моменты
|
||||||
|
вызова у этих двух разные. `canon` срабатывает на «проверь документацию»,
|
||||||
|
«переведи на канон», «пришёл в старый проект»; `docs` — на «задача сделана,
|
||||||
|
обнови документацию», «заведи ADR», «запиши наблюдение». Одно описание покрывает
|
||||||
|
оба хуже, чем два покрывают каждое своё, и потеря здесь не в читаемости, а в том,
|
||||||
|
что скилл перестаёт находиться.
|
||||||
|
|
||||||
|
Второй довод — тот же разрез, что репозиторий подтверждал уже трижды:
|
||||||
|
**раскладка против содержимого**, «где лежит» против «что внутри». Он по глубине,
|
||||||
|
а такой разрез, в отличие от разреза по охвату (решение 50), даёт два разных
|
||||||
|
текста и держится сам, без помеченных копий.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
178. **Границу между скиллами держит не тема, а момент вызова.** Два текста об
|
||||||
|
одном предмете живут порознь законно, если зовут их в разные минуты; и
|
||||||
|
наоборот — один предмет, разрезанный так, что оба куска нужны одновременно,
|
||||||
|
разрезан неверно.
|
||||||
|
|
||||||
|
|
||||||
|
## 54. Стык плагинов: правило получило дом, адреса остались у владельцев (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКМ. Вопрос пришёл с другой стороны: ревью опирается на документы проекта,
|
||||||
|
но не должно жёстко предполагать, где файл лежит; напрашивалось оглавление
|
||||||
|
адресов и сводка возможностей скиллов в `CLAUDE.md` проекта.** Отклонено и то и
|
||||||
|
другое, но не потому, что проблемы нет.
|
||||||
|
|
||||||
|
**Оглавление адресов — второй дом раскладки.** Канон жёсток намеренно: пути
|
||||||
|
фиксированы, проект подгоняется под них, и цена этого записана в самом каноне.
|
||||||
|
Указатель в `CLAUDE.md` отменяет ровно эту цену — раскладка получает второе
|
||||||
|
описание, и разойдутся они молча. Здесь молчание особенно дорогое: прогон ревью
|
||||||
|
умеет **честно деградировать**, и протухший адрес попадает прямо в эту машинерию —
|
||||||
|
файл не открылся, в границах покрытия появляется строка «документа в проекте
|
||||||
|
нет», и отчёт выглядит добросовестным. Прямой путь в той же ситуации ломается
|
||||||
|
громче.
|
||||||
|
|
||||||
|
**Сводка возможностей — второй дом описаний.** `description` во фронтматтере это
|
||||||
|
триггер, по нему скилл и выбирается; переписанная руками сводка тех же описаний
|
||||||
|
не сверяется ничем.
|
||||||
|
|
||||||
|
**Настоящий пробел был в другом, и он измерен.** Правило обращения к соседнему
|
||||||
|
плагину стояло в пяти местах в пяти редакциях:
|
||||||
|
|
||||||
|
| Где стояло | Довод | Ветка «не разрешился» |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `task-pipeline` | устаревшая проектная копия | нет |
|
||||||
|
| `task-batch` | то же | нет |
|
||||||
|
| `review-pipeline` | вшито в пункт про удаление проектных копий | нет |
|
||||||
|
| `openspec` | путём в чужое дерево — никогда | есть |
|
||||||
|
| `canon` | — | есть |
|
||||||
|
|
||||||
|
Два разных довода, и ни в одном месте не было обоих; три места из пяти молчали о
|
||||||
|
том, что делать при неразрешившемся вызове, — то есть о единственном, ради чего
|
||||||
|
правило написано. Плюс невысказанный инвариант: `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин, употреблён двадцать раз и нигде не оговорён — а именно он
|
||||||
|
соблазняет дописать `/../av-dev-docs/`.
|
||||||
|
|
||||||
|
**Сделано:** дом `shared/plugin-boundary.md`, блок `граница-плагинов`, семь
|
||||||
|
помеченных копий — четыре скилла конвейера и три скилла канона. В дом вошли
|
||||||
|
полное имя, запрет пути в чужое дерево, ветка «не разрешился» с обязанностью
|
||||||
|
доклада и признак присутствия по заведённому соседом файлу.
|
||||||
|
|
||||||
|
**Разрез, по которому дом наполнялся: правило общее, последствие местное.** «Нет
|
||||||
|
`av-dev-tasks` — учёт остаётся владельцу» знает только конвейер; «нет конвейера —
|
||||||
|
`docs.py` о каталоге `openspec/` молчит» знает только канон. Держи дом
|
||||||
|
последствия — он знал бы наперечёт всех своих потребителей и стал бы вторым
|
||||||
|
каноном.
|
||||||
|
|
||||||
|
**Адреса при этом наружу не поехали.** У них владелец есть: раскладку `docs/`
|
||||||
|
держит канон, каталог задач — плагин задач. `shared/` заводится **только для
|
||||||
|
фактов без владельца**; чужое с владельцем остаётся дома, а сходимость упоминаний
|
||||||
|
в чужих деревьях проверяет машина — `scripts/addresses.py`, тем же заходом.
|
||||||
|
|
||||||
|
**Что выяснилось при написании чекера: судить незнакомое нельзя.** Первый прогон
|
||||||
|
дал шесть находок, и три из них были не дрейфом, а свойством канона: `docs/**` —
|
||||||
|
шаблон, а `docs/accessibility.md` в двух местах — пример **своей темы проекта**,
|
||||||
|
которую канон разрешает заводить произвольно. Список тем открытый, значит
|
||||||
|
незнакомое имя опровергнуть нечем, и проверка «есть ли такой документ у
|
||||||
|
владельца» ловила бы законное. Переименование при этом ловится точно и по другому
|
||||||
|
основанию: канон, убирая слот, кладёт его в карту переездов `RETIRED` — она и
|
||||||
|
есть перечень запрещённого. Рядом одна догадка: имя, почти совпавшее с
|
||||||
|
каноническим, читается как опечатка. Порог замерен по репозиторию — законные
|
||||||
|
имена дают до 0.64, опечатки от 0.91, и между ними пусто.
|
||||||
|
|
||||||
|
Четвёртая находка оказалась настоящей: `REMAINING.md` иллюстрировал смысловой
|
||||||
|
дубль адресом `docs/specs/recognition.md` — слотом, упразднённым в версии 1
|
||||||
|
канона, то есть при десяти нынешних.
|
||||||
|
Пример, который сам протух, — ровно то, ради чего чекер и писался.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
179. **Механизм честной деградации превращает протухший адрес в правдоподобный
|
||||||
|
доклад.** Там, где отсутствие источника — законный исход с названной ценой,
|
||||||
|
ошибка адреса неотличима от этого исхода. Значит адрес в таком месте обязан
|
||||||
|
сверяться машиной, а не аккуратностью: единственная альтернатива —
|
||||||
|
ломаться громко, а именно её деградация и убирает.
|
||||||
|
180. **`shared/` — для фактов без владельца, и только.** У адресов владелец есть,
|
||||||
|
и вынести их наружу значило бы отобрать у него его же предмет. Признак
|
||||||
|
верного дома не «нужно нескольким», а «никому из них не принадлежит».
|
||||||
|
181. **Общее правило и его последствия живут порознь.** Правило можно вынести в
|
||||||
|
дом, последствие — нет: оно знает про место, а место про правило знать не
|
||||||
|
обязано. Дом, вобравший последствия, становится реестром потребителей и
|
||||||
|
устаревает быстрее их всех.
|
||||||
|
182. **Проверять надо запрещённое, а не незнакомое, когда словарь открыт.**
|
||||||
|
Открытый список делает «нет такого имени» неопровержимым, и проверка на
|
||||||
|
принадлежность перечню начинает ловить законное. Ловится ровно то, что
|
||||||
|
владелец объявил упразднённым: карта переездов — не побочный артефакт
|
||||||
|
миграции, а перечень запрещённого, и стоит она ровно там, где нужна.
|
||||||
|
183. **Замер порога записывается рядом с порогом.** Число, выбранное на глаз,
|
||||||
|
через месяц неотличимо от подогнанного под один случай. Обе стороны разрыва
|
||||||
|
названы (0.64 и 0.91) — и видно не только, что порог верен, но и насколько
|
||||||
|
он не на грани.
|
||||||
|
|
||||||
|
## 55. `task-pipeline` стал `resolve`: два плановых стопа вместо полной автономии (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКН. Автоматическое решение задач агентом признано утопией — «работает, но
|
||||||
|
работает плохо», — и хуже того, автор перестал ориентироваться в собственном
|
||||||
|
процессе.** Отсюда разворот: задачи решаются по одной, а в цикл возвращается
|
||||||
|
человек. `task-batch` удалён целиком; `task-pipeline` переписан в `resolve`.
|
||||||
|
|
||||||
|
**Прежняя доктрина звучала «умолчание — делать, а не спрашивать», и она не
|
||||||
|
отменена, а ограничена.** Полностью автономный прогон плох не тем, что ошибается,
|
||||||
|
а тем, что ошибку видно на готовом коде: развилка, стоившая бы абзаца до
|
||||||
|
`propose`, стоит переписывания после `apply`. Постоянное же согласование
|
||||||
|
возвращает ту цену, ради ухода от которой пайплайн и писался. Разрез поэтому по
|
||||||
|
**месту**, а не по важности решения: развилка, найденная до ближайшего чекпоинта,
|
||||||
|
копится в него; найденная после последнего — по-прежнему уходит вопросом в запись,
|
||||||
|
и задача доводится в объявленных границах.
|
||||||
|
|
||||||
|
**Чекпоинтов два, и второй обязателен всегда.**
|
||||||
|
|
||||||
|
- **«варианты»** — у исследовательской задачи, до первого требования. Признак
|
||||||
|
ветки не объём работы, а **отсутствие одного очевидного способа решения**:
|
||||||
|
обсуждать варианты после `propose` поздно, предложение уже воплотило один из
|
||||||
|
них, и разговор пойдёт не о выборе, а о переделке. Форма ограничена сверху —
|
||||||
|
2–4 варианта: больше четырёх человек не сравнивает, а признаёт неспособность
|
||||||
|
сравнить и просит рекомендацию.
|
||||||
|
- **«объяснение»** — у всякой задачи, **после** ревью дизайна. Порядок обоснован:
|
||||||
|
человек читает то, что уже просеяла машина, и не тратит внимание на выловимое
|
||||||
|
`review-specs`. Внимание здесь самый дорогой ресурс процесса.
|
||||||
|
|
||||||
|
**Объяснение не стало новым артефактом, и это главная правка первоначального
|
||||||
|
замысла.** Задумывалось отдельным разделом в `design.md`; при разборе оказалось,
|
||||||
|
что оно там было бы **третьим домом** одного и того же: в `proposal.md` уже есть
|
||||||
|
`## Why` («в чём проблема»), в `design.md` — рассмотренные варианты. Поэтому
|
||||||
|
объяснение **собирается из двух существующих артефактов**, а требование к их
|
||||||
|
форме уехало в `openspec/config.yaml` — `rules.proposal` и `rules.design`. Это
|
||||||
|
единственное место, применяющееся **в момент написания**, а не после.
|
||||||
|
Побочная выгода: `design.md` с названными причинами отказа — половина будущего
|
||||||
|
ADR, а промоут ADR читает именно архивный `design.md`.
|
||||||
|
|
||||||
|
**Закрыт вопрос, висевший в плане открытым: что делает автоматический участок,
|
||||||
|
когда ревью кода спорит с одобренным дизайном.** Признак проверяемый —
|
||||||
|
**меняются ли дельта-спеки**. Не меняются: находка внутри дизайна, дожимается
|
||||||
|
сама. Меняются: решение стало другим, а одобрено было прежнее — разметка
|
||||||
|
пересчитывается (правило уже было) и **чекпоинт повторяется**. Чекпоинт, который
|
||||||
|
можно обойти находкой ревью, не значит ничего, и хуже того — человек уверен, что
|
||||||
|
одобрил именно то, что уехало в коммит.
|
||||||
|
|
||||||
|
**Удаление `task-batch` обошлось дороже своего каталога.** На нём держались:
|
||||||
|
третий режим `review-specs` (стык после слияния) вместе с исключением «живого
|
||||||
|
change нет — берём источником актуальные спеки»; единственное исключение из
|
||||||
|
правила `review-triage` «плана нет — не запускаюсь»; и обоснование имени основной
|
||||||
|
ветки в каноне — «в неё вливает батч». Первые два — послабления, существовавшие
|
||||||
|
только ради батча, и с ним они исчезли, сделав оба правила строже.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
184. **Автономность ограничивается местом, а не важностью решения.** «Спрашивать
|
||||||
|
о важном» неисполнимо: важность оценивает тот же, кто хочет закончить.
|
||||||
|
«Копить до ближайшего планового стопа» проверяемо и не требует суждения.
|
||||||
|
185. **Чекпоинт ставится после машинной проверки, а не до неё.** Внимание
|
||||||
|
человека тратится только на то, чего машина не ловит; порядок наоборот
|
||||||
|
сжигает его на выловимом и обесценивает саму остановку.
|
||||||
|
186. **Объяснение для человека не заводит своего артефакта.** Если оно
|
||||||
|
собирается из уже существующих, оно не может с ними разойтись; отдельный
|
||||||
|
текст «то же, но понятнее» — третий дом, и расходится он молча.
|
||||||
|
187. **Послабление, введённое ради одного потребителя, уходит вместе с ним.**
|
||||||
|
Исключение переживает своего заказчика и выглядит общим правилом; удаляя
|
||||||
|
потребителя, ищи его исключения — они и есть настоящий хвост.
|
||||||
|
|
||||||
|
## 56. `av-dev-pipeline` → `av-dev-code`, `review-pipeline` → `review` (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКО. Имя описывало устройство, а не предмет.** «Пайплайн» говорит, что внутри
|
||||||
|
конвейер, — а плагин занят кодом по задачам, и после появления чекпоинтов он уже
|
||||||
|
не конвейер в чистом виде: между остановками автоматика, на остановках разговор.
|
||||||
|
|
||||||
|
**Набор имён стал параллельным, и это довод сам по себе:** `docs` / `tasks` /
|
||||||
|
`code` / `git` — каждое называет **материал**, которым плагин занят. Прежнее имя
|
||||||
|
выбивалось: три существительных и одна метафора устройства. По той же причине
|
||||||
|
отвергнут `av-dev-solve` — глагол в ряду существительных, плюс заикание в главном
|
||||||
|
вызове (`solve:resolve`), — и `av-dev-work`: «работы» в этом репозитории уже
|
||||||
|
значат конкретное (цели и задачи роадмапа, секция «Сопровождение»), и имя начало
|
||||||
|
бы спорить со словарём.
|
||||||
|
|
||||||
|
Заодно `review-pipeline` стал `review`: слово «пайплайн» ушло из плагина целиком,
|
||||||
|
а не наполовину, и скиллы выровнялись — `resolve` / `review` / `openspec`.
|
||||||
|
|
||||||
|
**Журнал версий канона переписан вместе со всеми, и это не нарушение правила «не
|
||||||
|
переписываем задним числом».** Разрез проходит не по типу файла, а по типу
|
||||||
|
высказывания. Наблюдение и причина — неприкосновенны: их правка есть
|
||||||
|
фальсификация. **Предписание и адрес обязаны оставаться исполнимыми**: запись
|
||||||
|
версии 10 велит «проверить, что плагин `av-dev-pipeline` установлен», и проект,
|
||||||
|
дошедший до неё, выполнит невыполнимое. `DECISIONS.md` при этом не тронут — в нём
|
||||||
|
нет предписаний проекту, только записи о принятых решениях; там прежнее имя
|
||||||
|
верно, потому что описывает состояние на дату записи.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
188. **Имя плагина называет материал, а не устройство.** Устройство меняется —
|
||||||
|
конвейер обзавёлся остановками, — а материал остаётся. Имя по устройству
|
||||||
|
протухает первым и при этом выглядит осмысленным.
|
||||||
|
189. **Журнал не переписывается в наблюдениях и обязан оставаться исполнимым в
|
||||||
|
предписаниях.** Правило «не задним числом» защищает от подделки фактов, а не
|
||||||
|
от починки инструкций: инструкция, ссылающаяся на несуществующее, — не
|
||||||
|
свидетельство эпохи, а поломка с отложенным сроком.
|
||||||
|
|
||||||
|
## 57. Спринты отменены: приоритет стал порядком строк, `session` стал `groom` (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКП. Спринт отвечал на вопрос «что делать дальше» замороженным набором, а
|
||||||
|
между наборами на этот вопрос не отвечал никто.** Процесс идёт задача за задачей,
|
||||||
|
и набор перестал что-либо удерживать: он не синхронизировал (некого), не
|
||||||
|
ограничивал по времени (тайм-бокс не брали) и не защищал от врывания (врывалось
|
||||||
|
ровно два класса, оба назывались правилом). Осталась цена — обязанность собрать,
|
||||||
|
показать, заморозить и распустить.
|
||||||
|
|
||||||
|
**Приоритет вернулся, и вернулся туда, где ему место.** Прежнее правило «порядка
|
||||||
|
нет, есть цель» было обосновано **набором спринта**, и с ним потеряло опору.
|
||||||
|
Приоритет — свойство очереди, а не задачи, поэтому его дом **индекс**: то же
|
||||||
|
исключение из правила «файл — источник истины», что уже было у «в каком индексе
|
||||||
|
лежит запись». Числом в файле он быть не мог — два соседних файла смогли бы
|
||||||
|
утверждать одно место, а строка индекса противоречить обоим.
|
||||||
|
|
||||||
|
**Гейт готовности стоял на `sprint take` и чуть не исчез вместе с ним.** Это было
|
||||||
|
единственное место, где запись судили целиком: тип, цель у `feature`, пустой
|
||||||
|
раздел вопросов, схема типа. Без спринта момента не осталось бы вовсе, а узнают
|
||||||
|
о недописанной задаче на приёмке, когда сверять уже не с чем. Момент назвали
|
||||||
|
заново — команда `tasks.py ready <слаг>`, и зовёт её тот, кто берёт задачу.
|
||||||
|
Отказ там **код 1, а не 2**: запись не дописана — это рабочая ситуация, а не
|
||||||
|
ошибка употребления.
|
||||||
|
|
||||||
|
**`session` стал `groom`, и предмет сузился до двух вопросов** — что сейчас
|
||||||
|
самое важное и что перестало быть важным. Из четырёх шагов прежней сессии выжили
|
||||||
|
два (вопросы, переоценка порциями), один заменился (расстановка очереди вместо
|
||||||
|
набора спринта), два выпали:
|
||||||
|
|
||||||
|
- **приёмка закрытых задач** — грумингу не по предмету. Ритуала у неё больше нет,
|
||||||
|
остаётся `reopen` по требованию. Цена названа прямо: приёмка происходит только
|
||||||
|
тогда, когда что-то уже бросилось в глаза;
|
||||||
|
- **разбор процесса** — его якорем был прошедший спринт. Вместе с ним из скилла
|
||||||
|
ушёл прямой вызов агентов `doc-consistency` и `doc-code-drift`, и это **не
|
||||||
|
потеря, а починка**: агенты принадлежат `av-dev-docs`, и груминг звал их мимо
|
||||||
|
правила обращения к соседу, без ветки «плагина нет». Груминг теперь только
|
||||||
|
**называет повод** сверить канон, а когда их звать — решает их владелец.
|
||||||
|
|
||||||
|
**Побочно найдено:** `canon.md` — дом определения канона — объявлял себя версией
|
||||||
|
7, когда скрипт шёл на 11. Пять версий дом врал о себе, и не заметил никто:
|
||||||
|
машина сверяет версию проекта с константой скрипта, а прозу в заголовке не
|
||||||
|
читает.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
190. **Правило, обоснованное механикой, умирает вместе с ней — и надо проверять,
|
||||||
|
что вопрос умер тоже.** «Порядка нет» держалось на наборе спринта; набор
|
||||||
|
ушёл, а вопрос «что делать дальше» остался и повис без ответа. Снимая
|
||||||
|
механику, ищи не только то, что на ней стояло, но и то, на что она отвечала.
|
||||||
|
191. **Гейт живёт в моменте, а не в команде.** Проверка готовности была свойством
|
||||||
|
`sprint take` — и была бы потеряна как деталь удаляемой команды. Момент
|
||||||
|
«запись впервые судят целиком» существует независимо от того, чем он
|
||||||
|
назван, и переезжает вместе с процессом.
|
||||||
|
192. **Версия в прозе, которую не читает машина, протухает молча.** Дом канона
|
||||||
|
назвал себя версией 7 при текущей 11: сверка шла по константе скрипта, а
|
||||||
|
заголовок документа не сверял никто.
|
||||||
|
|
||||||
|
## 58. Судьи документов получили свой скилл — `healthcheck` (2026-08-09)
|
||||||
|
|
||||||
|
**АЕАКР. Момент вызова был свойством чужого ритуала и исчез вместе с ним.**
|
||||||
|
`doc-consistency` и `doc-code-drift` звались шагом сессии между спринтами. Сессия
|
||||||
|
стала грумингом, груминг судит задачи, а не документы, и звать чужих агентов он
|
||||||
|
не вправе — они живут в `av-dev-docs`. На живом проекте их не звал бы **никто**,
|
||||||
|
кроме разовых `adopt` и `upgrade`.
|
||||||
|
|
||||||
|
Чинить это возвратом вызова в груминг было нельзя: это ровно то нарушение
|
||||||
|
границы, которое там и обнаружилось (вызов агента чужого плагина по имени, без
|
||||||
|
ветки «плагина нет»). Момент нужно было назвать **у владельца** — и оказалось,
|
||||||
|
что владельца-то у них и нет: `canon` их звал, но владел раскладкой, а не
|
||||||
|
суждением.
|
||||||
|
|
||||||
|
**Скилл `av-dev-docs:healthcheck`.** Предмет — то, чего машина не видит:
|
||||||
|
разошлись ли документы между собой и с кодом. Разрез с `canon check` проверяемый:
|
||||||
|
**машина сверяет форму, healthcheck — утверждения.** «Раздел есть» проверит
|
||||||
|
скрипт; «написано, что зависимость одна, а в манифесте их три» — суждение.
|
||||||
|
|
||||||
|
**Почему скилл, а не просто описание агентов.** Триггер у агента и так есть — его
|
||||||
|
`description`. Но двоим нужна **оркестровка**: позвать обоих на весь канон разом,
|
||||||
|
передать `doc-code-drift` раздел запретов, разобрать урожай порциями, назвать
|
||||||
|
границы покрытия и то, кого именно позвал. Этого агент о себе не знает.
|
||||||
|
|
||||||
|
**`doc-wording` внутрь не взят, и это разрез, а не забывчивость.** Ему
|
||||||
|
оркестровка не нужна: он один и работает по названному списку документов. И ритм
|
||||||
|
другой — он нужен там, где текст только что писали, а не там, где он год лежал.
|
||||||
|
Скилл, собравший всех троих «потому что все про документы», склеил бы разные
|
||||||
|
вопросы под одним вызовом.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
193. **Момент вызова — такая же собственность, как сам инструмент.** Агент,
|
||||||
|
чей момент назначен чужим ритуалом, теряет его вместе с ритуалом и
|
||||||
|
замолкает беззвучно: он исправен, его просто никто не зовёт.
|
||||||
|
194. **Оркестровка — вот что отличает скилл от агента.** Одному исполнителю с
|
||||||
|
ясным входом скилл не нужен, его находит описание. Скилл заводят там, где
|
||||||
|
надо решить, кого звать, что передать, в каком объёме и что делать с
|
||||||
|
результатом.
|
||||||
|
|
||||||
|
## 59. Аудит четырьмя сабагентами: описания отстают от механики молча (2026-08-09)
|
||||||
|
|
||||||
|
Реорганизация была объявлена законченной «на бумаге», и я запустил по аудитору на
|
||||||
|
плагин — консистентность, самостоятельность, интегрируемость. Гейт при этом был
|
||||||
|
зелёным и остался честен: он проверяет ровно то, что умеет.
|
||||||
|
|
||||||
|
Нашлось около полусотни расхождений, и они **одного рода**. Каждый раз я правил
|
||||||
|
механику — вырезал спринт из скрипта, переименовал скиллы, перенёс судей — и
|
||||||
|
каждый раз не правил то, что механику **описывает вовне**: скелеты документов,
|
||||||
|
докстринги скрипта, уставы агентов, манифесты плагинов, README.
|
||||||
|
|
||||||
|
Самое дорогое: **скелет `CLAUDE.md` уносил слоты спринта в каждый новый проект**
|
||||||
|
через две недели после отмены спринтов. Скелет не описывает, а порождает: его
|
||||||
|
отставание не читается, оно исполняется.
|
||||||
|
|
||||||
|
**Механизм приоритета не запускался ни разу.** `--section` у `move` был
|
||||||
|
обязательным, а все три места, где груминг предписывает расстановку, дают команду
|
||||||
|
без него — usage error. Скилл написан, прогнан не был, и разницы между рабочим и
|
||||||
|
бумажным процессом не видно, пока его не запустят.
|
||||||
|
|
||||||
|
**Правило границы я же и нарушал.** Восемь дословных копий «путь в дерево чужого
|
||||||
|
плагина не пишется никогда» — и пять мест, где путь написан, одно из них строкой
|
||||||
|
выше собственного «пути туда конвейер не выносит».
|
||||||
|
|
||||||
|
Отдельно: **у описания плагина было два дома**, и три из четырёх разошлись. Класс
|
||||||
|
закрыт не дисциплиной, а машиной — `frontmatter.py` теперь сверяет `plugin.json` с
|
||||||
|
`marketplace.json`, а гейт разбужен на `*.json`.
|
||||||
|
|
||||||
|
Правки разобраны четырьмя пропусками по одному сабагенту на пропуск, с проверкой
|
||||||
|
результата каждого: скелеты и канон, исполнимость учёта задач, границы и стыки,
|
||||||
|
словарь и манифесты. Скриптовые правки проверены поведением на фикстурах, включая
|
||||||
|
настоящий git-репозиторий для `reopen`.
|
||||||
|
|
||||||
|
**Чего аудит не даёт.** Это было чтение. Ни один скилл по-прежнему не исполнялся
|
||||||
|
на живом проекте, и находки вроде «чекпоинт вырождается в ритуал» такой проверкой
|
||||||
|
не берутся по построению.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
195. **Механика проверяется прогоном, описание — только чтением.** Поэтому после
|
||||||
|
каждой правки механики отстают именно описания, и отстают молча. Меняя
|
||||||
|
механику, ищи её отражения поимённо: скелеты, докстринги, уставы агентов,
|
||||||
|
манифесты, README.
|
||||||
|
196. **Скелет дороже документа: он не описывает, а порождает.** Отставший
|
||||||
|
документ врёт одному читателю; отставший скелет уезжает в каждый новый
|
||||||
|
проект и становится там обязательным.
|
||||||
|
197. **Копия правила не заставляет его исполнять.** Правило исполняется там, где
|
||||||
|
его проверяет машина или чужой глаз; восемь копий на видном месте не
|
||||||
|
помешали автору нарушить его пятью строками.
|
||||||
|
198. **Бумажный процесс неотличим от рабочего, пока его не запустили.** Команда,
|
||||||
|
которую никто не набрал, может не существовать вовсе — и именно так и было.
|
||||||
|
199. **Два дома у факта расходятся не когда-нибудь, а сразу.** Из четырёх пар
|
||||||
|
описаний плагина совпала одна — та, которую с момента заведения не правили.
|
||||||
|
|||||||
@@ -9,26 +9,47 @@
|
|||||||
|
|
||||||
## Плагины
|
## Плагины
|
||||||
|
|
||||||
- **av-dev-pm** — управление продуктом. Владеет всем `docs/`.
|
- **av-dev-docs** — документация проекта. Владеет `docs/` и `CLAUDE.md`.
|
||||||
- `init` — новый проект: интервью по свободному описанию замысла → первичная
|
- `init` — новый проект: интервью по свободному описанию замысла → первичная
|
||||||
документация;
|
документация;
|
||||||
- `canon` — привести проект к канону документов: `check` / `adopt` /
|
- `canon` — привести проект к канону документов: `check` / `adopt` /
|
||||||
`upgrade`, плюс скрипт `docs.py`. Там же живёт язык проектных текстов —
|
`upgrade`, плюс скрипт `docs.py`. Там же лежит копия языка проектных
|
||||||
информационный стиль, англицизмы, жаргон. Смысловую часть, которой скрипт
|
текстов — информационный стиль, англицизмы, жаргон; дом у него общий,
|
||||||
не видит, судят два агента: `doc-consistency` (документы между собой и с
|
`shared/language.md`;
|
||||||
openspec) и `doc-code-drift` (документы против кода);
|
- `healthcheck` — здоровье документации **судом, а не машиной**: не разошлись
|
||||||
|
ли документы между собой и с кодом. Зовёт двух агентов на весь канон разом —
|
||||||
|
`doc-consistency` (документы между собой и с openspec) и `doc-code-drift`
|
||||||
|
(факты против кода) — и разбирает урожай порциями. Дорого, поэтому не на
|
||||||
|
каждой задаче. Язык документов вычитывает отдельный агент `doc-wording`, и
|
||||||
|
зовут его не отсюда, а те, кто только что писал текст: `docs`, `init` и
|
||||||
|
`canon`;
|
||||||
- `docs` — содержимое канона по ходу разработки: ADR из архивного
|
- `docs` — содержимое канона по ходу разработки: ADR из архивного
|
||||||
`design.md`, промоут конвенций, запись в разведку и журнал ревью, чистка
|
`design.md`, промоут конвенций, запись в разведку и журнал ревью, чистка
|
||||||
архитектуры;
|
архитектуры.
|
||||||
|
- **av-dev-tasks** — учёт работ. Владеет каталогом задач.
|
||||||
- `tasks` — задачи и цели каталогом markdown-файлов, у каждой записи тип
|
- `tasks` — задачи и цели каталогом markdown-файлов, у каждой записи тип
|
||||||
(`goal`, `feature`, `fix`, `chore`, `research`), и тип задаёт её схему;
|
(`goal`, `feature`, `fix`, `chore`, `research`), и тип задаёт её схему;
|
||||||
вычитывают их два отдельных прохода: `task-form` (форма записи) и
|
вычитывают их два отдельных прохода: `task-form` (форма записи) и
|
||||||
`doc-wording` (язык);
|
`task-wording` (язык записей);
|
||||||
- `session` — ритуал между спринтами и ведение спринта.
|
- `groom` — груминг беклога: что сейчас самое важное и что перестало быть
|
||||||
- **av-dev-pipeline** — исполнение. **Требует OpenSpec.**
|
важным. Ответ записывается **порядком строк** — приоритет это свойство
|
||||||
- `task-pipeline` — задача через полный цикл SDD, от постановки до коммита;
|
очереди, и живёт он в индексе. Интерактивный: разбирает вопросы пачкой,
|
||||||
- `task-batch` — несколько задач разом, каждая в своём worktree;
|
переоценивает порциями по 5–8, расставляет верх очереди с доводом на
|
||||||
- `review-pipeline` — конвейер ревью **по темам**: документ проекта либо
|
каждое движение.
|
||||||
|
- **av-dev-code** — код по задачам: решение одной задачи и его проверка.
|
||||||
|
Владеет `openspec/`. **Требует OpenSpec и сам его заводит.**
|
||||||
|
- `openspec` — завести, настроить и **проверить** `openspec/` в проекте:
|
||||||
|
`openspec init`, замена примера в `config.yaml` настройкой канонической
|
||||||
|
формы, скрипт `openspec.py` (форма файла + сверка слепка с живой версией
|
||||||
|
инструмента). Каталог принадлежит конвейеру, а не канону: без конвейера он
|
||||||
|
проекту не нужен, и `docs.py` о нём молчит;
|
||||||
|
- `resolve` — одна задача от постановки до закрытия. Обычная идёт циклом SDD
|
||||||
|
с **чекпоинтом после ревью дизайна**: объяснение человеческим языком, повод
|
||||||
|
скорректировать ход решения. Исследовательская начинается с `opsx:explore` и
|
||||||
|
**чекпоинта вариантов** — способы решить, цена каждого, рекомендация; выбор
|
||||||
|
оседает по адресу, который назвала сама задача. Между чекпоинтами — без
|
||||||
|
согласований;
|
||||||
|
- `review` — конвейер ревью **по темам**: документ проекта либо
|
||||||
заводит тему проверки, либо питает чужую тему источником, либо процессный и в
|
заводит тему проверки, либо питает чужую тему источником, либо процессный и в
|
||||||
ревью не читается вовсе. Разметка идёт **один раз на задачу**, сразу после
|
ревью не читается вовсе. Разметка идёт **один раз на задачу**, сразу после
|
||||||
`propose`: агент `review-scope` меряет изменение по двум осям — размер и
|
`propose`: агент `review-scope` меряет изменение по двум осям — размер и
|
||||||
@@ -42,23 +63,37 @@
|
|||||||
Соглашение об именах: имя **плагина** длинное с префиксом `av-dev-`, имена
|
Соглашение об именах: имя **плагина** длинное с префиксом `av-dev-`, имена
|
||||||
**скилов** внутри — короткие. Вызов выходит вида `/av-dev-<плагин>:<скилл>`.
|
**скилов** внутри — короткие. Вызов выходит вида `/av-dev-<плагин>:<скилл>`.
|
||||||
|
|
||||||
Кто кого зовёт (стрелка — вызов через пространство имён, не импорт):
|
Кто кого зовёт. Сплошная стрелка — вызов скилла через пространство имён, не
|
||||||
|
импорт; пунктир — совет позвать, а не вызов. Агенты-проходы в графе не показаны:
|
||||||
|
их зовут скиллы, названные выше.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TB
|
flowchart TB
|
||||||
subgraph pipe["av-dev-pipeline — исполнение, требует OpenSpec"]
|
subgraph pipe["av-dev-code — исполнение, требует OpenSpec"]
|
||||||
direction LR
|
direction LR
|
||||||
batch["task-batch"] --> tp["task-pipeline"]
|
tp["resolve<br/>2 чекпоинта человеку"] --> rp["review<br/>10 агентов-проходов"]
|
||||||
tp --> rp["review-pipeline<br/>10 агентов-проходов"]
|
osp["openspec<br/>заводит и проверяет openspec/"]
|
||||||
batch --> rp
|
|
||||||
end
|
end
|
||||||
subgraph pm["av-dev-pm — управление продуктом, владеет docs/"]
|
subgraph docsp["av-dev-docs — документация, владеет docs/"]
|
||||||
direction LR
|
direction LR
|
||||||
init["init"] --> tasks["tasks"]
|
init["init"]
|
||||||
canon["canon"] --> tasks
|
canon["canon"]
|
||||||
session["session"] --> tasks
|
|
||||||
docs["docs"]
|
docs["docs"]
|
||||||
|
hc["healthcheck"]
|
||||||
end
|
end
|
||||||
|
subgraph tasksp["av-dev-tasks — учёт работ"]
|
||||||
|
direction LR
|
||||||
|
groom["groom"] --> tasks["tasks"]
|
||||||
|
end
|
||||||
|
init --> tasks
|
||||||
|
init --> osp
|
||||||
|
canon --> tasks
|
||||||
|
canon --> osp
|
||||||
|
canon --> hc
|
||||||
|
hc --> tasks
|
||||||
|
docs --> rp
|
||||||
|
rp --> tasks
|
||||||
|
groom -.-> hc
|
||||||
opsx["opsx:* — внешний плагин:<br/>explore, propose, apply, archive"]
|
opsx["opsx:* — внешний плагин:<br/>explore, propose, apply, archive"]
|
||||||
git["av-dev-git: commit"]
|
git["av-dev-git: commit"]
|
||||||
|
|
||||||
@@ -68,9 +103,18 @@ flowchart TB
|
|||||||
tp --> tasks
|
tp --> tasks
|
||||||
```
|
```
|
||||||
|
|
||||||
Зависимость **односторонняя: `av-dev-pipeline` знает про `av-dev-pm`, обратно —
|
Зависимости **взаимные, но каждая мягкая**. `av-dev-code` зовёт обоих соседей;
|
||||||
нет.** Управление продуктом работает в проекте без конвейера; конвейер без
|
обратные вызовы тоже есть — `av-dev-docs:init` и `av-dev-docs:canon` заводят
|
||||||
канона деградирует поразрядно и говорит об этом строкой.
|
OpenSpec скиллом `av-dev-code:openspec`, `av-dev-docs:docs` берёт у
|
||||||
|
`av-dev-code:review` форму записи журнала дефектов и процедуру промоута,
|
||||||
|
`av-dev-docs:canon` и `av-dev-docs:healthcheck` зовут `av-dev-tasks:tasks`.
|
||||||
|
**Мягкая** значит, что у любого вызова есть ветка «не разрешился»: соседа в
|
||||||
|
проекте нет — вызывающий называет строкой, чего теперь не делает никто, и работу
|
||||||
|
не останавливает. Как именно зовут соседа и что делают, когда вызов не
|
||||||
|
разрешился, — `shared/plugin-boundary.md`: правило нужно большинству скиллов, и
|
||||||
|
ни один плагин им не владеет. То, что нужно нескольким дословно — граница
|
||||||
|
плагинов, язык проектных текстов, словарь сопровождения, — живёт домом в
|
||||||
|
`shared/` и уезжает в каждый плагин помеченной копией.
|
||||||
|
|
||||||
## Канон документов проекта
|
## Канон документов проекта
|
||||||
|
|
||||||
@@ -79,7 +123,7 @@ flowchart TB
|
|||||||
`docs/` (паспорт, архитектура, база, безопасность, конвенции, разведка, ADR,
|
`docs/` (паспорт, архитектура, база, безопасность, конвенции, разведка, ADR,
|
||||||
ревью, задачи) и `openspec/` — **раскладка целиком, роли документов и правило
|
ревью, задачи) и `openspec/` — **раскладка целиком, роли документов и правило
|
||||||
единственного дома живут одним домом**:
|
единственного дома живут одним домом**:
|
||||||
[canon.md](av-dev-pm/skills/canon/references/canon.md). Здесь она не
|
[canon.md](av-dev-docs/skills/canon/references/canon.md). Здесь она не
|
||||||
пересказывается: копия перечня путей уже расходилась с домом, и как раз в
|
пересказывается: копия перечня путей уже расходилась с домом, и как раз в
|
||||||
обязательных — в ней не хватало путей, чьё отсутствие `docs.py check` считает
|
обязательных — в ней не хватало путей, чьё отсутствие `docs.py check` считает
|
||||||
нарушением.
|
нарушением.
|
||||||
@@ -95,11 +139,11 @@ flowchart TB
|
|||||||
|
|
||||||
Отдельного файла-брифа при этом нет — проходы читают документы напрямую; карта
|
Отдельного файла-брифа при этом нет — проходы читают документы напрямую; карта
|
||||||
«тема → её дом → что оттуда берётся» —
|
«тема → её дом → что оттуда берётся» —
|
||||||
[project-facts.md](av-dev-pipeline/skills/review-pipeline/references/project-facts.md).
|
[project-facts.md](av-dev-code/skills/review/references/project-facts.md).
|
||||||
|
|
||||||
Прийти в старый проект и перевести его на канон — `/av-dev-pm:canon`. Канон
|
Прийти в старый проект и перевести его на канон — `/av-dev-docs:canon`. Канон
|
||||||
версионируется, и проекты повышаются по [журналу
|
версионируется, и проекты повышаются по [журналу
|
||||||
версий](av-dev-pm/skills/canon/references/changelog.md).
|
версий](av-dev-docs/skills/canon/references/changelog.md).
|
||||||
|
|
||||||
## Подключение
|
## Подключение
|
||||||
|
|
||||||
@@ -114,8 +158,9 @@ cd /path/to/project
|
|||||||
claude plugin marketplace add https://git.vakhrushev.me/av/dev-skills.git --scope project
|
claude plugin marketplace add https://git.vakhrushev.me/av/dev-skills.git --scope project
|
||||||
|
|
||||||
# плагины: scope обязателен, умолчание у команды — user, а нам нужен project
|
# плагины: scope обязателен, умолчание у команды — user, а нам нужен project
|
||||||
claude plugin install av-dev-pm@av-dev-skills --scope project
|
claude plugin install av-dev-docs@av-dev-skills --scope project
|
||||||
claude plugin install av-dev-pipeline@av-dev-skills --scope project
|
claude plugin install av-dev-tasks@av-dev-skills --scope project
|
||||||
|
claude plugin install av-dev-code@av-dev-skills --scope project
|
||||||
claude plugin install av-dev-git@av-dev-skills --scope project
|
claude plugin install av-dev-git@av-dev-skills --scope project
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -131,15 +176,16 @@ claude plugin install av-dev-git@av-dev-skills --scope project
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enabledPlugins": {
|
"enabledPlugins": {
|
||||||
"av-dev-pm@av-dev-skills": true,
|
"av-dev-docs@av-dev-skills": true,
|
||||||
"av-dev-pipeline@av-dev-skills": true,
|
"av-dev-tasks@av-dev-skills": true,
|
||||||
|
"av-dev-code@av-dev-skills": true,
|
||||||
"av-dev-git@av-dev-skills": true
|
"av-dev-git@av-dev-skills": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**При установке в проект, где лежали проектные копии** скиллов и агентов
|
**При установке в проект, где лежали проектные копии** скиллов и агентов
|
||||||
(`.claude/skills/` — голые `task-pipeline`, `review-pipeline`, `task-batch`
|
(`.claude/skills/` — голые `task-pipeline`, `review-pipeline`
|
||||||
и с префиксом проекта `<проект>-task-pipeline`,
|
и с префиксом проекта `<проект>-task-pipeline`,
|
||||||
`.claude/agents/<проект>-review-*.md`) — снеси их. Две копии одного скилла
|
`.claude/agents/<проект>-review-*.md`) — снеси их. Две копии одного скилла
|
||||||
расходятся, и побеждает та, что короче названа.
|
расходятся, и побеждает та, что короче названа.
|
||||||
@@ -161,8 +207,9 @@ claude plugin marketplace update av-dev-skills
|
|||||||
|
|
||||||
# 2. снимки плагинов — из каталога проекта, где они установлены
|
# 2. снимки плагинов — из каталога проекта, где они установлены
|
||||||
cd /path/to/project
|
cd /path/to/project
|
||||||
claude plugin update av-dev-pm@av-dev-skills --scope project
|
claude plugin update av-dev-docs@av-dev-skills --scope project
|
||||||
claude plugin update av-dev-pipeline@av-dev-skills --scope project
|
claude plugin update av-dev-tasks@av-dev-skills --scope project
|
||||||
|
claude plugin update av-dev-code@av-dev-skills --scope project
|
||||||
claude plugin update av-dev-git@av-dev-skills --scope project
|
claude plugin update av-dev-git@av-dev-skills --scope project
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -234,9 +281,10 @@ claude plugin uninstall <плагин>@av-dev-skills --scope project
|
|||||||
<plugin>/.claude-plugin/plugin.json манифест плагина
|
<plugin>/.claude-plugin/plugin.json манифест плагина
|
||||||
<plugin>/skills/<skill>/SKILL.md скилы (авто-обнаружение)
|
<plugin>/skills/<skill>/SKILL.md скилы (авто-обнаружение)
|
||||||
<plugin>/skills/<skill>/references/ что читается по ссылке из скилла
|
<plugin>/skills/<skill>/references/ что читается по ссылке из скилла
|
||||||
<plugin>/skills/<skill>/scripts/ tasks.py, docs.py
|
<plugin>/skills/<skill>/scripts/ tasks.py, docs.py, openspec.py
|
||||||
<plugin>/agents/ charter'ы сабагентов
|
<plugin>/agents/ charter'ы сабагентов
|
||||||
scripts/ проверки репозитория: копии, диаграммы, фронтматтеры
|
shared/ дома правил, общих для нескольких плагинов
|
||||||
|
scripts/ проверки репозитория и пересборка копий
|
||||||
pyproject.toml линтеры скриптов, только для этого репозитория
|
pyproject.toml линтеры скриптов, только для этого репозитория
|
||||||
lefthook.yml гейт коммита: проверки документов
|
lefthook.yml гейт коммита: проверки документов
|
||||||
```
|
```
|
||||||
@@ -259,17 +307,17 @@ uv run pyrefly check # типы
|
|||||||
линтеров, и любой сторонний импорт у него не разрешается. Список запретов —
|
линтеров, и любой сторонний импорт у него не разрешается. Список запретов —
|
||||||
не перечень мира, настоящий страж второй.
|
не перечень мира, настоящий страж второй.
|
||||||
|
|
||||||
## Проверка фронтматтеров
|
## Проверка фронтматтеров и описаний плагинов
|
||||||
|
|
||||||
Фронтматтер читает не человек, а загрузчик: по `name` он разрешает вызов, по
|
Фронтматтер читает не человек, а загрузчик: по `name` он разрешает вызов, по
|
||||||
`description` решает, звать ли скилл вообще. **Ошибка здесь не выглядит
|
`description` решает, звать ли скилл вообще. **Ошибка здесь не выглядит
|
||||||
ошибкой** — тем же способом, что и в диаграммах.
|
ошибкой** — тем же способом, что и в диаграммах.
|
||||||
|
|
||||||
```
|
```
|
||||||
uv run python scripts/frontmatter.py # 0 в порядке, 1 расхождение, 3 не тот каталог
|
python3 scripts/frontmatter.py # 0 в порядке, 1 расхождение, 3 не тот каталог
|
||||||
```
|
```
|
||||||
|
|
||||||
Ловится три класса:
|
Ловится четыре класса:
|
||||||
|
|
||||||
- **двоеточие с пробелом в описании без кавычек.** Для YAML `: ` внутри
|
- **двоеточие с пробелом в описании без кавычек.** Для YAML `: ` внутри
|
||||||
простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт,
|
простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт,
|
||||||
@@ -281,10 +329,16 @@ uv run python scripts/frontmatter.py # 0 в порядке, 1 расхожд
|
|||||||
а не «имя не то»;
|
а не «имя не то»;
|
||||||
- **цвет charter'а, не отвечающий его модели.** Цвет кодирует модель, а не роль
|
- **цвет charter'а, не отвечающий его модели.** Цвет кодирует модель, а не роль
|
||||||
прохода — раскладка живёт в
|
прохода — раскладка живёт в
|
||||||
[review-pipeline/SKILL.md](av-dev-pipeline/skills/review-pipeline/SKILL.md),
|
[review/SKILL.md](av-dev-code/skills/review/SKILL.md),
|
||||||
разделе «Модель по проходу», здесь только её механизация. Держаться вниманием
|
разделе «Модель по проходу», здесь только её механизация. Держаться вниманием
|
||||||
правило не может: цвет ставится один раз при заведении charter'а, а модель
|
правило не может: цвет ставится один раз при заведении charter'а, а модель
|
||||||
потом меняется калибровкой.
|
потом меняется калибровкой;
|
||||||
|
- **описание плагина, разошедшееся между манифестами.** У описания два дома:
|
||||||
|
`<плагин>/.claude-plugin/plugin.json` показывает его установленному плагину,
|
||||||
|
корневой `.claude-plugin/marketplace.json` — тому, кто выбирает, ставить ли.
|
||||||
|
Правят обычно один, и разойтись они успели уже трижды из четырёх. `copies.py`
|
||||||
|
этот класс не берёт: он смотрит markdown, а манифест — json. Отсюда и `*.json`
|
||||||
|
в глобе задачи гейта.
|
||||||
|
|
||||||
## Проверка копий правил
|
## Проверка копий правил
|
||||||
|
|
||||||
@@ -293,7 +347,7 @@ uv run python scripts/frontmatter.py # 0 в порядке, 1 расхожд
|
|||||||
говорить. Значит копия допустима, но **дословная и помеченная**:
|
говорить. Значит копия допустима, но **дословная и помеченная**:
|
||||||
|
|
||||||
```
|
```
|
||||||
uv run python scripts/copies.py # 0 сошлось, 1 расхождение, 2 разметка, 3 не тот каталог
|
python3 scripts/copies.py # 0 сошлось, 1 расхождение, 2 разметка, 3 не тот каталог
|
||||||
```
|
```
|
||||||
|
|
||||||
Разметка — HTML-комментарии, невидимые в отрендеренном markdown:
|
Разметка — HTML-комментарии, невидимые в отрендеренном markdown:
|
||||||
@@ -311,10 +365,84 @@ uv run python scripts/copies.py # 0 сошлось, 1 расхождение
|
|||||||
сверку не входят. Маркер, уехавший в проект вместе со скелетом, там полезен: он
|
сверку не входят. Маркер, уехавший в проект вместе со скелетом, там полезен: он
|
||||||
говорит, что у текста есть дом и правится он там.
|
говорит, что у текста есть дом и правится он там.
|
||||||
|
|
||||||
|
**Дом правила, общего для нескольких плагинов, лежит в `shared/` и ни одному из
|
||||||
|
них не принадлежит.** Так живёт язык проектных текстов: он одинаково нужен
|
||||||
|
документам канона и задачам, и хранить его внутри одного плагина значило бы
|
||||||
|
отдать общее правило во владение половине. Так же живёт граница плагинов —
|
||||||
|
правило обращения к соседу. Плагин везёт копию и потому остаётся
|
||||||
|
самодостаточным — `shared/` нужен этому репозиторию, а не установленному
|
||||||
|
плагину.
|
||||||
|
|
||||||
|
**Дом ставится в `shared/` только тогда, когда владельца нет.** У адресов
|
||||||
|
владелец есть: раскладку `docs/` держит канон, каталог задач — плагин задач, и
|
||||||
|
переносить их наружу значило бы отобрать у владельца его же предмет. Общее без
|
||||||
|
владельца едет копией из `shared/`; чужое с владельцем остаётся дома, а
|
||||||
|
потребитель на него ссылается.
|
||||||
|
|
||||||
Скрипт ловит четыре вещи: копия разошлась с домом (с диффом), копия указывает не
|
Скрипт ловит четыре вещи: копия разошлась с домом (с диффом), копия указывает не
|
||||||
на тот файл, дом остался без копий, разметка сломана. Чего он **не** ловит —
|
на тот файл, дом остался без копий, разметка сломана. Чего он **не** ловит —
|
||||||
копию, которую забыли пометить: помечать — по-прежнему решение человека.
|
копию, которую забыли пометить: помечать — по-прежнему решение человека.
|
||||||
|
|
||||||
|
### Пересборка — `scripts/resync.py`
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 scripts/resync.py # переписать тела всех разошедшихся копий из домов
|
||||||
|
# 0 готово, 2 разметка сломана, 3 не тот каталог
|
||||||
|
```
|
||||||
|
|
||||||
|
Правка дома касается стольких файлов, сколько у него копий, и последний из них
|
||||||
|
забывают — это и есть причина, по которой копии расходятся. Пересборка делает то
|
||||||
|
же машиной и потому дословна по построению.
|
||||||
|
|
||||||
|
**В гейт коммита скрипт не ставится, и это решение.** Автоматическая пересборка
|
||||||
|
протащила бы правку дома во все копии мимо глаз автора, а правка дома, чья копия
|
||||||
|
уезжает в репозиторий проекта, обязана ещё и попасть в журнал версий канона —
|
||||||
|
этого машина не напишет. Гейт поэтому только **называет** расхождение; согласие с
|
||||||
|
ним остаётся действием человека.
|
||||||
|
|
||||||
|
Разметку разбирает не он сам: `copies.py` импортируется целиком. Второй
|
||||||
|
разборщик той же разметки разошёлся бы с первым молча — ровно тот класс дефекта,
|
||||||
|
против которого механика копий и заведена.
|
||||||
|
|
||||||
|
**Ограда блока кода принадлежит месту, а не дому.** Одно и то же тело живёт в
|
||||||
|
доме внутри ```` ``` ````, а в скелете канона — внутри чужой, объемлющей ограды,
|
||||||
|
и своей там иметь не должно. Пересборка берёт тело дома без крайних оград и
|
||||||
|
надевает обратно ту, что была у копии; пустые строки по краям — так же.
|
||||||
|
|
||||||
|
## Проверка адресов документов
|
||||||
|
|
||||||
|
Адрес документа принадлежит одному плагину, а называют его все: `docs/*` стоит
|
||||||
|
примерно в сорока местах конвейера, `tasks/ROADMAP.md` — в четырёх местах канона.
|
||||||
|
Переименование в каноне до этих мест само не доходит.
|
||||||
|
|
||||||
|
```
|
||||||
|
python3 scripts/addresses.py # весь репозиторий
|
||||||
|
# 0 сошлось, 1 упразднённый адрес или опечатка, 3 перечень владельца недоступен
|
||||||
|
```
|
||||||
|
|
||||||
|
**Зачем машина, а не аккуратность.** Прогон ревью умеет честно деградировать:
|
||||||
|
дома темы нет — в границах покрытия появляется строка «документа в проекте нет»
|
||||||
|
с названной ценой. Протухший адрес попадает ровно в эту машинерию и выходит
|
||||||
|
**правдоподобным отчётом**, а не поломкой. Громкий признак ошибки деградацией
|
||||||
|
убран, и здесь он возвращается гейтом.
|
||||||
|
|
||||||
|
Перечень берётся из **константы владельца** — той, по которой он и так проверяет
|
||||||
|
раскладку (`docs.py`, `tasks.py`). Второй перечень прозой был бы вторым домом
|
||||||
|
ровно того сорта, против которого написан канон.
|
||||||
|
|
||||||
|
Судится **упразднённое, а не незнакомое**, и это следует из канона: список тем
|
||||||
|
открытый, всё, что проект кладёт в `docs/` сверх закрытых категорий, — законная
|
||||||
|
тема, и опровергнуть её нечем. Зато переименование ловится точно: канон, убирая
|
||||||
|
слот, кладёт его в карту переездов, и она здесь и есть перечень запрещённого.
|
||||||
|
Рядом единственная догадка — имя, **почти** совпавшее с каноническим: `securty`
|
||||||
|
это опечатка вероятнее, чем новая тема. Порог замерен по репозиторию: законные
|
||||||
|
имена дают до 0.64, опечатки — от 0.91.
|
||||||
|
|
||||||
|
Не проверяются журналы (они описывают прошлые состояния и задним числом не
|
||||||
|
переписываются), адреса `openspec/*` (раскладка чужого инструмента, владельца у
|
||||||
|
нас нет) и упоминания в комментариях скриптов — сверяется только markdown. Эти
|
||||||
|
границы скрипт печатает сам.
|
||||||
|
|
||||||
## Проверка диаграмм
|
## Проверка диаграмм
|
||||||
|
|
||||||
Диаграммы `mermaid` живут исходником в markdown — картинок в репозитории нет.
|
Диаграммы `mermaid` живут исходником в markdown — картинок в репозитории нет.
|
||||||
@@ -322,8 +450,8 @@ uv run python scripts/copies.py # 0 сошлось, 1 расхождение
|
|||||||
правдоподобно, диff показывает разумную строку, а рендер падает.
|
правдоподобно, диff показывает разумную строку, а рендер падает.
|
||||||
|
|
||||||
```
|
```
|
||||||
uv run python scripts/diagrams.py # весь репозиторий
|
python3 scripts/diagrams.py # весь репозиторий
|
||||||
uv run python scripts/diagrams.py A.md B.md # только названные файлы
|
python3 scripts/diagrams.py A.md B.md # только названные файлы
|
||||||
# 0 рендерятся, 1 нет, 3 нет mermaid-cli
|
# 0 рендерятся, 1 нет, 3 нет mermaid-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -344,7 +472,7 @@ uv run python scripts/diagrams.py A.md B.md # только названные
|
|||||||
|
|
||||||
## Гейт коммита
|
## Гейт коммита
|
||||||
|
|
||||||
Все пять проверок стоят в `pre-commit` через [lefthook](https://lefthook.dev) —
|
Все шесть проверок стоят в `pre-commit` через [lefthook](https://lefthook.dev) —
|
||||||
конфиг в [lefthook.yml](lefthook.yml), ставится один раз на клон:
|
конфиг в [lefthook.yml](lefthook.yml), ставится один раз на клон:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -354,8 +482,9 @@ lefthook run pre-commit # прогнать руками, не коммитя
|
|||||||
|
|
||||||
| Проверка | Когда идёт | Что смотрит | Сколько |
|
| Проверка | Когда идёт | Что смотрит | Сколько |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| фронтматтеры | правка `*.md` | весь репозиторий | миллисекунды |
|
| фронтматтеры | правка `*.md` или `*.json` | весь репозиторий | миллисекунды |
|
||||||
| копии правил | правка `*.md` | весь репозиторий | миллисекунды |
|
| копии правил | правка `*.md` | весь репозиторий | миллисекунды |
|
||||||
|
| адреса документов | **каждый коммит** | весь репозиторий | ~0.07 с |
|
||||||
| диаграммы | правка `*.md` | staged-файлы | ~1 с на файл |
|
| диаграммы | правка `*.md` | staged-файлы | ~1 с на файл |
|
||||||
| `ruff check --fix` | правка `*.py` | staged-файлы | доли секунды |
|
| `ruff check --fix` | правка `*.py` | staged-файлы | доли секунды |
|
||||||
| `pyrefly check` | правка `*.py` | staged-файлы | доли секунды |
|
| `pyrefly check` | правка `*.py` | staged-файлы | доли секунды |
|
||||||
@@ -364,15 +493,21 @@ Glob разводит две половины: коммит, трогающий
|
|||||||
диаграмм, а коммит в документы не гоняет линтеры.
|
диаграмм, а коммит в документы не гоняет линтеры.
|
||||||
|
|
||||||
**Судятся staged-файлы, а не рабочее дерево** — гейт обязан проверять то, что
|
**Судятся staged-файлы, а не рабочее дерево** — гейт обязан проверять то, что
|
||||||
уедет в историю, а не то, что случайно лежит рядом на диске. Исключений два, и
|
уедет в историю, а не то, что случайно лежит рядом на диске. Исключений три, и
|
||||||
оба про существо, а не про удобство: `copies.py` сверяет копию с домом, а дом
|
все про существо, а не про удобство: `copies.py` сверяет копию с домом, а
|
||||||
лежит в другом файле, которого в индексе может не быть (список staged дал бы
|
дом лежит в другом файле, которого в индексе может не быть (список staged дал бы
|
||||||
«копии дословны» ровно там, где правка дома их и разошлась), а `frontmatter.py`
|
«копии дословны» ровно там, где правка дома их и разошлась); `frontmatter.py`
|
||||||
обходит весь репозиторий за сотые доли секунды — экономить тут нечего.
|
обходит весь репозиторий за сотые доли секунды — экономить тут нечего;
|
||||||
|
`addresses.py` идёт **без glob вовсе**, потому что сводит две стороны: перечень
|
||||||
|
адресов лежит в `*.py` владельца, а упоминания — в `*.md` соседей, и коммит с
|
||||||
|
переименованием документа трогает только первую.
|
||||||
|
|
||||||
**`ruff` чинит безопасное сам, и починка доносится до этого же коммита**
|
**`ruff` чинит безопасное сам, и починка доносится до этого же коммита**
|
||||||
(`stage_fixed: true`). Иначе исправленный файл остался бы в рабочем дереве, а в
|
(`stage_fixed: true`). Иначе исправленный файл остался бы в рабочем дереве, а в
|
||||||
историю уехал бы невычищенный — худший из исходов: гейт зелёный, коммит грязный.
|
историю уехал бы невычищенный — худший из исходов: гейт зелёный, коммит грязный.
|
||||||
|
|
||||||
|
**`resync.py` в гейте нет намеренно** — он чинит, а не проверяет, и его правка
|
||||||
|
обязана быть прочитана глазами (см. выше).
|
||||||
|
|
||||||
**Обход разовый — `LEFTHOOK=0 git commit …`.** Он законен ровно для случая,
|
**Обход разовый — `LEFTHOOK=0 git commit …`.** Он законен ровно для случая,
|
||||||
когда найденное нечем чинить прямо сейчас; молча пропущенная проверка — нет.
|
когда найденное нечем чинить прямо сейчас; молча пропущенная проверка — нет.
|
||||||
|
|||||||
+35
-23
@@ -36,13 +36,14 @@ severity. Пробы готовы и синтетических не нужно
|
|||||||
но работает ли обязанность, не проверено. Оркестратор реагирует на severity,
|
но работает ли обязанность, не проверено. Оркестратор реагирует на severity,
|
||||||
поэтому цена — не «не найдём», а **«найдём и не починим»**.
|
поэтому цена — не «не найдём», а **«найдём и не починим»**.
|
||||||
|
|
||||||
Сама работа — [TODO.md](TODO.md), раздел 3; здесь только цена: замер стоит
|
Сама работа — [TODO.md](TODO.md), раздел «Калибровка»; здесь только цена: замер
|
||||||
перед переездом jellybit и блокирует его (решение 39), а ожидаемый исход уже
|
стоит перед переездом jellybit и блокирует его (решение 39), а ожидаемый исход
|
||||||
назван выше.
|
уже назван выше.
|
||||||
|
|
||||||
**«Главный» здесь про цену, а не про очередь.** Первой идёт адаптация healthlog
|
**«Главный» здесь про цену, а не про очередь.** Первой идёт адаптация healthlog
|
||||||
(TODO, раздел 2): без неё нет проекта под каноном, на котором работают остальные
|
(TODO, раздел «Живые проекты»): без неё нет проекта под каноном, на котором
|
||||||
скиллы. Калибровка блокирует один шаг — переезд jellybit, — а не всё подряд.
|
работают остальные скиллы. Калибровка блокирует один шаг — переезд jellybit, — а
|
||||||
|
не всё подряд.
|
||||||
|
|
||||||
## Что ещё не сделано
|
## Что ещё не сделано
|
||||||
|
|
||||||
@@ -51,12 +52,16 @@ severity. Пробы готовы и синтетических не нужно
|
|||||||
|
|
||||||
- **Ни один скилл не прогонялся на живом проекте.** `docs.py` прогнан на
|
- **Ни один скилл не прогонялся на живом проекте.** `docs.py` прогнан на
|
||||||
healthlog и jellybit в режиме `check` и находит осмысленный дрейф; `init`,
|
healthlog и jellybit в режиме `check` и находит осмысленный дрейф; `init`,
|
||||||
`canon adopt`, `canon upgrade` и скилл `docs` не исполнялись ни разу.
|
`canon adopt`, `canon upgrade`, скиллы `docs`, `openspec` и `resolve` не
|
||||||
|
исполнялись ни разу. `openspec.py`, раскол плагинов и оба чекпоинта `resolve`
|
||||||
|
проверены только на фикстурах и на установке каждого плагина в одиночку.
|
||||||
- **Проектные копии в healthlog и jellybit.** Два `.claude/skills/` и одиннадцать
|
- **Проектные копии в healthlog и jellybit.** Два `.claude/skills/` и одиннадцать
|
||||||
`.claude/agents/` старого поколения. У jellybit хуже: его скиллы названы
|
`.claude/agents/` старого поколения — их надо снести при установке.
|
||||||
`task-pipeline`, `review-pipeline`, `task-batch` — **ровно как в плагине**.
|
**Совпадение имён при этом больше не грозит:** скиллы jellybit названы
|
||||||
Claude Code не переопределяет их, а держит обе пары, так что короткое имя может
|
`task-pipeline`, `review-pipeline`, `task-batch`, а плагин теперь даёт
|
||||||
увести в устаревшую копию, и молча.
|
`resolve`, `review`, `openspec` — ни одно имя не пересекается. Риск снят
|
||||||
|
переименованием, а не устранён по существу: заведись у проекта свой `review`,
|
||||||
|
Claude Code держал бы обе пары, и короткое имя увело бы в копию молча.
|
||||||
|
|
||||||
## Открытые вопросы
|
## Открытые вопросы
|
||||||
|
|
||||||
@@ -76,10 +81,11 @@ check` сверяет версию, но не то, что миграционн
|
|||||||
только её последствия.
|
только её последствия.
|
||||||
|
|
||||||
**Не выродились ли «границы покрытия» в шаблон.** Строка «что смотрели и чего не
|
**Не выродились ли «границы покрытия» в шаблон.** Строка «что смотрели и чего не
|
||||||
смотрели» обязательна в докладе `check`, `adopt`, интейка, штурма, сессии,
|
смотрели» обязательна в докладе `check`, `adopt`, интейка, штурма и всех пяти
|
||||||
спринта и всех четырёх агентов — семь и больше раз за сессию, и проверить её
|
агентов канона и задач (`doc-consistency`, `doc-code-drift`, `doc-wording`,
|
||||||
исполнение некому: приёмщик и исполнитель одно лицо (`session/SKILL.md`,
|
`task-form`, `task-wording`) — девять мест, и проверить её исполнение некому:
|
||||||
«Стимулы»). Выродившаяся строка **хуже отсутствия**: доклад выглядит проверенным.
|
приёмщик и исполнитель одно лицо (`groom/SKILL.md`, «Стимулы»). Выродившаяся
|
||||||
|
строка **хуже отсутствия**: доклад выглядит проверенным.
|
||||||
|
|
||||||
Приём не правится: это гипотеза об износе, а не находка, и менять работающее по
|
Приём не правится: это гипотеза об износе, а не находка, и менять работающее по
|
||||||
догадке дороже. **Наблюдение к первой обкатке на живом проекте:** если в трёх
|
догадке дороже. **Наблюдение к первой обкатке на живом проекте:** если в трёх
|
||||||
@@ -88,10 +94,11 @@ check` сверяет версию, но не то, что миграционн
|
|||||||
|
|
||||||
**Форма ADR при пересмотре решения.** Парный статус («старая запись получает
|
**Форма ADR при пересмотре решения.** Парный статус («старая запись получает
|
||||||
`заменено на`») судит агент `doc-consistency` — правило 6 его устава. Охват был
|
`заменено на`») судит агент `doc-consistency` — правило 6 его устава. Охват был
|
||||||
открытым вопросом, пока агент зовётся пачкой, отобранной работой; переезд вызова
|
открытым вопросом, пока агент звался пачкой, отобранной работой; переезд вызова
|
||||||
на сессию с пачкой «весь канон» его снял. Остаётся зазор в спринт и отсутствие
|
в `av-dev-docs:healthcheck` с пачкой «весь канон» его снял. Остаётся зазор до
|
||||||
механической проверки — то есть пересмотр, сделанный сегодня, судится на
|
ближайшего прогона `healthcheck` и отсутствие механической проверки — то есть
|
||||||
ближайшей сессии, а не в момент правки.
|
пересмотр, сделанный сегодня, судится тогда, когда позовут сверку, а не в момент
|
||||||
|
правки.
|
||||||
|
|
||||||
## Известные пределы — приняты, чинить не планируется
|
## Известные пределы — приняты, чинить не планируется
|
||||||
|
|
||||||
@@ -111,14 +118,19 @@ check` сверяет версию, но не то, что миграционн
|
|||||||
словарь строится каждый раз заново из спек и архитектуры. Цена не измерена.
|
словарь строится каждый раз заново из спек и архитектуры. Цена не измерена.
|
||||||
|
|
||||||
**Смысловые дубли ловит только агент.** `docs.py` видит раскладку, но не то, что
|
**Смысловые дубли ловит только агент.** `docs.py` видит раскладку, но не то, что
|
||||||
`docs/specs/recognition.md` описывает то же, что capability `recognition`.
|
раздел `docs/architecture.md` описывает поведение, уже записанное capability
|
||||||
|
`recognition`.
|
||||||
Граница объявляется вслух в каждом отчёте — это единственная защита от
|
Граница объявляется вслух в каждом отчёте — это единственная защита от
|
||||||
«соблюдено» на проекте с тремя лишними файлами.
|
«соблюдено» на проекте с тремя лишними файлами.
|
||||||
|
|
||||||
**Приёмщик и исполнитель совпали.** Граница «пайплайн не закрывает задачу» снята
|
**Приёмщик и исполнитель совпали, и опор стало меньше.** Граница «пайплайн не
|
||||||
сознательно (решение P); три защиты из раздела «Стимулы» держатся теперь текстом,
|
закрывает задачу» снята сознательно (решение P); защиты держатся текстом, а не
|
||||||
а не механикой. Реальные опоры — сохранённый отчёт триажа, `SPRINT.md` под git и
|
механикой. Реальных опор было три, осталось две: сохранённый отчёт триажа и
|
||||||
`reopen`. Это записано в самом скилле, а не спрятано.
|
`reopen` (индексы под git показывают закрытие, потому что оно коммитится
|
||||||
|
отдельным коммитом учёта). Третья — приёмка шагом сессии — ушла вместе со
|
||||||
|
спринтами: у неё больше **нет момента**, и происходит она только тогда, когда
|
||||||
|
что-то бросилось в глаза на груминге. Это записано в самих скиллах, а не
|
||||||
|
спрятано.
|
||||||
|
|
||||||
**Копия правила в шаблонах проекта.** `adr/README.md` и `review.md` уезжают в
|
**Копия правила в шаблонах проекта.** `adr/README.md` и `review.md` уезжают в
|
||||||
репозиторий и обязаны там что-то говорить, поэтому правило канона в них
|
репозиторий и обязаны там что-то говорить, поэтому правило канона в них
|
||||||
|
|||||||
@@ -1,227 +1,118 @@
|
|||||||
# Работы по итогам разбора
|
# Что осталось сделать
|
||||||
|
|
||||||
Порядок и обоснование — [DECISIONS.md](DECISIONS.md), тема 8. Номера в скобках —
|
**Здесь только работы и их порядок.** Чего здесь нет намеренно:
|
||||||
следствия оттуда.
|
|
||||||
|
|
||||||
Замер (шаг 3) — **единственный шаг, который нельзя переставить**: он блокирует
|
- **риски, открытые вопросы и принятые пределы** — [REMAINING.md](REMAINING.md);
|
||||||
переезд jellybit. Всё остальное можно тасовать.
|
- **почему решено так** — [DECISIONS.md](DECISIONS.md), записи датированы;
|
||||||
|
- **шаги повышения проекта с версии канона на версию** — журнал версий
|
||||||
|
([changelog.md](av-dev-docs/skills/canon/references/changelog.md)). Пересказ их
|
||||||
|
сюда был бы вторым домом, и прежний план на этом уже разъезжался: он повторял
|
||||||
|
записи версий 3, 4 и 5 построчно, и половина повторов протухла молча.
|
||||||
|
|
||||||
## 0. Предусловие
|
Сделанное отсюда **удаляется, а не помечается галочкой**. След остаётся в
|
||||||
|
коммитах и в `DECISIONS.md`; список из двух сотен `[x]` перестают читать целиком,
|
||||||
|
и живые пункты в нём теряются — прежний план умер именно так.
|
||||||
|
|
||||||
- [x] `git push` — `092d07c..88c5d97`, 17 коммитов ушли на origin (37)
|
## Где мы сейчас
|
||||||
- [x] `claude plugin marketplace update av-dev-skills` — клон встал на `88c5d97`
|
|
||||||
и видит `av-dev-pm` и `av-dev-pipeline`
|
|
||||||
|
|
||||||
## 1. Репозиторий плагинов
|
Плагинов четыре, и каждый ставится отдельно: `av-dev-docs` (канон документов и
|
||||||
|
их содержимое), `av-dev-tasks` (задачи и цели), `av-dev-code` (код по задачам:
|
||||||
|
цикл SDD, конвейер ревью, OpenSpec), `av-dev-git`. Общее, что нужно нескольким
|
||||||
|
дословно, живёт домом в `shared/` и уезжает копиями.
|
||||||
|
|
||||||
### 1.1 Переименование
|
Канон документов — **версия 12**. Живые проекты стоят на 2–3 и на плагине
|
||||||
|
`av-dev-pm`, которого больше нет.
|
||||||
|
|
||||||
- [x] `av-dev-tasks` → `av-dev-pm`: каталог, `plugin.json`, `marketplace.json` (18)
|
Бумажная часть закрыта аудитом четырёх плагинов и четырьмя пропусками правок
|
||||||
- [x] пространство имён во всех текстах: `av-dev-tasks:session` →
|
(DECISIONS, запись 59). Всё, что ниже, проверяется **только на живом коде**.
|
||||||
`av-dev-pm:session`, включая ссылку из `task-pipeline` (18)
|
|
||||||
|
|
||||||
### 1.2 Канон — единственный дом определения
|
## 1. Живые проекты — вернуть в рабочее состояние
|
||||||
|
|
||||||
- [x] `av-dev-pm/skills/canon/references/canon.md` — раскладка, роли документов,
|
Блокирует всё остальное: под текущим каноном не стоит ни один проект, и ни один
|
||||||
правило единственного дома. Читают `init`, `canon`, `docs` (AA)
|
скилл, кроме `docs.py check`, не исполнялся на живом коде ни разу
|
||||||
- [x] `av-dev-pm/skills/canon/references/changelog.md` — журнал версий канона,
|
(см. REMAINING, «Что ещё не сделано»).
|
||||||
версия 1 (26)
|
|
||||||
|
|
||||||
### 1.3 Правки существующих скиллов
|
### healthlog — первым
|
||||||
|
|
||||||
- [x] `tasks`: убрать слот 6 «Команда учёта задач» (33)
|
- [ ] переустановить плагины: снять `av-dev-pm` и `av-dev-pipeline`, поставить
|
||||||
- [x] `tasks`: путь каталога жёсткий `docs/tasks`, убрать цепочку разрешения (F)
|
`av-dev-docs`, `av-dev-tasks`, `av-dev-code`, `av-dev-git`. Оба прежних
|
||||||
- [x] `tasks`: `.tasks.json` → `docs/.pm.json`, там же версия канона и путь
|
имени мертвы, и `plugin update` их не переименует — только снять и
|
||||||
миграций (23, 30)
|
поставить. `marketplace update`, затем `plugin update` — одного шага мало
|
||||||
- [x] `tasks`: убрать слоты 3 «куда переезжает суть» и 5 «оракулы» — отвечает
|
(README, «Обновление»)
|
||||||
канон и семантика гейта (тема 4)
|
|
||||||
- [x] `session`: убрать слот 7 и слот 4 «где живёт разбор процесса» (33, K)
|
|
||||||
- [x] `session`: переписать «Стимулы, которые процесс создаёт» — снятая граница
|
|
||||||
выбила опору у трёх защит (19)
|
|
||||||
|
|
||||||
### 1.4 Новые скиллы
|
|
||||||
|
|
||||||
- [x] `init` — интервью по брифу → канон нового проекта (R)
|
|
||||||
- [x] `canon` — `check` / `adopt` / `upgrade`; поглощает скилл `adopt` (R, 21, 24)
|
|
||||||
- [x] `docs` — содержимое канона: ADR из архивного `design.md`, промоут
|
|
||||||
конвенций, запись в `research/` и `review.md`, чистка `architecture.md` (X)
|
|
||||||
- [x] `docs.py` — раскладка, лишние файлы, битые ссылки, версия, плейсхолдеры,
|
|
||||||
маркеры долга; сверки миграции ↔ `database.md` и capability ↔
|
|
||||||
`architecture.md` (T, 31)
|
|
||||||
|
|
||||||
### 1.5 av-dev-pipeline
|
|
||||||
|
|
||||||
- [x] удалить скилл `project-brief` и `references/{project-brief,brief-template}.md` (13)
|
|
||||||
- [x] снять ветки деградации OpenSpec в трёх местах: `task-pipeline`,
|
|
||||||
`review-pipeline`, `task-batch` (1)
|
|
||||||
- [x] девять charter'ов: разделы брифа → пути канона; `ops`/`adversary`/`reimpl`
|
|
||||||
обязаны сшивать `research/` и `database.md` (14, 15)
|
|
||||||
- [x] `review-pipeline`: убрать бриф, поразрядная деградация по документам (16)
|
|
||||||
- [x] `task-pipeline` шаг 9 → построчный доклад по документам канона (28)
|
|
||||||
- [x] `task-pipeline`/`task-batch`: закрытие задачи вызовом скилла
|
|
||||||
`av-dev-pm:tasks`, слот убрать (32, 33)
|
|
||||||
- [x] `promote.md` шаг 3: перечень механизированного → `conventions/README.md` (29)
|
|
||||||
- [x] описание плагина: «требует OpenSpec» (2)
|
|
||||||
|
|
||||||
### 1.6 Прочее
|
|
||||||
|
|
||||||
- [x] `av-dev-backlog` — пометить устаревшим, переписать описание, чтобы не
|
|
||||||
ловило триггер (Q)
|
|
||||||
- [x] `README.md` маркетплейса — три плагина, канон, установка
|
|
||||||
- [x] `HISTORY.md` — сжать `AGENTIC-TASKS.md` до истории решений (CC)
|
|
||||||
- [x] `REMAINING.md` пересобрать: пункт 2 отменён, четыре вопроса закрыты,
|
|
||||||
калибровка стала обязательной (38)
|
|
||||||
|
|
||||||
### 1.7 Линтеры скриптов (тема 9)
|
|
||||||
|
|
||||||
- [x] `pyproject.toml`: ruff + pyrefly через `uv`, версии прибиты (DD, FF)
|
|
||||||
- [x] запрет внешних зависимостей двумя способами: `banned-api` + пустое
|
|
||||||
окружение pyrefly (EE)
|
|
||||||
- [x] `RUF001`–`RUF003` выключены, `av-dev-backlog` исключён (GG, HH)
|
|
||||||
- [x] починены 27 находок ruff и 14 pyrefly; `os` из `tasks.py` ушёл (40, 41)
|
|
||||||
- [x] раздел «Проверка скриптов» в `README.md`
|
|
||||||
|
|
||||||
### 1.8 Ревью двумя проходами (тема 10)
|
|
||||||
|
|
||||||
- [x] `reopen` берёт текст из `HEAD`, когда коммита удаления ещё нет (JJ)
|
|
||||||
- [x] шаг 11 коммитит учёт вторым коммитом; батч проверяет чистоту дерева (JJ)
|
|
||||||
- [x] фиктивный ключ `tasks.sections` убран из четырёх документов (KK)
|
|
||||||
- [x] `init` пишет конфиг в `docs/.pm.json`; `looks_like_tasks` его читает
|
|
||||||
- [x] урожай спринта заводится до `sprint close`, слаг — из его отчёта
|
|
||||||
- [x] ответ на вопрос опустошает раздел «Вопросы» — во всех трёх местах
|
|
||||||
- [x] путь отчёта триажа переживает архивацию (5 мест)
|
|
||||||
- [x] `review-specs` получил режим 3 — стык после слияния
|
|
||||||
- [x] остальные 12 находок: `sprint.md`, «9а», перечень проектных копий,
|
|
||||||
параллельность в батче, триаж в финальной сверке, триггеры профиля, 8–12
|
|
||||||
|
|
||||||
### 1.9 Ревью зависимостей между плагинами (тема 11)
|
|
||||||
|
|
||||||
- [x] опоры приёмки названы абстрактно, деградация без конвейера объявлена (MM)
|
|
||||||
- [x] ветка деградации шага 9 ходит в свой `project-facts.md` (NN)
|
|
||||||
- [x] `docs` даёт ветку «конвейера нет» для журнала и промоута
|
|
||||||
- [x] форма журнала дефектов сведена к дому, копия помечена в `changelog.md`
|
|
||||||
- [x] `specs` вернулся в читатели `docs/research/`; дом списка назначен
|
|
||||||
- [x] пайплайн не называет `items/` и `SPRINT.md` — их знает `av-dev-pm`
|
|
||||||
- [x] манифесты объявили `av-dev-pm` опциональным для конвейера (49)
|
|
||||||
|
|
||||||
### 1.10 Механическая проверка копий (тема 12)
|
|
||||||
|
|
||||||
- [x] `scripts/copies.py`: маркеры дома и копии, побайтовая сверка (OO)
|
|
||||||
- [x] строгий id, повторяемый в закрывающем маркере (PP)
|
|
||||||
- [x] помечены два контракта; «когда заводить ADR» сведён к дословному (50)
|
|
||||||
- [x] раздел «Проверка копий правил» в `README.md`, правило — в обоих домах
|
|
||||||
|
|
||||||
## 2. healthlog — первая боевая проверка
|
|
||||||
|
|
||||||
- [ ] `canon adopt`; `docs/backlog/` → `docs/tasks/`
|
|
||||||
- [ ] `architecture.md` 1662 строки → обзор, остаток маркерами (W)
|
|
||||||
- [ ] после выноса поведения — замерить остаток `architecture.md`; **решать
|
|
||||||
больше нечего**: канон 5 разрешил любой теме быть каталогом с `README.md`,
|
|
||||||
так что жмёт — заводи `docs/architecture/`, и это не смена версии
|
|
||||||
(тема 16, GGG, 65; закрыто темой 36)
|
|
||||||
- [ ] завести `security.md` с периметром первой строкой (J)
|
|
||||||
- [ ] `review-journal.md` → `review.md` + настройка конвейера (K, L)
|
|
||||||
- [ ] `conventions.md` → `conventions/`, `local-research.md` → `research/` (G)
|
|
||||||
- [ ] `plan.md` → `docs/tasks/ROADMAP.md` (E)
|
|
||||||
- [ ] завести `docs/adr/`
|
|
||||||
- [ ] `CLAUDE.md`: severity инвариантов, семантика гейта, убрать раздел
|
|
||||||
«Процесс» (M, N)
|
|
||||||
- [ ] `docs.py check` в `task gate` (V)
|
|
||||||
- [ ] почистить `openspec/config.yaml` (C)
|
|
||||||
- [ ] удалить проектные копии: `.claude/skills/healthlog-{task,review}-pipeline`
|
- [ ] удалить проектные копии: `.claude/skills/healthlog-{task,review}-pipeline`
|
||||||
и девять `.claude/agents/healthlog-review-*.md` — они прошлого поколения и
|
и девять `.claude/agents/healthlog-review-*.md`. Они прошлого поколения и
|
||||||
после переезда указывают на `docs/conventions.md`, `docs/local-research.md`,
|
после переезда указывают на документы, которых уже не будет
|
||||||
`docs/review-journal.md`, которых уже не будет
|
- [ ] `av-dev-docs:canon` в режиме `adopt` — он приведёт проект к канону 12
|
||||||
|
сразу, картой и с подтверждением. Файл-в-файл здесь не расписан: раскладку
|
||||||
|
знает скилл, и второй перечень разошёлся бы с ним
|
||||||
|
- [ ] каталог задач — в `tasks/` корня (канон 11), не в `docs/tasks/`, и без
|
||||||
|
`SPRINT.md` (канон 12). Скилл задач зовётся из `adopt` сам
|
||||||
|
- [ ] гейт проекта: три шага вместо одного — `docs.py check`, `tasks.py check
|
||||||
|
--dir tasks`, `openspec.py check`. **Второй и третий раньше не были
|
||||||
|
нужны:** согласованность задач тянул за собой `docs.py`, форму `config.yaml`
|
||||||
|
он же. Теперь оба молчат, и без своих шагов дрейф перестанет ловиться
|
||||||
|
- [ ] разобрать урожай `doc-consistency` и `doc-code-drift` порциями — правило
|
||||||
|
единственного дома на живом проекте не проверял никто
|
||||||
|
|
||||||
## 3. Калибровка — блокирует шаг 5
|
### jellybit — после калибровки
|
||||||
|
|
||||||
|
Порядок не произволен: замер (раздел 3) блокирует переезд jellybit, и только его.
|
||||||
|
|
||||||
|
- [ ] то же, что у healthlog: плагины, проектные копии, `adopt`, каталог задач,
|
||||||
|
гейт
|
||||||
|
- [ ] проектные копии здесь опаснее: скиллы названы `task-pipeline`,
|
||||||
|
`review-pipeline` — **ровно как в плагине**, и короткое имя
|
||||||
|
может увести в устаревшую копию молча (REMAINING)
|
||||||
|
|
||||||
|
## 2. Учёт работ без спринтов — что осталось
|
||||||
|
|
||||||
|
Сделано: спринт снят со скрипта и текстов, приоритет стал порядком строк в
|
||||||
|
беклоге, гейт готовности переехал в `tasks.py ready`, `session` стал скиллом
|
||||||
|
`groom`, запись 12 в журнал версий канона написана.
|
||||||
|
|
||||||
|
- [ ] прогнать груминг на живом беклоге — на фикстуре проверялись команды, а не
|
||||||
|
сам разбор. Наблюдение к первому прогону: **не выродился ли шаг 4 в
|
||||||
|
«оставить как есть»** — признак тот, что доклад не называет ни одного
|
||||||
|
движения с доводом
|
||||||
|
|
||||||
|
## 3. Калибровка — блокирует переезд jellybit
|
||||||
|
|
||||||
- [ ] замер на четырёх находках healthlog: скелет из `null`, откат бинаря,
|
- [ ] замер на четырёх находках healthlog: скелет из `null`, откат бинаря,
|
||||||
канонизация в транзакции, `-1 >= -1` (1 из REMAINING, 14)
|
канонизация в транзакции, `-1 >= -1`. Цена и ожидаемый исход — REMAINING,
|
||||||
|
«Главный незакрытый риск»
|
||||||
|
|
||||||
## 4. Обкатка
|
## 4. Конвейер: что осталось после `resolve`
|
||||||
|
|
||||||
- [ ] один-два спринта healthlog на новом процессе
|
Сам скилл написан (`av-dev-code:resolve`, два чекпоинта, ветка разведки),
|
||||||
|
`task-batch` удалён. Осталось то, что на бумаге не проверяется:
|
||||||
|
|
||||||
## 5. jellybit
|
- [ ] перемерить скилл `review` тем же вопросом, что и проект целиком:
|
||||||
|
сколько из его стадий реально смотрятся глазами. Тысяча строк, и весь
|
||||||
|
автоматический участок между чекпоинтами держится на них
|
||||||
|
- [ ] чекпоинт «объяснение» собирается из `proposal.md` и `design.md`, а
|
||||||
|
требования к их форме уехали в `openspec/config.yaml` (`rules.proposal`,
|
||||||
|
`rules.design`). **На живом проекте это ни разу не работало:** неизвестно,
|
||||||
|
хватает ли двух артефактов, чтобы объяснение не пришлось дописывать руками
|
||||||
|
|
||||||
- [ ] `BRIEF.md` → `docs/passport.md`, обновить
|
## 5. Мелочь, оставленная аудитом сознательно
|
||||||
- [ ] `docs/specs/{recognition,review-ux,workflow}.md` — сверить с capability и
|
|
||||||
удалить как дубли (10)
|
|
||||||
- [ ] `docs/specs/architecture.md` → `docs/architecture.md`, `database.md` →
|
|
||||||
`docs/database.md`, `jellyfin-layout.md` → `docs/research/`
|
|
||||||
- [ ] `docs/review/journal.md` → `docs/review.md`
|
|
||||||
- [ ] `drafts/` растворить: roadmap → `ROADMAP.md`, conventions-backlog → записи
|
|
||||||
`research` (сырьё: тип есть, «Вопрос» пуст), logical-title-model → ADR (H)
|
|
||||||
- [ ] `docs/backlog/` → `docs/tasks/`
|
|
||||||
- [ ] удалить проектные копии скиллов и агентов (4 из REMAINING)
|
|
||||||
- [x] `av-dev-backlog` удалить из маркетплейса и снять с проекта (тема 30)
|
|
||||||
|
|
||||||
## 6. Канон версии 3 — повысить живые проекты (тема 17)
|
Одной пачкой, когда будет повод открыть эти файлы, — не раньше:
|
||||||
|
|
||||||
Оба проекта стоят на каноне 2 и держат `docs/tasks/PLAN.md`: healthlog 55 задач,
|
- [ ] «чекпоинт» несёт третий смысл — точка наблюдаемости в коде
|
||||||
jellybit 43. Шаги повышения — [changelog.md](av-dev-pm/skills/canon/references/changelog.md),
|
(`finding-contract.md`, `promote.md`). Слово занято дважды по своему же
|
||||||
запись «Версия 3»; делаются скиллом `av-dev-pm:canon` в режиме `upgrade`.
|
правилу, но домены разные, и переименование здесь может выйти дороже
|
||||||
|
путаницы
|
||||||
|
- [ ] закрытый словарь `shared/language.md` не содержит ни «конвейера», ни
|
||||||
|
«чекпоинта», ни «груминга» — трёх рабочих терминов репозитория. Список
|
||||||
|
объявлен закрытым, и пополнять его на ходу нельзя
|
||||||
|
- [ ] `move <слаг>` без флагов теперь легален и значит «в конец своей секции» —
|
||||||
|
осмысленная операция, но в прозе не описана нигде
|
||||||
|
- [ ] `reopen` печатает «позиция это приоритет» и для целей роадмапа, где секции
|
||||||
|
очередью не являются
|
||||||
|
|
||||||
- [x] healthlog: `PLAN.md` → `ROADMAP.md`, ссылки, `"canon": 3` — сделано,
|
## 6. Обкатка
|
||||||
лежит в рабочем дереве проекта некоммитнутым
|
|
||||||
- [ ] jellybit: то же
|
|
||||||
- [ ] род работы и раздел «Затрагивает» — **не задним числом**: сперва то, что
|
|
||||||
идёт в ближайший набор (`sprint take` без них откажет), остальное по ходу
|
|
||||||
переоценки (PPP)
|
|
||||||
- [ ] секции роадмапа: `порядок` → `Запланировано`, `темы` → `Направления`,
|
|
||||||
завести `Готово` и `Сопровождение`; прозаические разделы healthlog («Что уже
|
|
||||||
пройдено», «Почему в таком порядке») разложить — звенья строками в
|
|
||||||
`Готово`, обоснование очереди прозой внутри `Запланировано` (тема 19, 80).
|
|
||||||
`check` теперь называет чужую секцию ошибкой, так что шаг обязателен
|
|
||||||
- [ ] переформулировать цели ответом на «что приложение будет уметь»; цели не
|
|
||||||
про приложение («Процесс и качество разработки» в jellybit) — в
|
|
||||||
`Сопровождение`
|
|
||||||
- [ ] `check --fix` на обоих: поднимет написание канонических секций, поставит
|
|
||||||
отбивку после заголовков и сведёт секцию в мете файлов с заголовками.
|
|
||||||
Секции беклога переименовать руками — имена выбирал проект (тема 20, ККК)
|
|
||||||
- [ ] заголовки задач в форму действия — **не задним числом**: по мере попадания
|
|
||||||
задачи в работу. `check` печатает их число, `task-form` предложит
|
|
||||||
формулировки пачкой (тема 20, ЕЕЕ)
|
|
||||||
|
|
||||||
**Канон 4** — сверх того (changelog, запись «Версия 4»):
|
- [ ] один-два цикла healthlog на новом процессе. Наблюдения к первой обкатке
|
||||||
|
два: не выродились ли «границы покрытия» в шаблон (REMAINING, «Открытые
|
||||||
- [ ] healthlog: `## Разработка` → `## Сопровождение`, поле «Секция» в целях этой
|
вопросы») и **не превратился ли чекпоинт в ритуал одобрения** — признак
|
||||||
секции, `check --fix` (переставит `Готово` вниз и поправит отбивку),
|
тот же, дословно повторяющийся текст и согласие без единой правки
|
||||||
`"canon": 4`
|
|
||||||
- [ ] jellybit едет сразу на 4: `Готово` заводить **последней**, секцию
|
|
||||||
сопровождения — сразу с новым именем, переставлять дважды не нужно
|
|
||||||
- [ ] типы: `check --fix` переведёт `kind:`/`[goal]`/`[idea]` в поле «Тип», снимет
|
|
||||||
тег, поставит эмодзи, переименует «Секция» → «Категория» у задач и снесёт
|
|
||||||
сырьё в конец категорий — **за один проход, вместе с порядком секций**
|
|
||||||
- [ ] разобрать `НЕОДНОЗНАЧНО` после `--fix`: записи без типа (заведены до
|
|
||||||
появления рода работы) машина не угадывает — `edit <слаг> --type …`
|
|
||||||
- [ ] имена файлов: `docs.py check` назовёт кириллицу, не-kebab-case и форму
|
|
||||||
имени ADR. Переименование ADR — **перенос ссылок одним проходом**: слаг
|
|
||||||
стоит в `adr/README.md`, в `architecture.md` и в чужих документах
|
|
||||||
- [ ] первый прогон `doc-consistency` на живом проекте — правило единственного
|
|
||||||
дома до сих пор не проверял никто, урожай ожидается крупный; разбирать
|
|
||||||
порциями
|
|
||||||
- [ ] `doc-code-drift` — на ближайшей сессии между спринтами, с разделом
|
|
||||||
запретов `CLAUDE.md` на входе
|
|
||||||
- [ ] новые обязательные разделы — **не задним числом**: `Воспроизведение` у
|
|
||||||
каждого `fix` и `Вопрос` + `Куда ляжет ответ` у каждого `research` пишутся
|
|
||||||
по мере того, как задача идёт в набор (`sprint take` без них откажет).
|
|
||||||
Сколько записей готово к взятию, печатает блок здоровья `check`
|
|
||||||
- [ ] `docs/review.md`, «Триггеры профиля» — переписать целиком: снести перечень
|
|
||||||
мест для `deep` (профиль упразднён), а оставшийся перевести на новое
|
|
||||||
правило — `wide` это крупное или незнакомое изменение, 5–10% задач, плюс
|
|
||||||
отдельный список мелкого для `quick`. Там же две честные строки в
|
|
||||||
«перестали проверять сознательно»: форма решения (снят проход независимой
|
|
||||||
реализации) и всё, что требует запуска (меряющие проходы только в `wide`)
|
|
||||||
|
|
||||||
**Канон 5** — сверх того (changelog, запись «Версия 5»):
|
|
||||||
|
|
||||||
- [ ] `docs/review.md`: «Вопросы к проходам» → **«Вопросы по темам»**, каждый
|
|
||||||
вопрос переадресовать теме вместо имени прохода (`requirements`,
|
|
||||||
`autotests`, `conventions`, `architecture`, `security`, `operations` плюс
|
|
||||||
свои). «Недоступно проверке» — тоже разнести по темам
|
|
||||||
- [ ] проверить, не просился ли в `docs/` документ, который раньше считался
|
|
||||||
лишним: теперь он законен и **становится темой ревью**. Это единственный
|
|
||||||
способ добавить проверку, которой в конвейере нет
|
|
||||||
- [ ] `"canon": 5` в `docs/.pm.json` обоих проектов; форму домов не трогать —
|
|
||||||
обе законны
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "av-dev-code",
|
||||||
|
"description": "Решение одной задачи от постановки до закрытия скиллом resolve — полный цикл Spec Driven Development с двумя плановыми остановками: чекпоинт вариантов у исследовательской задачи и чекпоинт с объяснением человеческим языком после ревью дизайна у всякой. Между ними работа идёт без согласований. Плюс конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом и обязательным триажем. Требует OpenSpec и сам его заводит скиллом openspec. Задача принимается и обычным текстом. Плагины av-dev-docs и av-dev-tasks опциональны: первый даёт документы канона, из которых проходы читают проектную конкретику, второй — учёт задач; без них прогон деградирует поразрядно и называет это строкой.",
|
||||||
|
"author": {
|
||||||
|
"name": "Anton Vakhrushev",
|
||||||
|
"email": "anwinged@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: review-adversary
|
name: review-adversary
|
||||||
description: "Враждебный проход ревью — не проверяет свойства, а строит путь: «ты контролируешь вход целиком — выведи запись за пределы песочницы»; «ты шлёшь запрос и хочешь, чтобы данные не доехали или испортились — построй такой вход»; «ты можешь повторить и переставить любую операцию — что ломается»; «доведи чувствительное до места, где его быть не должно». Находка — построенный путь с шагами, а не наблюдение. Свойства без пути идут в отдельную секцию и не получают critical. Модель угроз берётся из docs/security.md проекта. Только чтение."
|
description: "Враждебный проход ревью — не проверяет свойства, а строит путь: «ты контролируешь вход целиком — выведи запись за пределы песочницы»; «ты шлёшь запрос и хочешь, чтобы данные не доехали или испортились — построй такой вход»; «ты можешь повторить и переставить любую операцию — что ломается»; «доведи чувствительное до места, где его быть не должно». Находка — построенный путь с шагами, а не наблюдение. Свойства без пути идут в отдельную секцию и не получают critical. Модель угроз берётся из docs/security.md проекта. Запускается только с меткой large — на изменении, которое не крупное и не незнакомое, построенного пути он не находит, а стоит дорого. Только чтение."
|
||||||
tools: Read, Grep, Glob, Bash
|
tools: Read, Grep, Glob, Bash
|
||||||
model: opus
|
model: opus
|
||||||
color: yellow
|
color: yellow
|
||||||
@@ -13,7 +13,7 @@ color: yellow
|
|||||||
равно опасен.
|
равно опасен.
|
||||||
|
|
||||||
Находки — по контракту
|
Находки — по контракту
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании).
|
(точный путь конвейер передаёт в задании).
|
||||||
|
|
||||||
**Ты помечен «держит машину»** — за тем, чтобы построенный путь можно было
|
**Ты помечен «держит машину»** — за тем, чтобы построенный путь можно было
|
||||||
@@ -73,7 +73,7 @@ color: yellow
|
|||||||
находкой.
|
находкой.
|
||||||
|
|
||||||
Карта «что нужно проходу → где лежит» —
|
Карта «что нужно проходу → где лежит» —
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`.
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/project-facts.md`.
|
||||||
|
|
||||||
**Деградация поразрядная, и каждый пробел называется своей строкой.**
|
**Деградация поразрядная, и каждый пробел называется своей строкой.**
|
||||||
`docs/security.md` нет — работай по общей рамке ниже, `critical` не присваивай и
|
`docs/security.md` нет — работай по общей рамке ниже, `critical` не присваивай и
|
||||||
+2
-2
@@ -25,7 +25,7 @@ color: yellow
|
|||||||
и граф зависимостей есть только у тебя.
|
и граф зависимостей есть только у тебя.
|
||||||
|
|
||||||
Находки — по контракту
|
Находки — по контракту
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании).
|
(точный путь конвейер передаёт в задании).
|
||||||
|
|
||||||
## Вход (собери до чтения диффа)
|
## Вход (собери до чтения диффа)
|
||||||
@@ -52,7 +52,7 @@ grep по именам концепций) и скажи об этом в гра
|
|||||||
- дельта-спеки change.
|
- дельта-спеки change.
|
||||||
|
|
||||||
Карта «что нужно проходу → где лежит» —
|
Карта «что нужно проходу → где лежит» —
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`.
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/project-facts.md`.
|
||||||
|
|
||||||
Дифф — **последним, не первым**: он должен ложиться на карту, а не задавать её.
|
Дифф — **последним, не первым**: он должен ложиться на карту, а не задавать её.
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ color: green
|
|||||||
намеренно нет, из темы не выпадает — она уходит в границы покрытия.
|
намеренно нет, из темы не выпадает — она уходит в границы покрытия.
|
||||||
|
|
||||||
Находки — по контракту
|
Находки — по контракту
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании). Русская проза, идентификаторы и
|
(точный путь конвейер передаёт в задании). Русская проза, идентификаторы и
|
||||||
команды — в оригинале.
|
команды — в оригинале.
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ color: green
|
|||||||
запускать запрещено, с путями.
|
запускать запрещено, с путями.
|
||||||
|
|
||||||
Карта «что нужно проходу → где лежит» —
|
Карта «что нужно проходу → где лежит» —
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`.
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/project-facts.md`.
|
||||||
|
|
||||||
**Семантики гейта в `CLAUDE.md` нет** — найди команду сама (`Taskfile.yml`,
|
**Семантики гейта в `CLAUDE.md` нет** — найди команду сама (`Taskfile.yml`,
|
||||||
`Makefile`, `justfile`, `scripts/`) и выполни её, но: `critical` по основанию
|
`Makefile`, `justfile`, `scripts/`) и выполни её, но: `critical` по основанию
|
||||||
@@ -96,7 +96,7 @@ color: green
|
|||||||
просило: она может стоить минут и трогать данные.
|
просило: она может стоить минут и трогать данные.
|
||||||
- **Правило есть в конвенциях, но не в линтере.** Если по ходу видно, что отказ
|
- **Правило есть в конвенциях, но не в линтере.** Если по ходу видно, что отказ
|
||||||
или замечание могло быть поймано правилом, — пиши `Promote candidate` по
|
или замечание могло быть поймано правилом, — пиши `Promote candidate` по
|
||||||
процедуре `references/promote.md`.
|
процедуре `${CLAUDE_PLUGIN_ROOT}/skills/review/references/promote.md`.
|
||||||
|
|
||||||
## Что читать не нужно
|
## Что читать не нужно
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ color: yellow
|
|||||||
самый дорогой проход, вместо которого его позвали.
|
самый дорогой проход, вместо которого его позвали.
|
||||||
|
|
||||||
Находки — по контракту
|
Находки — по контракту
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании).
|
(точный путь конвейер передаёт в задании).
|
||||||
|
|
||||||
## Что тебе даёт план прогона
|
## Что тебе даёт план прогона
|
||||||
@@ -146,8 +146,8 @@ color: yellow
|
|||||||
**Молча отменённое решение ADR больше не проверяет никто, и это сознательно.**
|
**Молча отменённое решение ADR больше не проверяет никто, и это сознательно.**
|
||||||
Раньше вопрос стоял здесь и требовал чтения индекса решений; теперь `docs/adr/` —
|
Раньше вопрос стоял здесь и требовал чтения индекса решений; теперь `docs/adr/` —
|
||||||
процессный документ, и прогон его не открывает. Расхождение изменения с записанным
|
процессный документ, и прогон его не открывает. Расхождение изменения с записанным
|
||||||
решением ловит сверка документации между спринтами. Строка об этом обязательна в
|
решением ловит сверка документации — скилл `av-dev-docs:healthcheck`. Строка об
|
||||||
твоих границах покрытия.
|
этом обязательна в твоих границах покрытия.
|
||||||
|
|
||||||
**Карты проекта и графа зависимостей у тебя нет** — они стоят широкого входа, то
|
**Карты проекта и графа зависимостей у тебя нет** — они стоят широкого входа, то
|
||||||
есть `large`. Твой вход — **дифф и его окрестности**. Греп по базе тебе разрешён
|
есть `large`. Твой вход — **дифф и его окрестности**. Греп по базе тебе разрешён
|
||||||
@@ -52,7 +52,7 @@ color: yellow
|
|||||||
его неизбежным.
|
его неизбежным.
|
||||||
|
|
||||||
Находки — по контракту
|
Находки — по контракту
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании). Русская проза, идентификаторы и пути —
|
(точный путь конвейер передаёт в задании). Русская проза, идентификаторы и пути —
|
||||||
в оригинале. Читай реальный код, ничего не выдумывай.
|
в оригинале. Читай реальный код, ничего не выдумывай.
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: review-ops
|
name: review-ops
|
||||||
description: "Эксплуатационный проход ревью — пишет постмортем «это упало через неделю на проде» от симптома у владельца сервиса к строке кода. Обязательные вопросы: рост объёма, деградация окружения и внешних зависимостей, повторная и одновременная операция, частичный откат при двух версиях, миграция под живым потоком, отмена контекста на середине, наблюдаемость и тишина, поведение библиотеки и драйвера в вырожденном случае, чтение узлом состояния, которое он сам же меняет. Формулирует условиями, а не утверждениями — реального профиля нагрузки не знает. Только чтение."
|
description: "Эксплуатационный проход ревью — пишет постмортем «это упало через неделю на проде» от симптома у владельца сервиса к строке кода. Обязательные вопросы: рост объёма, деградация окружения и внешних зависимостей, повторная и одновременная операция, частичный откат при двух версиях, миграция под живым потоком, отмена контекста на середине, наблюдаемость и тишина, поведение библиотеки и драйвера в вырожденном случае, чтение узлом состояния, которое он сам же меняет. Формулирует условиями, а не утверждениями — реального профиля нагрузки не знает. Запускается только с меткой large: постмортем на малом знакомом изменении пишется по общей практике, а не по этому проекту. Только чтение."
|
||||||
tools: Read, Grep, Glob, Bash
|
tools: Read, Grep, Glob, Bash
|
||||||
model: sonnet
|
model: sonnet
|
||||||
color: green
|
color: green
|
||||||
@@ -11,7 +11,7 @@ color: green
|
|||||||
увидит владелец сервиса, и дойди до строки кода.
|
увидит владелец сервиса, и дойди до строки кода.
|
||||||
|
|
||||||
Находки — по контракту
|
Находки — по контракту
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании).
|
(точный путь конвейер передаёт в задании).
|
||||||
|
|
||||||
**Ты помечен «держит машину».** Конвейер за это ставит тебя в цепочку с другими
|
**Ты помечен «держит машину».** Конвейер за это ставит тебя в цепочку с другими
|
||||||
@@ -46,7 +46,7 @@ color: green
|
|||||||
`docs/research/` процессный документ, и прогон его не открывает; чужое число
|
`docs/research/` процессный документ, и прогон его не открывает; чужое число
|
||||||
неизвестной свежести делало находку похожей на доказанную, ничего не доказывая.
|
неизвестной свежести делало находку похожей на доказанную, ничего не доказывая.
|
||||||
Почему именно так и какие ещё есть стыки —
|
Почему именно так и какие ещё есть стыки —
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`, раздел
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/project-facts.md`, раздел
|
||||||
«Сшивать обязаны проходы». Там же карта «что нужно проходу → где лежит».
|
«Сшивать обязаны проходы». Там же карта «что нужно проходу → где лежит».
|
||||||
|
|
||||||
Два обстоятельства почти всегда меняют цену отказов, и если документы их
|
Два обстоятельства почти всегда меняют цену отказов, и если документы их
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: review-rubric
|
name: review-rubric
|
||||||
description: "Generative-проход ревью — сперва, НЕ ВИДЯ КОДА, порождает 8–12 проверяемых свойств, по которым сильный инженер судит узел такого назначения (парсер входного формата, HTTP-обработчик, репозиторий, воркер, клиент внешнего сервиса, CLI-команда, файловое хранилище), и только потом читает код и оценивает по этой рубрике. Достаёт слой, которого нет ни в одной конвенции. Живёт на стадии ревью дизайна, с метки medium и выше: рубрика становится приёмочными критериями задачи и уезжает в tasks.md. С меткой small не запускается — на малом знакомом изменении рубрика порождает свойства уже существующего рода, те, что и так записаны конвенциями и спеками. Только чтение."
|
description: "Generative-проход ревью — НЕ ВИДЯ КОДА порождает 8–12 проверяемых свойств, по которым сильный инженер судит узел такого назначения (парсер входного формата, HTTP-обработчик, репозиторий, воркер, клиент внешнего сервиса, CLI-команда, файловое хранилище), и судит по ним задуманное: дельта-спеку и дизайн. Достаёт слой, которого нет ни в одной конвенции. Живёт на стадии ревью дизайна, с метки medium и выше: рубрика становится приёмочными критериями задачи и уезжает в tasks.md. Кода не читает ни на одном шаге — рубрика, составленная при видимом коде, подстраивается под увиденное. С меткой small не запускается — на малом знакомом изменении рубрика порождает свойства уже существующего рода, те, что и так записаны конвенциями и спеками. Только чтение."
|
||||||
tools: Read, Grep, Glob, Bash
|
tools: Read, Grep, Glob, Bash
|
||||||
model: opus
|
model: opus
|
||||||
color: yellow
|
color: yellow
|
||||||
@@ -11,7 +11,7 @@ color: yellow
|
|||||||
**порождаешь сам** — и делаешь это до того, как увидишь код.
|
**порождаешь сам** — и делаешь это до того, как увидишь код.
|
||||||
|
|
||||||
Находки — по контракту
|
Находки — по контракту
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании). Русская проза, идентификаторы — в
|
(точный путь конвейер передаёт в задании). Русская проза, идентификаторы — в
|
||||||
оригинале.
|
оригинале.
|
||||||
|
|
||||||
@@ -26,18 +26,16 @@ color: yellow
|
|||||||
сформулированное по прецеденту, сильнее любого общего.
|
сформулированное по прецеденту, сильнее любого общего.
|
||||||
|
|
||||||
Карта «что нужно проходу → где лежит» —
|
Карта «что нужно проходу → где лежит» —
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`.
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/project-facts.md`.
|
||||||
|
|
||||||
**Документа нет — строка на каждый, отдельно.** Нет `docs/review.md`: «рода
|
**Документа нет — строка на каждый, отдельно.** Нет `docs/review.md`: «рода
|
||||||
узлов и прецеденты неизвестны; требование „минимум три пункта специфичны для
|
узлов и прецеденты неизвестны; требование „минимум три пункта специфичны для
|
||||||
типа узла" выполнено по общей практике, а не по этому проекту». Нет инвариантов
|
типа узла" выполнено по общей практике, а не по этому проекту». Нет инвариантов
|
||||||
в `CLAUDE.md`: `critical` по основанию «нарушен инвариант проекта» в фазе 2 не
|
в `CLAUDE.md`: `critical` по основанию «нарушен инвариант проекта» не присваивай
|
||||||
присваивай и скажи об этом. Одной строкой за два документа не отделывайся —
|
и скажи об этом. Одной строкой за два документа не отделывайся — чинятся они
|
||||||
чинятся они разным.
|
разным.
|
||||||
|
|
||||||
## Порядок фаз обязателен
|
## Рубрика. Код читать ЗАПРЕЩЕНО
|
||||||
|
|
||||||
### Фаза 1 — рубрика. Код читать ЗАПРЕЩЕНО
|
|
||||||
|
|
||||||
Тебе дают только: назначение узла (одна-две фразы), его тип, сигнатуры на входе и
|
Тебе дают только: назначение узла (одна-две фразы), его тип, сигнатуры на входе и
|
||||||
выходе, соответствующие требования из дельта-спеки. **Не открывай файлы
|
выходе, соответствующие требования из дельта-спеки. **Не открывай файлы
|
||||||
@@ -86,28 +84,29 @@ color: yellow
|
|||||||
Тот же вопрос на **готовом коде** задаёт эксплуатационный проход
|
Тот же вопрос на **готовом коде** задаёт эксплуатационный проход
|
||||||
(вопрос 9); здесь он задаётся дизайну.
|
(вопрос 9); здесь он задаётся дизайну.
|
||||||
|
|
||||||
Выведи рубрику **до** любых находок. Она — часть результата, даже если код
|
Выведи рубрику **до** любых находок. Она — часть результата, даже если
|
||||||
окажется идеальным.
|
задуманное окажется безупречным.
|
||||||
|
|
||||||
### Фаза 2 — оценка
|
## По рубрике судится задуманное, а не код
|
||||||
|
|
||||||
Выполняется только если тебя позвали на готовый код (вне стадии ревью дизайна).
|
Пройди рубрику против **дельта-спеки и дизайна**. Находка — там, где задуманное
|
||||||
Читай код и оцени **по каждому пункту рубрики**: соблюдено / нарушено /
|
пункту прямо противоречит либо оставляет его неопределённым в месте, где
|
||||||
неприменимо, с файлом и строкой.
|
определённость обязательна («что происходит при перекрытии тиков» не сказано ни
|
||||||
|
в спеке, ни в дизайне). Остальные пункты уезжают приёмочными критериями в
|
||||||
|
`tasks.md` change: там их и проверит приёмка.
|
||||||
|
|
||||||
**Новые критерии на этой фазе не добавляются.** Если по ходу чтения возник
|
**Оценки кода у этого прохода нет, и это решение, а не пробел.** Судить код по
|
||||||
критерий, которого не было в рубрике, — вынеси его в отдельную секцию «Появилось
|
критерию, под который он писался, — корреляция по построению, и потому проход
|
||||||
при чтении кода» и пометь `Confidence: low`: он подстроен под увиденное и потому
|
живёт только на стадии ревью дизайна, где кода ещё нет. Позвали на готовый
|
||||||
слабее.
|
код — это ошибка вызова: скажи об этом строкой и рубрику всё равно не подгоняй
|
||||||
|
под увиденное.
|
||||||
|
|
||||||
## Что делать с рубрикой дальше
|
## Что делать с рубрикой дальше
|
||||||
|
|
||||||
Пункты рубрики, которых **нет в конвенциях проекта**, — кандидаты на промоут: это
|
Пункты рубрики, которых **нет в конвенциях проекта**, — кандидаты на промоут: это
|
||||||
и есть неявный слой, ради которого проход существует. Выведи их отдельной секцией
|
и есть неявный слой, ради которого проход существует. Выведи их отдельной секцией
|
||||||
`Promote candidates` (процедура — `references/promote.md`).
|
`Promote candidates` (процедура —
|
||||||
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/promote.md`).
|
||||||
На стадии ревью дизайна (кода ещё нет) фаза 2 не выполняется: рубрика уезжает в
|
|
||||||
`tasks.md` change как приёмочные критерии.
|
|
||||||
|
|
||||||
## Чего этот проход принципиально не может поймать
|
## Чего этот проход принципиально не может поймать
|
||||||
|
|
||||||
@@ -122,22 +121,21 @@ color: yellow
|
|||||||
|
|
||||||
## Формат вывода
|
## Формат вывода
|
||||||
|
|
||||||
1. `## Рубрика` — нумерованный список свойств (порождена до чтения кода).
|
1. `## Рубрика` — нумерованный список свойств (порождена до чтения спеки).
|
||||||
2. `## Оценка` — по каждому пункту: соблюдено/нарушено/неприменимо + файл:строка
|
2. `## Разбор` — по каждому пункту: покрыт задуманным / противоречие /
|
||||||
(только вне стадии ревью дизайна).
|
не определён / неприменим, со ссылкой на требование или раздел дизайна.
|
||||||
3. Находки по контракту — только по нарушенным пунктам.
|
3. Находки по контракту — только по пунктам с противоречием и неопределённостью.
|
||||||
4. `## Появилось при чтении кода` — если было.
|
4. `## Promote candidates`.
|
||||||
5. `## Promote candidates`.
|
5. Обязательный блок:
|
||||||
6. Обязательный блок:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
## Coverage of this pass
|
## Coverage of this pass
|
||||||
- проверено: <какие пункты рубрики против каких файлов>
|
- проверено: <какие пункты рубрики против каких требований и разделов дизайна>
|
||||||
- не проверялось и почему: ...
|
- не проверялось и почему: ...
|
||||||
- принципиально недоступно этому проходу: рантайм, сверка со спекой, межмодульные связи
|
- принципиально недоступно этому проходу: код, рантайм, сверка со спекой, межмодульные связи
|
||||||
```
|
```
|
||||||
|
|
||||||
## Ограничения
|
## Ограничения
|
||||||
|
|
||||||
Только чтение. В фазе 1 — не читать реализацию вообще; если задание не дало
|
Только чтение, и реализацию не читать вообще; если задание не дало назначения и
|
||||||
назначения и сигнатур, попроси их, а не иди смотреть код сам.
|
сигнатур, попроси их, а не иди смотреть код сам.
|
||||||
@@ -74,6 +74,15 @@ color: green
|
|||||||
| **`tasks.md`** | число шагов и их разнородность: шаги, лежащие в разных узлах и слоях | шаг вида «разобраться», «выяснить», «попробовать» |
|
| **`tasks.md`** | число шагов и их разнородность: шаги, лежащие в разных узлах и слоях | шаг вида «разобраться», «выяснить», «попробовать» |
|
||||||
| **дельта-спеки** | сколько capability затронуто и сколько требований в каждой | `ADDED` целой capability — поведения такого рода не было; только `MODIFIED` в одной — было |
|
| **дельта-спеки** | сколько capability затронуто и сколько требований в каждой | `ADDED` целой capability — поведения такого рода не было; только `MODIFIED` в одной — было |
|
||||||
|
|
||||||
|
**Записи задачи может не быть вовсе, и это не довод за незнакомое.** Задача
|
||||||
|
приходит текстом или из проекта без плагина задач — тогда раздела «Затрагивает»
|
||||||
|
нет **по построению**, а не потому, что границы не назвали. Отличай:
|
||||||
|
запись есть, а раздела в ней нет → незнакомое, как сказано в таблице; записи нет
|
||||||
|
→ строка источника снимается, обе оси выводятся из остальных четырёх, и это
|
||||||
|
называется в плане строкой «записи задачи нет, оси выведены по четырём
|
||||||
|
источникам». Иначе всякая задача без плагина задач систематически едет в `large`
|
||||||
|
за то, чего никто не терял.
|
||||||
|
|
||||||
**`design.md` информативен и своим отсутствием.** Его нет — либо задача
|
**`design.md` информативен и своим отсутствием.** Его нет — либо задача
|
||||||
тривиальна (тогда это подтверждает малое и знакомое), либо нетривиальную завели
|
тривиальна (тогда это подтверждает малое и знакомое), либо нетривиальную завели
|
||||||
без разбора решения, и тогда «форму знали заранее» ничем не подтверждено: считай
|
без разбора решения, и тогда «форму знали заранее» ничем не подтверждено: считай
|
||||||
@@ -186,7 +195,7 @@ color: green
|
|||||||
**Ты меряешь изменение по двум независимым осям и называешь обе.** Метка — не
|
**Ты меряешь изменение по двум независимым осям и называешь обе.** Метка — не
|
||||||
ответ на один вопрос, а максимум по двум измерениям.
|
ответ на один вопрос, а максимум по двум измерениям.
|
||||||
|
|
||||||
Ниже рабочая выжимка. Дом правила — скилл `av-dev-pipeline:review-pipeline`,
|
Ниже рабочая выжимка. Дом правила — скилл `av-dev-code:review`,
|
||||||
`references/review-levels.md`: там разобрано, почему оси именно эти, чем `small`
|
`references/review-levels.md`: там разобрано, почему оси именно эти, чем `small`
|
||||||
дешевле `medium` и какие доли служат проверкой правила. Открывай его, когда
|
дешевле `medium` и какие доли служат проверкой правила. Открывай его, когда
|
||||||
метка **спорная или оспорена**; на обычной задаче хватает того, что здесь.
|
метка **спорная или оспорена**; на обычной задаче хватает того, что здесь.
|
||||||
@@ -333,7 +342,7 @@ security docs/security.md разбор basics
|
|||||||
operations docs/architecture.md, «Эксплуатация» разбор basics
|
operations docs/architecture.md, «Эксплуатация» разбор basics
|
||||||
дома нет: docs/database.md отсутствует
|
дома нет: docs/database.md отсутствует
|
||||||
|
|
||||||
процессные: docs/tasks/, docs/review.md, docs/adr/, docs/research/
|
процессные: tasks/, docs/review.md, docs/adr/, docs/research/
|
||||||
директивы: CLAUDE.md найден, AGENTS.md отсутствует
|
директивы: CLAUDE.md найден, AGENTS.md отсутствует
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: review-specs
|
name: review-specs
|
||||||
description: "Сверка изменения с дельта-спеками в обе стороны — spec→code (каждое требование реализовано и подтверждено тестом) и, что важнее, code→spec (поведение, которое код имеет, а спека не заказывала: тихие ветки, самодеятельные дефолты, проглоченные ошибки, отброшенные поля, ретраи «на всякий случай»). Плюс границы спеки — что она не определяет и что пришлось домыслить. Работает в трёх режимах: дизайн/спеки ДО кода, код против спек ПОСЛЕ apply и стык после слияния нескольких задач, когда change уже заархивированы. Только чтение."
|
description: "Сверка изменения с дельта-спеками в обе стороны — spec→code (каждое требование реализовано и подтверждено тестом) и, что важнее, code→spec (поведение, которое код имеет, а спека не заказывала: тихие ветки, самодеятельные дефолты, проглоченные ошибки, отброшенные поля, ретраи «на всякий случай»). Плюс границы спеки — что она не определяет и что пришлось домыслить. Работает в двух режимах: дизайн/спеки ДО кода и код против спек ПОСЛЕ apply. Только чтение."
|
||||||
tools: Read, Grep, Glob, Bash
|
tools: Read, Grep, Glob, Bash
|
||||||
model: opus
|
model: opus
|
||||||
color: yellow
|
color: yellow
|
||||||
@@ -10,7 +10,7 @@ color: yellow
|
|||||||
Development на OpenSpec). Оптика — требования, а не стиль кода.
|
Development на OpenSpec). Оптика — требования, а не стиль кода.
|
||||||
|
|
||||||
Находки — по контракту
|
Находки — по контракту
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании). Русская проза; идентификаторы, пути и
|
(точный путь конвейер передаёт в задании). Русская проза; идентификаторы, пути и
|
||||||
ключевые слова спек (`SHALL`, `GIVEN/WHEN/THEN`) — в оригинале. Читай реальные
|
ключевые слова спек (`SHALL`, `GIVEN/WHEN/THEN`) — в оригинале. Читай реальные
|
||||||
файлы перед выводом, ничего не выдумывай.
|
файлы перед выводом, ничего не выдумывай.
|
||||||
@@ -49,7 +49,7 @@ Development на OpenSpec). Оптика — требования, а не ст
|
|||||||
|
|
||||||
Пути спек жёсткие: актуальные — `openspec/specs/<capability>/spec.md`, дельты —
|
Пути спек жёсткие: актуальные — `openspec/specs/<capability>/spec.md`, дельты —
|
||||||
`openspec/changes/<id>/specs/`. Карта «что нужно проходу → где лежит» —
|
`openspec/changes/<id>/specs/`. Карта «что нужно проходу → где лежит» —
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`.
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/project-facts.md`.
|
||||||
|
|
||||||
**Нет инвариантов в `CLAUDE.md`** — сверяй только спеку с кодом, `critical` по
|
**Нет инвариантов в `CLAUDE.md`** — сверяй только спеку с кодом, `critical` по
|
||||||
основанию «нарушен инвариант проекта» не присваивай и дай строку: «инвариантов в
|
основанию «нарушен инвариант проекта» не присваивай и дай строку: «инвариантов в
|
||||||
@@ -63,11 +63,9 @@ Development на OpenSpec). Оптика — требования, а не ст
|
|||||||
намерение, а спека нормирует. Расхождение между proposal и дельтой — само по себе
|
намерение, а спека нормирует. Расхождение между proposal и дельтой — само по себе
|
||||||
находка.
|
находка.
|
||||||
|
|
||||||
**Исключение — режим 3 (ниже): живого change нет.** Тогда источник требований —
|
**Живого change нет — ты не запускаешься.** Оба режима стоят на дельта-спеке; без
|
||||||
**актуальные** `openspec/specs/<capability>/spec.md`, а дельты поднимаются из
|
неё сверять нечего, и это строка отказа, а не повод взять источником актуальные
|
||||||
архива (`openspec/changes/archive/<id>/specs/`) как свидетельство о намерении
|
спеки: они описывают, что система делает вообще, а не что заказало это изменение.
|
||||||
каждой слитой задачи. Задание обязано назвать этот режим явно; не названо —
|
|
||||||
работаешь по режиму 1 или 2 и говоришь в границах покрытия, что change не нашёл.
|
|
||||||
|
|
||||||
Дополнительно поднимаешь **с метки `medium`**: `design.md` и `tasks.md`
|
Дополнительно поднимаешь **с метки `medium`**: `design.md` и `tasks.md`
|
||||||
change, затронутые актуальные спеки. Инварианты из `CLAUDE.md` — при любой метке. Если тема ещё не перенесена в спеки и живёт только в
|
change, затронутые актуальные спеки. Инварианты из `CLAUDE.md` — при любой метке. Если тема ещё не перенесена в спеки и живёт только в
|
||||||
@@ -144,27 +142,6 @@ change, затронутые актуальные спеки. Инвариант
|
|||||||
скажи об этом прямо, с последствием. Такая находка всегда `Действие: развилка`:
|
скажи об этом прямо, с последствием. Такая находка всегда `Действие: развилка`:
|
||||||
менять спеку — решение человека.
|
менять спеку — решение человека.
|
||||||
|
|
||||||
## Режим 3 — стык после слияния нескольких задач
|
|
||||||
|
|
||||||
Зовётся финальной сверкой `task-batch`: несколько задач влиты в основную ветку,
|
|
||||||
их change **уже заархивированы**, живой дельта-спеки не существует. Предмет —
|
|
||||||
**только то, что появилось от слияния**, а не capability целиком заново: каждая
|
|
||||||
задача уже проверена в своём worktree, и повторение даст те же находки дороже.
|
|
||||||
|
|
||||||
Ищешь ровно три вещи:
|
|
||||||
|
|
||||||
- **отменённое требование** — одна задача его выполнила, соседняя незаметно
|
|
||||||
сняла; в актуальной спеке требование есть, в интегрированном коде его больше
|
|
||||||
нет;
|
|
||||||
- **два описания одного поведения** — два архивных change по-разному нормировали
|
|
||||||
одно и то же, и актуальная спека собрала из них противоречие;
|
|
||||||
- **осиротевшее поведение** — код, пришедший от слияния (разрешение конфликта,
|
|
||||||
правка при rebase), которого не заказывал ни один из change.
|
|
||||||
|
|
||||||
База — интегрированный дифф основной ветки против точки, с которой батч начался.
|
|
||||||
В границах покрытия скажи прямо: **capability целиком в этом режиме не
|
|
||||||
сверялась**, проверялись стыки.
|
|
||||||
|
|
||||||
## Чего этот проход принципиально не может поймать
|
## Чего этот проход принципиально не может поймать
|
||||||
|
|
||||||
- Качество формы решения: код может точно соответствовать спеке и быть плохим.
|
- Качество формы решения: код может точно соответствовать спеке и быть плохим.
|
||||||
@@ -16,7 +16,7 @@ color: yellow
|
|||||||
Потолок в 7 пунктов защищает код, а не читателя.
|
Потолок в 7 пунктов защищает код, а не читателя.
|
||||||
|
|
||||||
Контракт находок и формат финального отчёта —
|
Контракт находок и формат финального отчёта —
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/finding-contract.md`
|
||||||
(точный путь конвейер передаёт в задании).
|
(точный путь конвейер передаёт в задании).
|
||||||
|
|
||||||
## Вход
|
## Вход
|
||||||
@@ -29,12 +29,10 @@ color: yellow
|
|||||||
и метка с обоснованием. Он твой главный инструмент сверки: ты единственный, кто
|
и метка с обоснованием. Он твой главный инструмент сверки: ты единственный, кто
|
||||||
видит и то, что размечено, и то, что пришло.
|
видит и то, что размечено, и то, что пришло.
|
||||||
|
|
||||||
**Плана нет — ты не запускаешься.** Сверка размеченного с пришедшим — твоя
|
**Плана нет — ты не запускаешься, и исключений нет.** Сверка размеченного с
|
||||||
единственная защита от молчащего пропуска, и без плана она не выполняется вовсе.
|
пришедшим — твоя единственная защита от молчащего пропуска, и без плана она не
|
||||||
Отчёт, собранный без неё, выглядит полным ровно настолько же, насколько и
|
выполняется вовсе. Отчёт, собранный без неё, выглядит полным ровно настолько же,
|
||||||
неполный. Исключение одно и объявленное: финальная сверка стыка в
|
насколько и неполный.
|
||||||
`av-dev-pipeline:task-batch` — там разметчика нет по построению, и план тебе
|
|
||||||
собирает сам батч, коротким списком запущенного.
|
|
||||||
|
|
||||||
Из документов проекта тебе нужны:
|
Из документов проекта тебе нужны:
|
||||||
|
|
||||||
@@ -49,7 +47,7 @@ color: yellow
|
|||||||
целиком уезжают в границы покрытия и **не сливаются в один список**.
|
целиком уезжают в границы покрытия и **не сливаются в один список**.
|
||||||
|
|
||||||
Карта «что нужно проходу → где лежит» —
|
Карта «что нужно проходу → где лежит» —
|
||||||
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`.
|
`${CLAUDE_PLUGIN_ROOT}/skills/review/references/project-facts.md`.
|
||||||
|
|
||||||
**Деградация поразрядная, и ты — тот, кто собирает её строки в один список,
|
**Деградация поразрядная, и ты — тот, кто собирает её строки в один список,
|
||||||
сохраняя каждую.** Свою часть
|
сохраняя каждую.** Свою часть
|
||||||
@@ -209,7 +207,7 @@ severity:
|
|||||||
|
|
||||||
1. **Решения проекта не сверялись.** `docs/adr.*` — процессный документ, прогон
|
1. **Решения проекта не сверялись.** `docs/adr.*` — процессный документ, прогон
|
||||||
его не открывает. Расхождение изменения с записанным решением ловит сверка
|
его не открывает. Расхождение изменения с записанным решением ловит сверка
|
||||||
документации между спринтами, а не ревью.
|
документации — скилл `av-dev-docs:healthcheck`, а не ревью.
|
||||||
2. **Записанные наблюдения проекта не использовались.** `docs/research.*` — тоже
|
2. **Записанные наблюдения проекта не использовались.** `docs/research.*` — тоже
|
||||||
процессный. Всякое число в находках снято проходом на этом прогоне; числа без
|
процессный. Всякое число в находках снято проходом на этом прогоне; числа без
|
||||||
приложенной команды замера в отчёте быть не должно.
|
приложенной команды замера в отчёте быть не должно.
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
---
|
||||||
|
name: openspec
|
||||||
|
description: "Завести и настроить OpenSpec в проекте — openspec init --tools claude, замена закомментированного примера в openspec/config.yaml на настройку канонической формы (язык, правила именования capability, придирки валидатора, адреса паспорта и CLAUDE.md), проверка формы своим скриптом openspec.py (имя файла, схема, незаменённый пример, адреса документов, ключи rules против артефактов схемы) и сверка слепка с живой версией инструмента. Использовать, когда в проекте нет каталога openspec/, когда config.yaml остался примером из коробки, когда заводят новый проект или переводят чужой и дошли до шага OpenSpec, а также когда конвейер отказался работать без источника требований. Каталог openspec нужен именно конвейеру: без него не работают ни opsx:propose, ни ревью дизайна, ни сверка требований."
|
||||||
|
---
|
||||||
|
|
||||||
|
# OpenSpec в проекте
|
||||||
|
|
||||||
|
Каталог `openspec/` — **предпосылка конвейера**, а не канона документов. Без него
|
||||||
|
не работают ни `opsx:propose`, ни ревью дизайна, ни `review-specs`: у требований
|
||||||
|
не остаётся дома. Поэтому заводит и настраивает его этот плагин — тот, кто по
|
||||||
|
OpenSpec и работает.
|
||||||
|
|
||||||
|
Канон документов о файле не высказывается вовсе: `docs.py` его не открывает и об
|
||||||
|
его отсутствии молчит. Проект без конвейера живёт без OpenSpec законно, и
|
||||||
|
проверять там нечего. За каноном остаётся одно — **единственный дом**: не
|
||||||
|
пересказан ли в `context` документ, у которого есть свой файл. Это суждение, а не
|
||||||
|
форма, и смотрит его агент.
|
||||||
|
|
||||||
|
## Два шага, и второй важнее первого
|
||||||
|
|
||||||
|
**1. Завести.**
|
||||||
|
|
||||||
|
```
|
||||||
|
openspec init --tools claude
|
||||||
|
```
|
||||||
|
|
||||||
|
Команда кладёт ещё `.claude/skills/openspec-*` и `.claude/commands/opsx/*` — это
|
||||||
|
её нормальная работа, не трогай их.
|
||||||
|
|
||||||
|
**2. Заменить пример.** `openspec init` кладёт `config.yaml`, где `context` и
|
||||||
|
`rules` — закомментированный пример на английском. **Файл из коробки хуже
|
||||||
|
отсутствующего:** он есть, он валиден, имя правильное, — и читается как
|
||||||
|
настроенный, работая как пустой. Узнаётся это по уже написанному предложению: на
|
||||||
|
другом языке, с capability по имени пакета, без единого `SHALL`.
|
||||||
|
|
||||||
|
Пример **заменяется целиком** по образцу:
|
||||||
|
[references/config-skeleton.md](references/config-skeleton.md).
|
||||||
|
|
||||||
|
## Что туда пишут, а что нет
|
||||||
|
|
||||||
|
**Это маршрутизатор, а не второй дом фактов.** Внутрь идёт ровно то, что нужно
|
||||||
|
**в момент порождения артефакта** и чего в этот момент ещё никто не открыл: язык,
|
||||||
|
правила именования capability, придирки валидатора и **адреса** документов
|
||||||
|
проекта.
|
||||||
|
|
||||||
|
Сюда же — **требования к форме `proposal` и `design`**, на которых стоит чекпоинт
|
||||||
|
скилла `av-dev-code:resolve`: объяснение человеку собирается из этих двух
|
||||||
|
артефактов, и требование к ним обязано применяться в момент, когда их пишут, а не
|
||||||
|
вспоминаться шагом позже. Образец их содержит.
|
||||||
|
|
||||||
|
Пересказ паспорта, инвариантов, конвенций и правил ревью сюда **не переносится**.
|
||||||
|
Место для второго дома здесь самое частое: `context` читается при порождении
|
||||||
|
каждого артефакта, туда удобно дописать «чтобы агент знал», и так заводятся копии
|
||||||
|
инвариантов, состава гейта и правил выбора метки. Расходятся они молча, а
|
||||||
|
замечают это в уже написанном предложении.
|
||||||
|
|
||||||
|
Разрез, по которому отличают одно от другого: **утверждение, которое можно
|
||||||
|
опровергнуть, открыв другой файл проекта, — пересказ; строка, которая говорит,
|
||||||
|
какой файл открыть, — ссылка.** Машина этот разрез не проверяет; его смотрит
|
||||||
|
агент `doc-consistency` из плагина канона, когда тот подключён.
|
||||||
|
|
||||||
|
Два адреса обязательны — `docs/passport.md` и `CLAUDE.md`: предложение пишется до
|
||||||
|
того, как кто-либо откроет `docs/`, и без них его пишут, не зная ни границы
|
||||||
|
домена, ни инвариантов. Отсутствие адреса к **существующему** документу
|
||||||
|
`openspec.py check` называет отказом; документа нет в проекте — нет и требования.
|
||||||
|
|
||||||
|
## Инструмент
|
||||||
|
|
||||||
|
```
|
||||||
|
os="$CLAUDE_PLUGIN_ROOT/skills/openspec/scripts/openspec.py"
|
||||||
|
|
||||||
|
python3 $os check --dir <корень> # форма config.yaml в проекте
|
||||||
|
python3 $os form # слепок формы против живого OpenSpec
|
||||||
|
```
|
||||||
|
|
||||||
|
**Коды выхода — общий словарь скриптов av-dev:** 0 сошлось, 1 дрейф, 2 ошибка
|
||||||
|
употребления, 3 окружение, 4 внутренний сбой. Ветвись на коде, а не на тексте.
|
||||||
|
Различать 1 и 3 обязательно: «форма разошлась» — рабочая ситуация, «openspec не
|
||||||
|
отвечает» — нерабочая.
|
||||||
|
|
||||||
|
`check` проверяет форму, и каждая проверка — про молчащий пробел, а не про вкус:
|
||||||
|
каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об этом
|
||||||
|
не сообщает); ключ `schema` называет ту схему, для которой форма описана;
|
||||||
|
`context` и `rules.specs` не остались примером, **а `SHALL` назван именно внутри
|
||||||
|
`rules.specs`** (в `context` он стоит и в образце, поэтому греп по файлу здесь
|
||||||
|
ничего не значит); `context` называет паспорт и `CLAUDE.md`; ключи под `rules:` —
|
||||||
|
имена артефактов схемы, а не свободные слова. Числа проверок здесь нет намеренно:
|
||||||
|
оно протухает от каждой добавленной.
|
||||||
|
|
||||||
|
**Адреса требуются только к тем документам, которые в проекте есть.** Канон
|
||||||
|
документов ставится отдельным плагином и может быть не подключён; требовать
|
||||||
|
ссылку на несуществующий файл значит требовать битую ссылку. Нет
|
||||||
|
`docs/passport.md` — проверка по нему идёт строкой «не проверялось», и там же
|
||||||
|
сказано, что без канона конвейер работает вслепую.
|
||||||
|
|
||||||
|
### Форма сверяется с живым инструментом
|
||||||
|
|
||||||
|
Схема (`spec-driven`) и перечень артефактов (`proposal`, `specs`, `design`,
|
||||||
|
`tasks`) — **состояние чужого инструмента**, а не наше решение. OpenSpec
|
||||||
|
переименует артефакт: правила под прежним именем перестанут применяться, конфиг
|
||||||
|
останется выглядеть написанным, и молчат при этом все три стороны.
|
||||||
|
|
||||||
|
Сторож — сравнение версий. `check` каждым прогоном спрашивает `openspec
|
||||||
|
--version` (десятые доли секунды) и сравнивает `major.minor` с той версией, на
|
||||||
|
которой форма сверялась; разошлось — **замечание**, не отказ, с именем команды.
|
||||||
|
Патч-версия в сравнение не берётся намеренно: формы она не меняет, а нагоняй на
|
||||||
|
каждый багфикс приучает пролистывать весь блок.
|
||||||
|
|
||||||
|
Перепроверяет `openspec.py form`: он спрашивает `openspec templates --json`, то
|
||||||
|
есть перечень артефактов текущей схемы, и печатает, что разошлось с константами.
|
||||||
|
Дорогой вызов вынесен из `check` сознательно — он стоит втрое дороже опроса
|
||||||
|
версии, а ответ меняется только вместе с версией. **Чинится расхождение в
|
||||||
|
плагине, а не в проекте:** константы скрипта, образец
|
||||||
|
[references/config-skeleton.md](references/config-skeleton.md) и запись в журнал
|
||||||
|
версий канона.
|
||||||
|
|
||||||
|
## Кто зовёт этот скилл
|
||||||
|
|
||||||
|
- `av-dev-docs:init` — шагом заведения нового проекта, до первого документа;
|
||||||
|
- `av-dev-docs:canon` в режиме `adopt` — если на переводимом проекте каталога нет
|
||||||
|
или `config.yaml` остался примером;
|
||||||
|
- `av-dev-code:resolve` и `av-dev-code:review` — не вызовом по ходу, а отсылкой:
|
||||||
|
OpenSpec у обоих жёсткая предпосылка, и на проекте без каталога оба посылают
|
||||||
|
сюда вместо того, чтобы заводить его руками;
|
||||||
|
- человек — когда конвейер отказался работать без источника требований.
|
||||||
|
|
||||||
|
**Копия.** Дом правила — `shared/plugin-boundary.md` в репозитории плагинов.
|
||||||
|
Правится дом, а не этот файл.
|
||||||
|
|
||||||
|
<!-- копия: граница-плагинов из shared/plugin-boundary.md -->
|
||||||
|
|
||||||
|
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
|
||||||
|
месте.
|
||||||
|
|
||||||
|
**Чужой скилл зовётся полным именем** — `av-dev-docs:canon`, `av-dev-tasks:tasks`,
|
||||||
|
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
|
||||||
|
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
|
||||||
|
|
||||||
|
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
|
||||||
|
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
|
||||||
|
прочитает его сам.
|
||||||
|
|
||||||
|
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
|
||||||
|
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
|
||||||
|
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
|
||||||
|
|
||||||
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||||
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
|
||||||
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
Здесь это значит: вызов не разрешился — плагина конвейера в проекте нет, и тогда
|
||||||
|
OpenSpec заводит человек командой выше.
|
||||||
|
|
||||||
|
## Чего этот скилл не делает
|
||||||
|
|
||||||
|
- **Не пишет спеки и предложения.** Это `opsx:propose` и конвейер задачи.
|
||||||
|
- **Не ведёт документы канона** — их дом плагин `av-dev-docs`, и адреса в
|
||||||
|
`context` только на них ссылаются.
|
||||||
|
- **Не чинит расхождение формы с версией OpenSpec в проекте.** Оно чинится в
|
||||||
|
плагине: константы скрипта, образец здесь, запись в журнал версий канона.
|
||||||
|
- **Не судит, ссылается `context` на документы или пересказывает их.** Машине
|
||||||
|
этот разрез не виден; его смотрит агент `doc-consistency` из плагина канона.
|
||||||
|
Плагина нет — эту проверку не делает никто, и так и скажи.
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# Образец `openspec/config.yaml`
|
||||||
|
|
||||||
|
Каталог `openspec/` заводится командой — `openspec init --tools claude`, — и она
|
||||||
|
кладёт `config.yaml` с закомментированным примером внутри. Пример **заменяется
|
||||||
|
целиком**: нетронутый файл выглядит настроенным, а работает как пустой.
|
||||||
|
|
||||||
|
**Это маршрутизатор, а не второй дом фактов.** Сюда пишут ровно то, что нужно
|
||||||
|
**в момент порождения артефакта** и чего в этот момент ещё никто не открыл:
|
||||||
|
язык, правила именования capability, придирки валидатора и **адреса** документов
|
||||||
|
канона. Пересказ паспорта, инвариантов, конвенций и правил ревью сюда не
|
||||||
|
переносится: расходится он молча, а замечают это в уже написанном предложении.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
schema: spec-driven
|
||||||
|
|
||||||
|
context: |
|
||||||
|
Language: Russian
|
||||||
|
Пиши на русском, но:
|
||||||
|
- Структурные заголовки оставляй на английском:
|
||||||
|
## ADDED/MODIFIED/REMOVED Requirements, ### Requirement:, #### Scenario:
|
||||||
|
- Ключевые слова GIVEN/WHEN/THEN и RFC 2119 (SHALL/MUST/SHOULD) — на английском
|
||||||
|
- Технические термины, пути и код — на английском
|
||||||
|
|
||||||
|
Имена capabilities:
|
||||||
|
- Capability — это ПОВЕДЕНИЕ или домен системы, а не пакет кода (совпадение с
|
||||||
|
именем пакета допустимо, но не критерий).
|
||||||
|
- Существительное, понятное без знания кода: ingest, parsing, storage,
|
||||||
|
read-api. НЕ store/httpapi — это реализация.
|
||||||
|
- Гранулярность по принципу «требования меняются вместе». Дробить, когда в
|
||||||
|
одной спеке смешиваются разные заботы. Переименовать дёшево (RENAMED
|
||||||
|
Requirements) — не дроби преждевременно в маленьком проекте.
|
||||||
|
|
||||||
|
RFC 2119 — требование валидатора, не стиль:
|
||||||
|
- Каждое ### Requirement ОБЯЗАНО содержать литерал SHALL или MUST, иначе
|
||||||
|
`openspec validate` падает. Поэтому эти слова и WHEN/THEN не русифицируем.
|
||||||
|
|
||||||
|
Что это за проект — читай перед предложением, а не отсюда:
|
||||||
|
- docs/passport.md — цель, её граница (чем проект НЕ является), потребители,
|
||||||
|
типовые сценарии, референсы;
|
||||||
|
- CLAUDE.md — инварианты с severity и семантика гейта;
|
||||||
|
- docs/architecture.md — устройство; docs/security.md — периметр;
|
||||||
|
docs/adr/ — почему решено так; docs/research/ — что уже измерено.
|
||||||
|
Пересказа этих документов здесь нет намеренно: второй дом факта расходится с
|
||||||
|
первым молча, и заметно это становится в предложении, которое уже написано.
|
||||||
|
|
||||||
|
Ревью: правило выбора метки и состав проходов здесь не пересказываем — их дом
|
||||||
|
скилл av-dev-code:review, проектная настройка — docs/review.md.
|
||||||
|
|
||||||
|
Конвенции кода: механизированное проверяет гейт, прозой остаётся
|
||||||
|
docs/conventions/. Ни состав шагов гейта, ни перечень конвенций здесь не
|
||||||
|
пересказываем: и то и другое растёт по ходу задач.
|
||||||
|
|
||||||
|
Развилка или блокер — сперва prior art. Готовые решения смотрим в референсах
|
||||||
|
паспорта, отвергаем — с названной причиной, и причина идёт в design.md этого
|
||||||
|
же изменения.
|
||||||
|
|
||||||
|
rules:
|
||||||
|
proposal:
|
||||||
|
- Capabilities называй по поведению или домену системы, не по пакету кода
|
||||||
|
- "Why и What Changes — языком домена из docs/passport.md: без SHALL, без имён модулей и функций там, где вещь называется по-русски"
|
||||||
|
design:
|
||||||
|
- "Назови рассмотренные варианты и причину отказа от каждого — из них потом пишется ADR"
|
||||||
|
- "Решение объясняется через то, что человек увидит иначе, а не через устройство кода"
|
||||||
|
specs:
|
||||||
|
# Кавычки обязательны: без них YAML обрежет строку на первом '#'.
|
||||||
|
- "Каждое ### Requirement обязано содержать SHALL или MUST (иначе валидация падает)"
|
||||||
|
- "Сценарий — ровно #### (четыре решётки); три или список молча теряются"
|
||||||
|
- "SHALL/MUST должно стоять в ПЕРВОМ абзаце требования: валидатор смотрит только его"
|
||||||
|
- "Заголовки и WHEN/THEN/GIVEN — на английском, остальной текст на русском"
|
||||||
|
tasks:
|
||||||
|
- "Критерии приёмки задачи — отдельным блоком и дословно: файл задачи закрытие удалит, критерии обязаны его пережить"
|
||||||
|
- "Рубрика ревью дизайна, если оно её дало, идёт в тот же блок: приёмка судится по одному списку, а не по двум"
|
||||||
|
- "Шаг плана формулируется проверяемо — по нему видно «сделано / не сделано» без суждения"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Четыре правила для `specs` сняты отказами валидатора, а не выведены из
|
||||||
|
документации** — потому и записаны дословно: без них каждое второе предложение
|
||||||
|
узнаёт их падением `openspec validate --strict`.
|
||||||
|
|
||||||
|
**Правила для `proposal` и `design` держат чекпоинт скилла
|
||||||
|
`av-dev-code:resolve`.** Там работа останавливается и человеку объясняют, в
|
||||||
|
чём проблема и как её решают, — а объяснение **собирается из этих двух
|
||||||
|
артефактов**, а не сочиняется заново: третий пересказ одного и того же разошёлся
|
||||||
|
бы с обоими. Требование поэтому стоит здесь, в момент порождения артефакта, а не
|
||||||
|
в скилле, который спохватится позже. `design.md` при этом ещё и **сырьё для
|
||||||
|
ADR** — отвергнутый вариант с названной причиной и есть половина будущей записи.
|
||||||
|
|
||||||
|
**Правила для `tasks` держит тот же скилл, и по той же причине — момент
|
||||||
|
порождения.** `tasks.md` — единственное, что переживает задачу: файл задачи
|
||||||
|
закрытие удаляет, а приёмка потом судится по критериям, которые в него
|
||||||
|
скопированы. Туда же ложится рубрика ревью дизайна, если оно её дало. Записанное
|
||||||
|
в момент порождения не приходится вспоминать шагом позже, когда артефакт уже
|
||||||
|
написан. Блок `context` проект
|
||||||
|
дополняет своим (стек, разведка, особенности домена), но **адреса паспорта и
|
||||||
|
`CLAUDE.md` обязательны** — отсутствие адреса к существующему документу
|
||||||
|
`openspec.py check` называет отказом.
|
||||||
|
|
||||||
|
**Ключи под `rules:` — имена артефактов схемы**, а не свободные слова:
|
||||||
|
`proposal`, `specs`, `design`, `tasks`. Правило под чужим именем не применяется
|
||||||
|
и об этом не сообщает, поэтому `rules.spec` вместо `rules.specs` даёт конфиг,
|
||||||
|
выглядящий написанным и не работающий; `openspec.py check` такой ключ называет.
|
||||||
|
Перечень артефактов задаёт OpenSpec, а не мы, — за его актуальностью следит
|
||||||
|
`openspec.py form`.
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Форма `openspec/config.yaml`: проверка проекта и сверка слепка с инструментом.
|
||||||
|
|
||||||
|
Каталог `openspec/` — предпосылка **конвейера**, а не канона документов: без него
|
||||||
|
не работают ни `opsx:propose`, ни ревью дизайна, ни сверка требований. Поэтому и
|
||||||
|
проверка формы живёт здесь, рядом со скиллом, который каталог заводит. Раньше она
|
||||||
|
жила в `docs.py` плагина канона, и у файла было два владельца: один заводит,
|
||||||
|
другой проверяет.
|
||||||
|
|
||||||
|
Проверяется то, что **молчит при поломке**. Файл из коробки хуже отсутствующего:
|
||||||
|
`openspec init` кладёт `config.yaml`, где `context` и `rules` — закомментированный
|
||||||
|
пример на английском. Он есть, он валиден, имя правильное — и читается как
|
||||||
|
настроенный, работая как пустой. Узнаётся это по уже написанному предложению.
|
||||||
|
|
||||||
|
Разбираем текстом, а не YAML-парсером: у скриптов ноль внешних зависимостей, а
|
||||||
|
PyYAML в стандартной библиотеке нет. Всё проверяемое различимо построчно,
|
||||||
|
комментарии отброшены, ключи верхнего уровня стоят в первой колонке.
|
||||||
|
|
||||||
|
Коды выхода — общий словарь скриптов av-dev:
|
||||||
|
0 сошлось
|
||||||
|
1 дрейф: форма разошлась с ожидаемой
|
||||||
|
2 ошибка употребления: аргументы
|
||||||
|
3 окружение: не тот каталог, инструмент не отвечает
|
||||||
|
4 внутренний сбой
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import NoReturn
|
||||||
|
|
||||||
|
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||||
|
|
||||||
|
# Команда заведения. Названа поимённо потому, что её печатает отказ, а отказ без
|
||||||
|
# команды заставляет искать её в другом месте.
|
||||||
|
OPENSPEC_INIT = "openspec init --tools claude"
|
||||||
|
|
||||||
|
# Адреса, которые обязан назвать блок context. Не пересказ документов, а именно
|
||||||
|
# ссылки: предложение пишется до того, как кто-либо откроет docs/, и без этих
|
||||||
|
# двух строк его пишут, не зная ни границы домена, ни инвариантов. Список
|
||||||
|
# короткий намеренно — длинный превращает context во второй дом фактов.
|
||||||
|
#
|
||||||
|
# Третий элемент — путь, по которому проверяется, есть ли документ в проекте
|
||||||
|
# вообще. Канон документов ставится отдельным плагином и может быть не подключён;
|
||||||
|
# требовать ссылку на файл, которого нет, значит требовать битую ссылку.
|
||||||
|
OPENSPEC_POINTERS = [
|
||||||
|
("passport", "docs/passport.md",
|
||||||
|
"граница домена и «чем НЕ является» останутся непрочитанными"),
|
||||||
|
("CLAUDE.md", "CLAUDE.md",
|
||||||
|
"инварианты и семантика гейта останутся непрочитанными"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# --- Слепок чужого инструмента ----------------------------------------------
|
||||||
|
#
|
||||||
|
# Схема, перечень артефактов и версия, на которой это проверено, живут в OpenSpec
|
||||||
|
# и меняются без нашего участия; здесь они записаны, чтобы проверка шла без
|
||||||
|
# запуска node на каждом прогоне.
|
||||||
|
#
|
||||||
|
# Слепок стареет, и потому есть кто это замечает: `check` сравнивает major.minor
|
||||||
|
# установленного OpenSpec с OPENSPEC_CHECKED и, если они разошлись, говорит
|
||||||
|
# замечанием «форма не перепроверена». Перепроверяет команда `form` — она
|
||||||
|
# спрашивает сам инструмент и печатает, что разошлось. Патч-версия сравнением
|
||||||
|
# намеренно не берётся: форма конфига в ней не меняется, а замечание на каждый
|
||||||
|
# багфикс приучило бы пролистывать весь блок.
|
||||||
|
OPENSPEC_CHECKED = "1.5"
|
||||||
|
OPENSPEC_SCHEMA = "spec-driven"
|
||||||
|
|
||||||
|
# Артефакты схемы. Ключ `rules:` адресуется артефакту, и адресованный
|
||||||
|
# несуществующему **молча не действует** — ровно тот класс, ради которого вся
|
||||||
|
# проверка и заведена.
|
||||||
|
OPENSPEC_ARTIFACTS = ("proposal", "specs", "design", "tasks")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Report:
|
||||||
|
errors: list[str] = field(default_factory=list)
|
||||||
|
notes: list[str] = field(default_factory=list)
|
||||||
|
skipped: list[str] = field(default_factory=list)
|
||||||
|
|
||||||
|
def error(self, msg: str) -> None:
|
||||||
|
self.errors.append(msg)
|
||||||
|
|
||||||
|
def note(self, msg: str) -> None:
|
||||||
|
self.notes.append(msg)
|
||||||
|
|
||||||
|
def skip(self, msg: str) -> None:
|
||||||
|
self.skipped.append(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def fail(code: int, msg: str) -> NoReturn:
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
sys.exit(code)
|
||||||
|
|
||||||
|
|
||||||
|
def openspec_cli(args: list[str]) -> str | None:
|
||||||
|
"""Спросить сам инструмент. None — его нет или он не ответил."""
|
||||||
|
try:
|
||||||
|
out = subprocess.run(
|
||||||
|
["openspec", *args], capture_output=True, text=True, timeout=30
|
||||||
|
)
|
||||||
|
except (FileNotFoundError, OSError, subprocess.SubprocessError):
|
||||||
|
return None
|
||||||
|
return out.stdout.strip() if out.returncode == 0 else None
|
||||||
|
|
||||||
|
|
||||||
|
def rules_keys(live: str) -> list[str]:
|
||||||
|
"""Имена артефактов, которым адресованы правила, — и только они.
|
||||||
|
|
||||||
|
Идём от строки `rules:` до следующего ключа нулевой колонки, а не ищем
|
||||||
|
отступ по всему файлу: блок `context: |` — литеральный скаляр, внутри него
|
||||||
|
строки вида «Language: Russian» и «av-dev-code:review» выглядят
|
||||||
|
ключами и дали бы находку на ровном месте. Проверено на живом конфиге,
|
||||||
|
который так и падал.
|
||||||
|
"""
|
||||||
|
out: list[str] = []
|
||||||
|
inside = False
|
||||||
|
for line in live.splitlines():
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
if not line[0].isspace():
|
||||||
|
inside = line.startswith("rules:")
|
||||||
|
continue
|
||||||
|
if not inside:
|
||||||
|
continue
|
||||||
|
m = re.fullmatch(r" ([A-Za-z_-]+):\s*", line)
|
||||||
|
if m:
|
||||||
|
out.append(m.group(1))
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def rules_block(live: str, name: str) -> str:
|
||||||
|
"""Строки правил, адресованных одному артефакту.
|
||||||
|
|
||||||
|
Обход тот же, что у `rules_keys`, и по той же причине: искать по всему файлу
|
||||||
|
нельзя. Литеральный скаляр `context` называет `SHALL` уже в образце, поэтому
|
||||||
|
проверка «правила называют SHALL» грепом по файлу проходила при **пустом**
|
||||||
|
`rules.specs` — то есть молчала ровно в том случае, ради которого написана.
|
||||||
|
"""
|
||||||
|
out: list[str] = []
|
||||||
|
in_rules = False
|
||||||
|
in_name = False
|
||||||
|
for line in live.splitlines():
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
if not line[0].isspace():
|
||||||
|
in_rules = line.startswith("rules:")
|
||||||
|
in_name = False
|
||||||
|
continue
|
||||||
|
if not in_rules:
|
||||||
|
continue
|
||||||
|
m = re.fullmatch(r" ([A-Za-z_-]+):\s*", line)
|
||||||
|
if m:
|
||||||
|
in_name = m.group(1) == name
|
||||||
|
continue
|
||||||
|
if in_name:
|
||||||
|
out.append(line)
|
||||||
|
return "\n".join(out)
|
||||||
|
|
||||||
|
|
||||||
|
def check_form(root: Path, rep: Report) -> None:
|
||||||
|
"""Настройка заведена и не осталась примером из коробки."""
|
||||||
|
os_dir = root / "openspec"
|
||||||
|
if not os_dir.is_dir():
|
||||||
|
# Здесь это отказ, а не «неприменимо»: скрипт принадлежит конвейеру, а
|
||||||
|
# конвейер без OpenSpec не работает вовсе. Тот же вопрос со стороны
|
||||||
|
# канона документов звучит иначе, и `docs.py` отвечает на него молчанием.
|
||||||
|
rep.error(
|
||||||
|
"нет openspec/ — там дом темы requirements (openspec/specs/) и "
|
||||||
|
f"настройка генерации артефактов; заводится `{OPENSPEC_INIT}`"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (os_dir / "config.yml").is_file():
|
||||||
|
rep.error(
|
||||||
|
"openspec/config.yml — читается только config.yaml, и этот файл "
|
||||||
|
"останется незамеченным: настройка будет пустой, а выглядеть будет "
|
||||||
|
"заполненной"
|
||||||
|
)
|
||||||
|
|
||||||
|
path = os_dir / "config.yaml"
|
||||||
|
if not path.is_file():
|
||||||
|
rep.error(
|
||||||
|
"нет openspec/config.yaml — язык, правила именования capability и "
|
||||||
|
"придирки валидатора будут заново угадываться на каждом предложении"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
text = path.read_text(encoding="utf-8")
|
||||||
|
live = "\n".join(
|
||||||
|
line for line in text.splitlines() if not line.lstrip().startswith("#")
|
||||||
|
)
|
||||||
|
keys = set(re.findall(r"(?m)^([A-Za-z_]+):", live))
|
||||||
|
|
||||||
|
schema = re.search(r"(?m)^schema:\s*(\S+)", live)
|
||||||
|
if schema is None:
|
||||||
|
rep.error(
|
||||||
|
f"в openspec/config.yaml нет ключа schema — ожидается {OPENSPEC_SCHEMA}"
|
||||||
|
)
|
||||||
|
elif schema.group(1) != OPENSPEC_SCHEMA:
|
||||||
|
rep.error(
|
||||||
|
f"schema в openspec/config.yaml — {schema.group(1)}, а форма описана "
|
||||||
|
f"для {OPENSPEC_SCHEMA}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if "context" not in keys:
|
||||||
|
rep.error(
|
||||||
|
"в openspec/config.yaml нет ключа context: файл остался примером из "
|
||||||
|
"коробки — предложение пишется без языка, правил именования "
|
||||||
|
"capability и адресов документов проекта"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
for pointer, where, why in OPENSPEC_POINTERS:
|
||||||
|
if not (root / where).exists():
|
||||||
|
rep.skip(
|
||||||
|
f"{where} в проекте нет — ссылка на него в context не "
|
||||||
|
f"требуется. Документы канона ведёт отдельный плагин "
|
||||||
|
f"(av-dev-docs), и без него конвейер работает вслепую"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
if pointer not in live:
|
||||||
|
rep.error(f"openspec/config.yaml не называет {pointer} — {why}")
|
||||||
|
|
||||||
|
if "rules" not in keys or "specs" not in rules_keys(live):
|
||||||
|
rep.error(
|
||||||
|
"в openspec/config.yaml нет rules.specs — придирки валидатора "
|
||||||
|
"нигде не записаны, и каждое предложение узнаёт их отказом"
|
||||||
|
)
|
||||||
|
elif "SHALL" not in rules_block(live, "specs"):
|
||||||
|
rep.error(
|
||||||
|
"rules.specs в openspec/config.yaml не называет SHALL — "
|
||||||
|
"требование без этого литерала валидатор отвергает, а правило "
|
||||||
|
"проекта об этом молчит"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Ключ под rules: — имя артефакта схемы. Опечатка или устаревшее имя не
|
||||||
|
# ломает ничего видимого: правила просто не применяются, а конфиг выглядит
|
||||||
|
# написанным.
|
||||||
|
for name in rules_keys(live):
|
||||||
|
if name not in OPENSPEC_ARTIFACTS:
|
||||||
|
rep.error(
|
||||||
|
f"rules.{name} в openspec/config.yaml — такого артефакта у схемы "
|
||||||
|
f"{OPENSPEC_SCHEMA} нет ({', '.join(OPENSPEC_ARTIFACTS)}): правила "
|
||||||
|
f"под ним не применяются и молчат об этом"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_fresh(rep: Report) -> None:
|
||||||
|
"""Не устарел ли слепок формы.
|
||||||
|
|
||||||
|
Стоит один запуск `openspec --version` — десятые доли секунды. Перечень
|
||||||
|
артефактов и имя схемы отсюда не спрашиваются намеренно: они стоят втрое
|
||||||
|
дороже, а меняются только вместе с версией, и потому за ними ходит команда
|
||||||
|
`form`, а эта проверка говорит, когда её звать.
|
||||||
|
"""
|
||||||
|
got = openspec_cli(["--version"])
|
||||||
|
if got is None:
|
||||||
|
rep.skip(
|
||||||
|
"openspec не отвечает (нет на PATH?) — актуальность формы "
|
||||||
|
"config.yaml не проверялась"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
installed = ".".join(got.split(".")[:2])
|
||||||
|
if installed != OPENSPEC_CHECKED:
|
||||||
|
rep.note(
|
||||||
|
f"форма openspec/config.yaml сверена с OpenSpec {OPENSPEC_CHECKED}, "
|
||||||
|
f"установлен {got}: перепроверить — `openspec.py form`. Пока не "
|
||||||
|
f"перепроверено, проверки формы судят по прежней схеме"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def report(rep: Report) -> int:
|
||||||
|
for msg in rep.errors:
|
||||||
|
print(f"ДРЕЙФ {msg}")
|
||||||
|
for msg in rep.notes:
|
||||||
|
print(f"ЗАМЕЧАНИЕ {msg}")
|
||||||
|
if rep.skipped:
|
||||||
|
print("\nНЕ ПРОВЕРЯЛОСЬ:")
|
||||||
|
for msg in rep.skipped:
|
||||||
|
print(f" {msg}")
|
||||||
|
|
||||||
|
print(
|
||||||
|
"\nМашина проверила форму: имя файла, схему, незаменённый пример, адреса\n"
|
||||||
|
"документов проекта и ключи rules против артефактов схемы. Чего она не\n"
|
||||||
|
"видит — **пересказ вместо ссылки**: утверждение, которое можно\n"
|
||||||
|
"опровергнуть, открыв другой файл проекта, от строки «открой такой-то\n"
|
||||||
|
"файл» она не отличает. Это суждение агента `doc-consistency` из плагина\n"
|
||||||
|
"канона документов; нет плагина — нет и этой проверки, и так и скажи."
|
||||||
|
)
|
||||||
|
if rep.errors:
|
||||||
|
print(f"\nИтог: дрейф, {len(rep.errors)} пунктов.")
|
||||||
|
return DRIFT
|
||||||
|
print("\nИтог: форма сошлась в механизируемой части.")
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_check(args: argparse.Namespace) -> int:
|
||||||
|
root = Path(args.dir).resolve()
|
||||||
|
if not root.is_dir():
|
||||||
|
fail(ENV, f"нет каталога {root}")
|
||||||
|
rep = Report()
|
||||||
|
check_form(root, rep)
|
||||||
|
check_fresh(rep)
|
||||||
|
return report(rep)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_form(args: argparse.Namespace) -> int:
|
||||||
|
"""Перепроверить слепок формы по живому OpenSpec.
|
||||||
|
|
||||||
|
Ничего не правит и не трогает проект: спрашивает инструмент и печатает, что
|
||||||
|
разошлось с константами скрипта. Чинит человек — правкой констант, образца в
|
||||||
|
references/config-skeleton.md и записью в журнал версий канона, если форма
|
||||||
|
действительно поменялась.
|
||||||
|
"""
|
||||||
|
version = openspec_cli(["--version"])
|
||||||
|
if version is None:
|
||||||
|
fail(
|
||||||
|
ENV,
|
||||||
|
"openspec не отвечает: поставь его или проверь PATH — "
|
||||||
|
"перепроверять форму нечем",
|
||||||
|
)
|
||||||
|
raw = openspec_cli(["templates", "--json"])
|
||||||
|
if raw is None:
|
||||||
|
fail(ENV, "`openspec templates --json` не отработал — схему не спросить")
|
||||||
|
try:
|
||||||
|
artifacts = tuple(json.loads(raw))
|
||||||
|
except json.JSONDecodeError as exc:
|
||||||
|
fail(ENV, f"`openspec templates --json` отдал неразбираемое: {exc}")
|
||||||
|
|
||||||
|
print(f"OpenSpec установлен: {version}")
|
||||||
|
print(f"форма сверена с: {OPENSPEC_CHECKED}")
|
||||||
|
print(f"артефакты схемы: {', '.join(artifacts)}")
|
||||||
|
print(f"записано в скрипте: {', '.join(OPENSPEC_ARTIFACTS)}")
|
||||||
|
|
||||||
|
diffs: list[str] = []
|
||||||
|
if ".".join(version.split(".")[:2]) != OPENSPEC_CHECKED:
|
||||||
|
diffs.append(
|
||||||
|
f"версия: поднять OPENSPEC_CHECKED до "
|
||||||
|
f"{'.'.join(version.split('.')[:2])} — но только после того, как "
|
||||||
|
f"остальные строки этого отчёта сойдутся"
|
||||||
|
)
|
||||||
|
for name in artifacts:
|
||||||
|
if name not in OPENSPEC_ARTIFACTS:
|
||||||
|
diffs.append(
|
||||||
|
f"новый артефакт {name}: решить, нужны ли ему правила в rules, "
|
||||||
|
f"и добавить имя в OPENSPEC_ARTIFACTS"
|
||||||
|
)
|
||||||
|
for name in OPENSPEC_ARTIFACTS:
|
||||||
|
if name not in artifacts:
|
||||||
|
diffs.append(
|
||||||
|
f"артефакта {name} у схемы больше нет: правила под ним в конфигах "
|
||||||
|
f"проектов молчат — убрать из OPENSPEC_ARTIFACTS, из образца и "
|
||||||
|
f"записать в журнал версий канона"
|
||||||
|
)
|
||||||
|
|
||||||
|
print()
|
||||||
|
if not diffs:
|
||||||
|
print("Слепок сходится. Осталось глазами: не изменились ли придирки")
|
||||||
|
print("валидатора — их скрипт проверить не может, они проявляются только")
|
||||||
|
print("отказом `openspec validate --strict` на живой спеке.")
|
||||||
|
return OK
|
||||||
|
print("Разошлось:")
|
||||||
|
for line in diffs:
|
||||||
|
print(f" - {line}")
|
||||||
|
print()
|
||||||
|
print("Правится в трёх местах сразу: константы этого скрипта, образец")
|
||||||
|
print("`references/config-skeleton.md` и запись в журнал версий канона —")
|
||||||
|
print("иначе проекты останутся на прежней форме молча.")
|
||||||
|
return DRIFT
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="openspec.py",
|
||||||
|
description="форма openspec/config.yaml: проверка проекта и сверка слепка",
|
||||||
|
)
|
||||||
|
sub = parser.add_subparsers(dest="cmd", required=True)
|
||||||
|
|
||||||
|
p_check = sub.add_parser("check", help="форма config.yaml в проекте")
|
||||||
|
p_check.add_argument("--dir", default=".", help="корень проекта")
|
||||||
|
p_check.set_defaults(func=cmd_check)
|
||||||
|
|
||||||
|
p_form = sub.add_parser(
|
||||||
|
"form", help="перепроверить слепок формы по живому OpenSpec"
|
||||||
|
)
|
||||||
|
p_form.set_defaults(func=cmd_form)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
try:
|
||||||
|
return args.func(args)
|
||||||
|
except SystemExit:
|
||||||
|
raise
|
||||||
|
except Exception as exc: # noqa: BLE001 — последний рубеж, код 4 по словарю
|
||||||
|
print(f"ВНУТРЕННИЙ СБОЙ: {exc}", file=sys.stderr)
|
||||||
|
return INTERNAL
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,655 @@
|
|||||||
|
---
|
||||||
|
name: resolve
|
||||||
|
description: "Решить одну задачу от постановки до закрытия. На входе путь к файлу задачи, её слаг или просто текст. Обычная задача идёт циклом Spec Driven Development: opsx propose → разметка → ревью дизайна → чекпоинт с объяснением человеческим языком → opsx apply → ревью кода → archive → синк документации → коммит → закрытие. Исследовательская (тип research, сырая идея, мутная постановка) начинается раньше: opsx explore и чекпоинт вариантов — два-четыре способа решить, с ценой каждого и рекомендацией; выбор оседает по адресу, который назвала сама задача. Между чекпоинтами работа идёт без согласований. Использовать, когда просят взять, сделать или решить задачу, довести идею до реализации, разобраться с записью из беклога."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Решение одной задачи
|
||||||
|
|
||||||
|
Проводит **одну** задачу от постановки до закрытия. Между плановыми
|
||||||
|
остановками — без согласований: механику не обсуждаем, делаем.
|
||||||
|
|
||||||
|
Тонкая обёртка над каноническими скиллами `opsx:explore` / `opsx:propose` /
|
||||||
|
`opsx:apply` / `opsx:archive` — зови их через Skill, не переизобретай их шаги.
|
||||||
|
Ревью — скилл `av-dev-code:review`; он же держит правило выбора
|
||||||
|
метки, а называет её агент `review-scope` — один раз на задачу, для обеих стадий
|
||||||
|
ревью.
|
||||||
|
|
||||||
|
## Предпосылки
|
||||||
|
|
||||||
|
- **OpenSpec и скиллы `opsx:*` — жёсткая предпосылка, а не опция.** На них стоят
|
||||||
|
шаги 2, 6 и 8, шаг Р2 разведки, проход `review-specs` и ревью дизайна (они
|
||||||
|
завязаны на `openspec/changes/<id>/specs/*/spec.md` и на
|
||||||
|
`openspec validate --strict`). **Проект без OpenSpec этим скиллом не ведётся** —
|
||||||
|
подключай OpenSpec, а не вырождай цикл; почему ветка деградации здесь не
|
||||||
|
пишется, сказано в `av-dev-code:review`, раздел «Предпосылки», и дом у этого
|
||||||
|
довода там. Заводить руками не надо: каталог и настройку в `config.yaml`
|
||||||
|
делает скилл `av-dev-code:openspec`.
|
||||||
|
- **Проектные копии этих скиллов и агентов удаляются при установке плагина**
|
||||||
|
(`.claude/skills/` — голые имена `resolve`, `review`, а у проектов прошлого
|
||||||
|
поколения ещё `task-pipeline`, `review-pipeline`, `task-batch`, и с префиксом
|
||||||
|
проекта: `<проект>-task-pipeline`, `<проект>-review-pipeline`;
|
||||||
|
`.claude/agents/<проект>-review-*.md`). Две копии одного скилла расходятся, и
|
||||||
|
побеждает та, что короче названа.
|
||||||
|
|
||||||
|
### Обращение к соседним плагинам
|
||||||
|
|
||||||
|
**Копия.** Дом — `shared/plugin-boundary.md` в репозитории плагинов: правило
|
||||||
|
общее для всех, кто зовёт чужое, и ни один плагин им не владеет. Правится дом, а
|
||||||
|
не этот файл.
|
||||||
|
|
||||||
|
<!-- копия: граница-плагинов из shared/plugin-boundary.md -->
|
||||||
|
|
||||||
|
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
|
||||||
|
месте.
|
||||||
|
|
||||||
|
**Чужой скилл зовётся полным именем** — `av-dev-docs:canon`, `av-dev-tasks:tasks`,
|
||||||
|
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
|
||||||
|
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
|
||||||
|
|
||||||
|
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
|
||||||
|
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
|
||||||
|
прочитает его сам.
|
||||||
|
|
||||||
|
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
|
||||||
|
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
|
||||||
|
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
|
||||||
|
|
||||||
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||||
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
|
||||||
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
Скилл зовёт `av-dev-code:review`, `av-dev-docs:docs` и
|
||||||
|
`av-dev-tasks:tasks`. Чем оборачивается отсутствие каждого — на самих шагах и в
|
||||||
|
разделе «Границы».
|
||||||
|
|
||||||
|
Перед стартом прочитай `CLAUDE.md` проекта и то, на что он ссылается, если ещё
|
||||||
|
не в контексте. Проектные факты, нужные ревью — инварианты, семантика гейта,
|
||||||
|
объёмы, модель угроз, прецеденты, — живут в **документах канона** `av-dev-docs`;
|
||||||
|
карта «что где» — `references/project-facts.md` конвейера ревью.
|
||||||
|
|
||||||
|
**Документов канона нет — проект к нему не приведён.** Скажи это строкой и
|
||||||
|
предложи скилл `av-dev-docs:canon`: одна операция на проект против поразрядной
|
||||||
|
деградации на каждой задаче. Работу при этом не останавливай.
|
||||||
|
|
||||||
|
## Вход
|
||||||
|
|
||||||
|
Задача задаётся **путём к файлу, именем файла, слагом или просто текстом**.
|
||||||
|
Ничего из этого не задано — попроси у вызывающего и остановись; сам в беклог не
|
||||||
|
лезь и приоритеты не интерпретируй: что делать дальше, решает не этот скилл.
|
||||||
|
|
||||||
|
**Запись из каталога сперва проверяется на готовность, и проверяет её машина.**
|
||||||
|
Вызови Skill `av-dev-tasks:tasks` и попроси прогнать `ready <слаг>`: он смотрит
|
||||||
|
тип, цель у `feature`, пустой ли раздел вопросов и собраны ли разделы схемы
|
||||||
|
типа. Судить это глазами нельзя — ровно тот случай, где машина дешевле и точнее,
|
||||||
|
а цена ошибки отложенная: недостающие критерии приёмки обнаружатся на приёмке,
|
||||||
|
когда сверять уже не с чем.
|
||||||
|
|
||||||
|
**`ready` отказал — это исход, а не препятствие для тебя.** Скажи, чего не
|
||||||
|
хватает, и остановись: дописывать чужую запись за автора не твоя работа, а у
|
||||||
|
сырья (`research` без раздела «Вопрос») и дописывать нечего — там сперва нужен
|
||||||
|
вопрос. Исход — «не доведена», с названной причиной.
|
||||||
|
|
||||||
|
Плагина `av-dev-tasks` в проекте нет или задача пришла текстом — прогонять
|
||||||
|
нечего. Тогда прочитай постановку сам и скажи строкой, что готовность машиной не
|
||||||
|
проверялась; работу при этом не останавливай.
|
||||||
|
|
||||||
|
## Две ветки
|
||||||
|
|
||||||
|
Развилка одна и стоит на входе:
|
||||||
|
|
||||||
|
- **обычная задача** — что делать, понятно; спорно только как. Идёт с шага 1;
|
||||||
|
- **исследовательская** — тип `research`, сырая идея, новое и незнакомое, мутная
|
||||||
|
постановка. Идёт с шага Р1, и там её ждёт **свой** чекпоинт: варианты решения
|
||||||
|
обсуждаются **до** того, как написано первое требование.
|
||||||
|
|
||||||
|
Признак не в объёме работы, а в том, **есть ли у задачи один очевидный способ
|
||||||
|
решения**. Его нет — обсуждать варианты после `propose` поздно: предложение уже
|
||||||
|
воплотило один из них, и разговор пойдёт не о выборе, а о переделке.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
in["вход: файл, слаг или текст"]
|
||||||
|
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/>вторым коммитом учёта"]
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Схема — **сводка**: содержание каждого шага в его разделе ниже, и при
|
||||||
|
расхождении прав текст.
|
||||||
|
|
||||||
|
## Автономность и два плановых стопа
|
||||||
|
|
||||||
|
**Между чекпоинтами умолчание прежнее — делать, а не спрашивать.** Чекпоинты не
|
||||||
|
отменяют автономность, они дают развилкам плановое место, куда копиться.
|
||||||
|
|
||||||
|
Разрез простой:
|
||||||
|
|
||||||
|
- развилка найдена **до** ближайшего чекпоинта — она его и ждёт. Не спрашивай
|
||||||
|
отдельно: чекпоинт рядом и стоит дёшево, а три вопроса подряд стоят дороже
|
||||||
|
одного разговора;
|
||||||
|
- развилка найдена **после** последнего чекпоинта — старое правило: **запиши
|
||||||
|
вопрос и доведи остаток**, не останавливаясь.
|
||||||
|
|
||||||
|
Запись вопроса устроена так:
|
||||||
|
|
||||||
|
1. **Запиши там, где проект держит вопросы** (секция беклога, файл задачи,
|
||||||
|
трекер — это знает проект). Проект не сказал, куда, — отдельной секцией
|
||||||
|
`Вопросы` в своём докладе, и это тоже исход. Тело отвечает на три вещи: **что
|
||||||
|
именно решить**, **какие есть варианты и цена каждого**, **что стоит, пока
|
||||||
|
решения нет**. Плюс твоя рекомендация — человек чаще соглашается, чем выбирает
|
||||||
|
заново, и готовое суждение экономит ему весь контекст.
|
||||||
|
2. **Переформулируй задачу на остаток** — то, что делается без этого решения.
|
||||||
|
Назови границу: докуда доводим сейчас.
|
||||||
|
3. Доведи остаток до конца и закоммить. Задача не «висит на вопросе», она сделана
|
||||||
|
в объявленных границах.
|
||||||
|
|
||||||
|
**Что остатком не является — правило живёт не здесь.** Канонический текст с обеими
|
||||||
|
оговорками — в плагине `av-dev-tasks`, скилл `av-dev-tasks:groom`, раздел
|
||||||
|
`## Вопрос, блокер, необратимое`, подраздел «Отличать вопрос от застревания».
|
||||||
|
Правило принадлежит управлению задачами, потому что решает **сделана задача или
|
||||||
|
вышла**, — это исход планирования, а не исполнения. **Ссылайся, не
|
||||||
|
пересказывай:** копия, заведённая здесь, уже однажды разошлась с оригиналом и
|
||||||
|
потеряла из перечня самое необратимое — запись **наружу**.
|
||||||
|
|
||||||
|
Коротко, чтобы знать, когда идти читать: остаток проверяется двумя порогами —
|
||||||
|
**материализация нерешённого** (запись состояния, зависящего от неотвеченного
|
||||||
|
вопроса) и **пол по пользе** (из остатка пропала польза, названная в постановке).
|
||||||
|
Оба порога — стоп: первый поднимает решение до начала записи, второй даёт исход
|
||||||
|
«не доведена».
|
||||||
|
|
||||||
|
Плагин `av-dev-tasks` не подключён — правило не отменяется, а становится
|
||||||
|
осторожнее: прежде чем записать зависящее от нерешённого куда бы то ни было —
|
||||||
|
в хранилище, в журнал, в витрину или наружу, — спрашивай человека.
|
||||||
|
|
||||||
|
Нет полезного остатка — задача заканчивается исходом «не доведена», вопрос
|
||||||
|
записан, ничего не коммитится наполовину.
|
||||||
|
|
||||||
|
### Когда спрашивать вне чекпоинтов
|
||||||
|
|
||||||
|
По другому основанию — не «сложное решение», а **необратимое действие**:
|
||||||
|
|
||||||
|
- деплой, выкладка наружу, смена публичного адреса или токенов;
|
||||||
|
- удаление или перезапись рабочих данных, включая подрезку архивов;
|
||||||
|
- всё, что уходит за пределы машины.
|
||||||
|
|
||||||
|
Здесь ошибка не откатывается коммитом, поэтому спрашиваем даже когда решение
|
||||||
|
кажется очевидным.
|
||||||
|
|
||||||
|
Стиль правок — заточка под проект и конвенции, right-size, без золочения.
|
||||||
|
|
||||||
|
## Границы: чем этот скилл не владеет
|
||||||
|
|
||||||
|
- **Беклогом, целями и приоритетами.** Задача приходит извне. Скилл её не
|
||||||
|
выбирает, не приоритизирует, не заводит и не переоценивает.
|
||||||
|
- **Форматом задач.** Индексы руками не правятся, путь к скрипту учёта не
|
||||||
|
выдумывается: этим владеет `av-dev-tasks:tasks` (шаг 11). Закрытие — работа
|
||||||
|
этого скилла, и это осознанное решение с названной ценой: **приёмщик и
|
||||||
|
исполнитель совпали**. Закрытие поэтому **не окончательно** — человек на
|
||||||
|
груминге (`av-dev-tasks:groom`) возвращает задачу `reopen` с причиной, а доклад
|
||||||
|
по критериям приёмки становится единственным, по чему приёмка вообще возможна.
|
||||||
|
- **Заведением задач из урожая ревью.** Отложенные находки отдаются **списком**;
|
||||||
|
превращать их в задачи — работа `av-dev-tasks:tasks`, у него на этот вход
|
||||||
|
отдельный сценарий «задачи из ревью и аудита». Плагина нет — урожай остаётся
|
||||||
|
списком в докладе, и это говорится строкой.
|
||||||
|
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос этого
|
||||||
|
скилла ни на одном шаге. Чекпоинт спрашивает «так ли решаем», а не «надо ли».
|
||||||
|
|
||||||
|
## Наблюдаемые исходы
|
||||||
|
|
||||||
|
Ровно четыре, и каждый обязан быть назван в докладе прямо:
|
||||||
|
|
||||||
|
- **сделана** — определение сделанного выполнено целиком;
|
||||||
|
- **не доведена** — с причиной и с записанным вопросом; что именно сделано и до
|
||||||
|
какой границы, названо явно. Сюда же попадает чекпоинт, на котором человек
|
||||||
|
решение не одобрил;
|
||||||
|
- **оказалась крупнее задачи** — распознаётся **до заведения 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>`. Это второй коммит осознанно: правило «одна задача
|
||||||
|
— один осмысленный коммит» про работу, а учёт — не работа.
|
||||||
|
|
||||||
|
Разведка, кончившаяся знанием, закрывается так же — но перед этим убедись, что
|
||||||
|
ответ **записан по названному адресу и закоммичен**. Закрытая разведка без
|
||||||
|
записанного ответа не оставляет следа вообще: файл задачи удалён, ответ был в
|
||||||
|
переписке.
|
||||||
|
|
||||||
|
Плагина в проекте нет — вызов не разрешится. Тогда **ничего не выдумывай**: скажи
|
||||||
|
в докладе, что учёт задач остаётся за владельцем, и назови исход.
|
||||||
|
|
||||||
|
## Доклад
|
||||||
|
|
||||||
|
Коротко, и в нём обязательно:
|
||||||
|
|
||||||
|
- **исход** одним из четырёх слов и, если не «сделана», чем ограничен результат;
|
||||||
|
- что сделано, какие вопросы записаны и куда;
|
||||||
|
- **что было одобрено на чекпоинте и совпало ли с тем, что уехало в коммит** —
|
||||||
|
расхождение здесь называется прямо, даже если оно мелкое;
|
||||||
|
- ссылка на архивный change и хеш коммита;
|
||||||
|
- по каждому критерию приёмки, если они были: **оракул и наблюдаемый исход** —
|
||||||
|
это доклад приёмщику, а не отметка «принято»;
|
||||||
|
- **`Урожай`** — отложенные находки списком (формулировка, оракул, провенанс);
|
||||||
|
- **одна строка границ покрытия**: какая метка и режим гонялись, какие проходы не
|
||||||
|
запускались и что проверить было невозможно. Доклад без неё сообщает
|
||||||
|
«проверено», не сообщая, что именно.
|
||||||
|
|
||||||
|
## Тонкости
|
||||||
|
|
||||||
|
- **Не завязывайся на основную ветку и корень репозитория.** Скилл работает в
|
||||||
|
текущем worktree и на текущей ветке: не делай `git checkout`/`switch`, не
|
||||||
|
создавай веток, не пушь.
|
||||||
|
- Обычная задача с очевидным решением проходит **один** чекпоинт, и это норма, а
|
||||||
|
не упрощение. Два чекпоинта — цена незнания, а не признак серьёзности задачи.
|
||||||
|
- Гейт блокирует: пока он красный, опиниативные проходы не запускаются. Чинить и
|
||||||
|
перезапускать, а не «посмотреть заодно».
|
||||||
|
- Держи вызывающего в цикле короткими репликами на переходах фаз, но не проси
|
||||||
|
подтверждать механику: чекпоинты — единственные места, где ждут ответа.
|
||||||
|
- **Занизить метку ревью, пропустить тему или проскочить чекпоинт — самый дешёвый
|
||||||
|
способ «ускориться», и он же самый дорогой по последствиям.** Защита устроена
|
||||||
|
так, что регулятора у тебя нет: метку выбирает разметчик **до того**, как ты
|
||||||
|
написал код, план сверяется по темам, непокрытое называется строкой, а
|
||||||
|
расхождение с одобренным — отдельным пунктом доклада.
|
||||||
+77
-40
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: review-pipeline
|
name: review
|
||||||
description: "Конвейер ревью изменения, устроенный по темам: документ проекта либо заводит тему ревью, либо питает чужую тему источником, либо процессный и в ревью не читается вовсе. Ядро тем — requirements, autotests, conventions, architecture, security, operations; список тем открытый, свои темы проект заводит документом. Разметка задачи идёт один раз, после propose: агент review-scope выводит размер и сложность, из их максимума — метка, и раздаёт темы проходам обеих стадий. Метка правит и ревью дизайна (small — только specs; medium — плюс rubric; large — плюс architecture), и ревью кода (small — гейт, спеки, код, триаж; medium — плюс приёмник тем; large — плюс доказательство: враждебные постановки, эксплуатационный постмортем, архитектурный проход на широком входе). Триаж обязателен всегда. Порядок прогона — граф зависимостей: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, триаж — единственный сток. Проектная специфика приходит из документов канона av-dev-pm. Вызывается из task-pipeline (чекпоинты ревью) и из task-batch (финальная сверка)."
|
description: "Конвейер ревью изменения, устроенный по темам: документ проекта либо заводит тему ревью, либо питает чужую тему источником, либо процессный и в ревью не читается вовсе. Ядро тем — requirements, autotests, conventions, architecture, security, operations; список тем открытый, свои темы проект заводит документом. Разметка задачи идёт один раз, после propose: агент review-scope выводит размер и сложность, из их максимума — метка, и раздаёт темы проходам обеих стадий. Метка правит и ревью дизайна (small — только specs; medium — плюс rubric; large — плюс architecture), и ревью кода (small — гейт, спеки, код, триаж; medium — плюс приёмник тем; large — плюс доказательство: враждебные постановки, эксплуатационный постмортем, архитектурный проход на широком входе). Триаж обязателен всегда. Порядок прогона — граф зависимостей: гейт открывает опиниативные проходы, проходы с пометкой «держит машину» идут цепочкой, триаж — единственный сток. Проектная специфика приходит из документов канона av-dev-docs. Вызывается из скилла resolve — двумя стадиями: ревью дизайна до кода и ревью кода после apply."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Конвейер ревью
|
# Конвейер ревью
|
||||||
@@ -44,7 +44,7 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
|
|
||||||
- **OpenSpec — жёсткая предпосылка, а не опция.** Ревью дизайна, проход
|
- **OpenSpec — жёсткая предпосылка, а не опция.** Ревью дизайна, проход
|
||||||
`review-specs` и
|
`review-specs` и
|
||||||
вызывающий пайплайн задачи завязаны на дельта-спеки
|
вызывающий скилл `av-dev-code:resolve` завязаны на дельта-спеки
|
||||||
(`openspec/changes/<id>/specs/*/spec.md`), на актуальные спеки
|
(`openspec/changes/<id>/specs/*/spec.md`), на актуальные спеки
|
||||||
(`openspec/specs/`) и на `openspec validate --strict`. В проекте без OpenSpec
|
(`openspec/specs/`) и на `openspec validate --strict`. В проекте без OpenSpec
|
||||||
шаги, зовущие `opsx:explore` / `opsx:propose` / `opsx:apply` / `opsx:archive`,
|
шаги, зовущие `opsx:explore` / `opsx:propose` / `opsx:apply` / `opsx:archive`,
|
||||||
@@ -52,17 +52,50 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
требований. **Проект без OpenSpec этим конвейером не проверяется** — подключай
|
требований. **Проект без OpenSpec этим конвейером не проверяется** — подключай
|
||||||
OpenSpec, а не понижай прогон: ветка деградации здесь не пишется, потому что
|
OpenSpec, а не понижай прогон: ветка деградации здесь не пишется, потому что
|
||||||
непроверенная ветка деградации хуже честного отказа. Заводить руками не надо:
|
непроверенная ветка деградации хуже честного отказа. Заводить руками не надо:
|
||||||
`av-dev-pm:init` делает `openspec init` на новом проекте, `canon adopt` — на
|
этим владеет скилл `av-dev-code:openspec` — он заводит каталог и заменяет
|
||||||
переводимом, и оба кладут `openspec/config.yaml` канонической формы.
|
пример в `config.yaml` настройкой. Его же зовут `av-dev-docs:init` на новом
|
||||||
|
проекте и `av-dev-docs:canon` в режиме `adopt` — на переводимом.
|
||||||
- **Документы канона** — см. следующий раздел.
|
- **Документы канона** — см. следующий раздел.
|
||||||
- **Проектные копии этих скиллов и агентов удаляются при установке.** Если в
|
- **Проектные копии этих скиллов и агентов удаляются при установке.** Если в
|
||||||
проекте уже лежат свои `.claude/skills/review-pipeline`,
|
проекте уже лежат свои `.claude/skills/review`,
|
||||||
`.claude/skills/task-pipeline`, `.claude/skills/task-batch` или
|
`.claude/skills/review-pipeline`, `.claude/skills/task-pipeline`,
|
||||||
|
`.claude/skills/task-batch`, `.claude/skills/resolve` или
|
||||||
`.claude/agents/<проект>-review-*.md` — снеси их. Иначе короткое имя разрешится
|
`.claude/agents/<проект>-review-*.md` — снеси их. Иначе короткое имя разрешится
|
||||||
в устаревшую проектную копию, молча и без признаков подмены. По той же причине
|
в устаревшую проектную копию, молча и без признаков подмены.
|
||||||
**скиллы этого плагина зовутся с пространством имён**:
|
|
||||||
`av-dev-pipeline:review-pipeline`, `av-dev-pipeline:task-pipeline`,
|
### Обращение к соседним плагинам
|
||||||
`av-dev-pipeline:task-batch`.
|
|
||||||
|
**Копия.** Дом — `shared/plugin-boundary.md` в репозитории плагинов. Правится
|
||||||
|
дом, а не этот файл.
|
||||||
|
|
||||||
|
<!-- копия: граница-плагинов из shared/plugin-boundary.md -->
|
||||||
|
|
||||||
|
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
|
||||||
|
месте.
|
||||||
|
|
||||||
|
**Чужой скилл зовётся полным именем** — `av-dev-docs:canon`, `av-dev-tasks:tasks`,
|
||||||
|
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
|
||||||
|
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
|
||||||
|
|
||||||
|
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
|
||||||
|
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
|
||||||
|
прочитает его сам.
|
||||||
|
|
||||||
|
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
|
||||||
|
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
|
||||||
|
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
|
||||||
|
|
||||||
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||||
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
|
||||||
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
Своих скиллов это касается ровно так же: `av-dev-code:review`,
|
||||||
|
`av-dev-code:resolve`, `av-dev-code:openspec` — подменяется короткое имя,
|
||||||
|
а не чужое.
|
||||||
|
|
||||||
## Темы, источники и процессные документы
|
## Темы, источники и процессные документы
|
||||||
|
|
||||||
@@ -80,7 +113,7 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **тема** | заводит направление проверки и требует исполнителя | `conventions.*`, `security.*`, `architecture.*`, свои документы проекта |
|
| **тема** | заводит направление проверки и требует исполнителя | `conventions.*`, `security.*`, `architecture.*`, свои документы проекта |
|
||||||
| **источник темы** | читает как материал чужой темы, своей не порождает | `passport.*`, `database.*`, `CLAUDE.md`/`AGENTS.md`, `openspec/specs/` |
|
| **источник темы** | читает как материал чужой темы, своей не порождает | `passport.*`, `database.*`, `CLAUDE.md`/`AGENTS.md`, `openspec/specs/` |
|
||||||
| **процессный документ** | не судит по нему изменение | `docs/tasks/`, `docs/review.*`, `docs/adr.*`, `docs/research.*`, `docs/.pm.json` |
|
| **процессный документ** | не судит по нему изменение | `tasks/`, `docs/review.*`, `docs/adr.*`, `docs/research.*`, `docs/.pm.json` |
|
||||||
|
|
||||||
**Одна процессная запись всё же читается — `docs/review.*`.** В ней лежит
|
**Одна процессная запись всё же читается — `docs/review.*`.** В ней лежит
|
||||||
настройка самого конвейера: вопросы по темам, журнал дефектов, типовые узлы,
|
настройка самого конвейера: вопросы по темам, журнал дефектов, типовые узлы,
|
||||||
@@ -88,8 +121,8 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
критерий, по которому судит изменение. `adr.*`, `research.*` и `tasks/` не
|
критерий, по которому судит изменение. `adr.*`, `research.*` и `tasks/` не
|
||||||
открывает никто.
|
открывает никто.
|
||||||
|
|
||||||
Дом канона этой раскладки — `av-dev-pm`, `references/canon.md`, раздел «Три
|
Дом канона этой раскладки — скилл `av-dev-docs:canon`, раздел «Три категории
|
||||||
категории документов». Конвейер её **читатель**: категории и имена тем он берёт
|
документов». Конвейер её **читатель**: категории и имена тем он берёт
|
||||||
оттуда и своих не заводит.
|
оттуда и своих не заводит.
|
||||||
|
|
||||||
Отсюда то, ради чего правило и заведено: **`docs/` перестаёт быть просто
|
Отсюда то, ради чего правило и заведено: **`docs/` перестаёт быть просто
|
||||||
@@ -120,7 +153,7 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
читал решения, а эксплуатационный и `specs` — числа. Цена решения записана в
|
читал решения, а эксплуатационный и `specs` — числа. Цена решения записана в
|
||||||
каноне и повторяется здесь, потому что платит её конвейер: **расхождение
|
каноне и повторяется здесь, потому что платит её конвейер: **расхождение
|
||||||
изменения с записанным решением прогоном не ловится**, это работа сверки
|
изменения с записанным решением прогоном не ловится**, это работа сверки
|
||||||
документации (`av-dev-pm`, агент `doc-consistency`) на сессии между спринтами.
|
документации — скилл `av-dev-docs:healthcheck`.
|
||||||
Строка об этом обязательна в границах покрытия каждого прогона.
|
Строка об этом обязательна в границах покрытия каждого прогона.
|
||||||
|
|
||||||
**Своя тема проекта бывает двух происхождений, и обе законны:** документ, который
|
**Своя тема проекта бывает двух происхождений, и обе законны:** документ, который
|
||||||
@@ -147,7 +180,7 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
дом для тех же фактов разошёлся бы и выглядел актуальным.
|
дом для тех же фактов разошёлся бы и выглядел актуальным.
|
||||||
|
|
||||||
**Документов канона нет вовсе** — проект не приведён к канону. Скажи это строкой
|
**Документов канона нет вовсе** — проект не приведён к канону. Скажи это строкой
|
||||||
и предложи скилл `av-dev-pm:canon`: одна операция на проект против деградации на
|
и предложи скилл `av-dev-docs:canon`: одна операция на проект против деградации на
|
||||||
каждой задаче. Прогон при этом не останавливается.
|
каждой задаче. Прогон при этом не останавливается.
|
||||||
|
|
||||||
## Что получает каждый проход
|
## Что получает каждый проход
|
||||||
@@ -163,7 +196,7 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
прохода между метками;
|
прохода между метками;
|
||||||
- **контракт находок** — путь к
|
- **контракт находок** — путь к
|
||||||
[references/finding-contract.md](references/finding-contract.md) (в
|
[references/finding-contract.md](references/finding-contract.md) (в
|
||||||
установленном плагине — `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/`);
|
установленном плагине — `${CLAUDE_PLUGIN_ROOT}/skills/review/references/`);
|
||||||
- **изменение** — идентификатор change и путь к его дельта-спекам;
|
- **изменение** — идентификатор change и путь к его дельта-спекам;
|
||||||
- **база диффа**;
|
- **база диффа**;
|
||||||
- **метка, его глубина и режим** прогона — чтобы проход знал, что писать в
|
- **метка, его глубина и режим** прогона — чтобы проход знал, что писать в
|
||||||
@@ -527,8 +560,7 @@ flowchart TD
|
|||||||
ничего не портит, он только дольше, и домысливать тут нечего;
|
ничего не портит, он только дольше, и домысливать тут нечего;
|
||||||
2. **машина занята, и знает об этом вызывающий.** Рядом идёт другая задача,
|
2. **машина занята, и знает об этом вызывающий.** Рядом идёт другая задача,
|
||||||
поднят сервис, гоняется дорогая проверка проекта. Сам конвейер занятости
|
поднят сервис, гоняется дорогая проверка проекта. Сам конвейер занятости
|
||||||
машины не видит — её обязан назвать тот, кто запускает; так и делает
|
машины не видит — её обязан назвать тот, кто запускает;
|
||||||
`av-dev-pipeline:task-batch`, когда ведёт задачи параллельно;
|
|
||||||
3. **разбор самого конвейера** — когда выясняется, почему проход чего-то не
|
3. **разбор самого конвейера** — когда выясняется, почему проход чего-то не
|
||||||
нашёл, порядок и изоляция важнее скорости.
|
нашёл, порядок и изоляция важнее скорости.
|
||||||
|
|
||||||
@@ -592,7 +624,7 @@ flowchart TD
|
|||||||
|
|
||||||
**План живёт в контексте прогона задачи и на диск не пишется.** Файл-план был бы
|
**План живёт в контексте прогона задачи и на диск не пишется.** Файл-план был бы
|
||||||
четвёртым артефактом рядом с `proposal.md`, `tasks.md` и `design.md`, жил бы
|
четвёртым артефактом рядом с `proposal.md`, `tasks.md` и `design.md`, жил бы
|
||||||
дольше задачи и расходился бы с ней молча. Прервали пайплайн — разметка
|
дольше задачи и расходился бы с ней молча. Прервали прогон задачи — разметка
|
||||||
повторяется; это самый дешёвый проход конвейера, и платить за его вечность
|
повторяется; это самый дешёвый проход конвейера, и платить за его вечность
|
||||||
дороже, чем перезапустить.
|
дороже, чем перезапустить.
|
||||||
|
|
||||||
@@ -816,8 +848,8 @@ Recall темы `conventions` равен длине конвенций прое
|
|||||||
|
|
||||||
## Ревью дизайна — до кода
|
## Ревью дизайна — до кода
|
||||||
|
|
||||||
Запускается на первом чекпоинте ревью (шаг 5 скилла
|
Запускается на первой стадии ревью (шаг 4 скилла
|
||||||
`av-dev-pipeline:task-pipeline`), когда change уже имеет `proposal.md` и
|
`av-dev-code:resolve`), когда change уже имеет `proposal.md` и
|
||||||
дельта-спеки, но кода ещё нет. Разметка задачи к этому моменту уже прошла — она
|
дельта-спеки, но кода ещё нет. Разметка задачи к этому моменту уже прошла — она
|
||||||
шагом раньше, и метка известна.
|
шагом раньше, и метка известна.
|
||||||
|
|
||||||
@@ -832,10 +864,11 @@ Recall темы `conventions` равен длине конвенций прое
|
|||||||
| `large` — крупное или незнакомое | `specs`, `rubric`, `architecture` + вопрос автору | **3** |
|
| `large` — крупное или незнакомое | `specs`, `rubric`, `architecture` + вопрос автору | **3** |
|
||||||
|
|
||||||
- **всегда** — `review-specs` в режиме «дизайн ДО кода». Дельта-спеки сверяются
|
- **всегда** — `review-specs` в режиме «дизайн ДО кода». Дельта-спеки сверяются
|
||||||
на каждой задаче: это самый дешёвый чекпоинт конвейера, и он ловит то, что на
|
на каждой задаче: это самый дешёвый проход конвейера, и он ловит то, что на
|
||||||
готовом коде уже не чинят;
|
готовом коде уже не чинят;
|
||||||
- **со `medium`** — `review-rubric` (фаза 1 без фазы 2: рубрика на задуманный
|
- **со `medium`** — `review-rubric`: рубрика на задуманный узел, по ней же
|
||||||
узел становится приёмочными критериями и уезжает в `tasks.md`);
|
разбирается дельта-спека, а сами пункты уезжают приёмочными критериями в
|
||||||
|
`tasks.md`;
|
||||||
- **только в `large`** — `review-architecture` на предложении: можно ли выразить
|
- **только в `large`** — `review-architecture` на предложении: можно ли выразить
|
||||||
существующими понятиями — **включая конструкции стандартной библиотеки**, — не
|
существующими понятиями — **включая конструкции стандартной библиотеки**, — не
|
||||||
появляется ли второй способ. Вопрос «не изобретаем ли то, что уже есть в
|
появляется ли второй способ. Вопрос «не изобретаем ли то, что уже есть в
|
||||||
@@ -853,14 +886,15 @@ Recall темы `conventions` равен длине конвенций прое
|
|||||||
отвечается «нет» ещё до запуска. Держать её ниже `large` значит платить за
|
отвечается «нет» ещё до запуска. Держать её ниже `large` значит платить за
|
||||||
предсказуемый ответ на каждой задаче.
|
предсказуемый ответ на каждой задаче.
|
||||||
|
|
||||||
Причина меток — арифметика, а не экономия на осторожности. Чекпоинт стоит
|
Причина меток — арифметика, а не экономия на осторожности. Стадия стоит
|
||||||
**на каждой задаче**, поэтому каждый проход здесь умножается на число задач, и при
|
**на каждой задаче**, поэтому каждый проход здесь умножается на число задач, и при
|
||||||
мелкой нарезке это самая большая статья конвейера.
|
мелкой нарезке это самая большая статья конвейера.
|
||||||
|
|
||||||
**Граф этой стадии свой, и он плоский.** Гейта нет — кода ещё нет, запускать
|
**Граф этой стадии свой, и он плоский.** Гейта нет — кода ещё нет, запускать
|
||||||
нечего; метка уже названа разметкой задачи; машину не держит ни один проход;
|
нечего; метка уже названа разметкой задачи; машину не держит ни один проход;
|
||||||
сток — не триаж, а шаг пайплайна задачи, где замечания отрабатываются правкой
|
сток — не триаж, а шаг скилла `av-dev-code:resolve`, где замечания
|
||||||
спек. Триаж здесь не нужен: находок единицы, и каждая либо правит спеку, либо
|
отрабатываются правкой спек. Триаж здесь не нужен: находок единицы, и каждая
|
||||||
|
либо правит спеку, либо
|
||||||
становится развилкой.
|
становится развилкой.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
@@ -868,10 +902,10 @@ flowchart TD
|
|||||||
plan[/"план разметки задачи:<br/>размер, сложность, метка"/]
|
plan[/"план разметки задачи:<br/>размер, сложность, метка"/]
|
||||||
proposal["предложение: proposal.md + дельта-спеки"]
|
proposal["предложение: proposal.md + дельта-спеки"]
|
||||||
specs["specs (режим «дизайн ДО кода») — всегда"]
|
specs["specs (режим «дизайн ДО кода») — всегда"]
|
||||||
rubric["rubric, фаза 1 → приёмочные критерии в tasks.md"]
|
rubric["rubric → приёмочные критерии в tasks.md"]
|
||||||
arch["architecture на предложении"]
|
arch["architecture на предложении"]
|
||||||
author["вопрос автору: три формы решения и компромисс каждой"]
|
author["вопрос автору: три формы решения и компромисс каждой"]
|
||||||
fix["шаг пайплайна: правка спек, развилки — вопросом в запись"]
|
fix["шаг resolve: правка спек, развилки — вопросом в запись"]
|
||||||
|
|
||||||
plan --> proposal
|
plan --> proposal
|
||||||
proposal --> specs
|
proposal --> specs
|
||||||
@@ -905,13 +939,16 @@ flowchart TD
|
|||||||
|
|
||||||
- Оркестратор чинит помеченное `Действие: инлайн` и **не логирует мелочь**.
|
- Оркестратор чинит помеченное `Действие: инлайн` и **не логирует мелочь**.
|
||||||
- `Действие: развилка` — вопросом с вариантами и ценой каждого туда, где проект
|
- `Действие: развилка` — вопросом с вариантами и ценой каждого туда, где проект
|
||||||
держит вопросы (это знает вызвавший пайплайн, а не конвейер). Оркестратор не
|
держит вопросы (это знает вызвавший скилл, а не конвейер ревью). Оркестратор не
|
||||||
останавливается: он урезает изменение до остатка и доводит его.
|
останавливается: он урезает изменение до остатка и доводит его.
|
||||||
- Находка не для этого мерджа, но реальная (отложенный `major`, развилка,
|
- Находка не для этого мерджа, но реальная (отложенный `major`, развилка,
|
||||||
решённая «потом»), — не теряется, но **и не заводится здесь**. Конвейер отдаёт
|
решённая «потом»), — не теряется, но **и не заводится здесь**. Конвейер отдаёт
|
||||||
её **списком урожая** в отчёте: формулировка, оракул, провенанс (какой проход,
|
её **списком урожая** в отчёте: формулировка, оракул, провенанс (какой проход,
|
||||||
какой change). Заведение задач принадлежит тому, кто ведёт задачи проекта, —
|
какой change). Заведение задач принадлежит `av-dev-tasks:tasks` — зови его со
|
||||||
у него свой формат, своя нарезка и свои правила дублей. Мелочь класса `nit`
|
списком урожая, у него на этот вход отдельный сценарий «задачи из ревью и
|
||||||
|
аудита»: свой формат, кластеризация по причине, дедуп против беклога и
|
||||||
|
кладбища. Плагина нет — урожай остаётся списком в отчёте, и это говорится
|
||||||
|
строкой доклада: задачи из него не заведёт никто. Мелочь класса `nit`
|
||||||
идёт в урожай одной пачкой, а не записью на находку.
|
идёт в урожай одной пачкой, а не записью на находку.
|
||||||
- `Promote candidates` — по процедуре [references/promote.md](references/promote.md):
|
- `Promote candidates` — по процедуре [references/promote.md](references/promote.md):
|
||||||
находка → конвенция → правило линтера → **удаление формулировки из конвенций**.
|
находка → конвенция → правило линтера → **удаление формулировки из конвенций**.
|
||||||
@@ -923,11 +960,11 @@ flowchart TD
|
|||||||
Он единственное, по чему потом видно, что было найдено и что из этого не
|
Он единственное, по чему потом видно, что было найдено и что из этого не
|
||||||
заведено: нулевой урожай при непустом отчёте виден сразу.
|
заведено: нулевой урожай при непустом отчёте виден сразу.
|
||||||
**Вместе с изменением он и переезжает:** после `opsx:archive` его адрес —
|
**Вместе с изменением он и переезжает:** после `opsx:archive` его адрес —
|
||||||
`openspec/changes/archive/<id>/review/`. Кто ищет отчёт после архивации (батч
|
`openspec/changes/archive/<id>/review/`. Кто ищет отчёт после архивации
|
||||||
на финальной сверке, приёмщик на сессии), смотрит **оба** пути; «отчёта нет»
|
(приёмщик на груминге `av-dev-tasks:groom`, разбор дефекта), смотрит **оба**
|
||||||
объявляется, только когда пуст и архивный, иначе самый дорогой сценарий
|
пути; «отчёта нет» объявляется, только когда пуст и архивный, иначе самый
|
||||||
«состав ревью неизвестен, гоняем заново» срабатывает на каждой доведённой
|
дорогой сценарий «состав ревью неизвестен, гоняем заново» срабатывает на
|
||||||
задаче.
|
каждой доведённой задаче.
|
||||||
|
|
||||||
## Честный предел
|
## Честный предел
|
||||||
|
|
||||||
@@ -994,7 +1031,7 @@ flowchart TD
|
|||||||
сверкой и доказательством лежит весь класс дефектов, который виден только
|
сверкой и доказательством лежит весь класс дефектов, который виден только
|
||||||
построенным путём, — и он проверяется на 5–10% задач.
|
построенным путём, — и он проверяется на 5–10% задач.
|
||||||
|
|
||||||
Это сознательная сделка, а не пробел в устройстве: цес меткой `large` платится на
|
Это сознательная сделка, а не пробел в устройстве: цена метки `large` платится на
|
||||||
каждой задаче, а окупается на немногих. Проверяется сделка не рассуждением, а
|
каждой задаче, а окупается на немногих. Проверяется сделка не рассуждением, а
|
||||||
журналом дефектов: если класс, который ловят только меряющие проходы, начал
|
журналом дефектов: если класс, который ловят только меряющие проходы, начал
|
||||||
всплывать после мерджа — метку выбирают слишком низко.
|
всплывать после мерджа — метку выбирают слишком низко.
|
||||||
@@ -1021,7 +1058,7 @@ flowchart TD
|
|||||||
и где это лежит в документах проекта; таблица поразрядной деградации.
|
и где это лежит в документах проекта; таблица поразрядной деградации.
|
||||||
- [references/review-levels.md](references/review-levels.md) — дом правила выбора
|
- [references/review-levels.md](references/review-levels.md) — дом правила выбора
|
||||||
метки: две оси, спорное вниз, чем `small` дешевле, доли как проверка правила.
|
метки: две оси, спорное вниз, чем `small` дешевле, доли как проверка правила.
|
||||||
- Skill `av-dev-pm:canon` — приведение проекта к канону документов.
|
- Skill `av-dev-docs:canon` — приведение проекта к канону документов.
|
||||||
- [references/finding-contract.md](references/finding-contract.md) — контракт находок.
|
- [references/finding-contract.md](references/finding-contract.md) — контракт находок.
|
||||||
- [references/promote.md](references/promote.md) — промоут находка → конвенция → правило → удаление.
|
- [references/promote.md](references/promote.md) — промоут находка → конвенция → правило → удаление.
|
||||||
- [references/calibration.md](references/calibration.md) — калибровка инъекцией, вердикты keep/retune/drop.
|
- [references/calibration.md](references/calibration.md) — калибровка инъекцией, вердикты keep/retune/drop.
|
||||||
+7
-7
@@ -5,12 +5,11 @@
|
|||||||
выдаёт правдоподобные общие места: их дорого опровергать и нечем подтверждать.
|
выдаёт правдоподобные общие места: их дорого опровергать и нечем подтверждать.
|
||||||
|
|
||||||
Отдельного файла-брифа **нет**. Проектная конкретика живёт в документах канона
|
Отдельного файла-брифа **нет**. Проектная конкретика живёт в документах канона
|
||||||
`av-dev-pm`, и проход читает их напрямую: пути жёсткие, посредник не нужен, а
|
`av-dev-docs`, и проход читает их напрямую: пути жёсткие, посредник не нужен, а
|
||||||
второй дом для тех же фактов разошёлся бы и выглядел актуальным.
|
второй дом для тех же фактов разошёлся бы и выглядел актуальным.
|
||||||
|
|
||||||
Определение канона — в плагине `av-dev-pm`,
|
Определение канона держит скилл `av-dev-docs:canon`. Здесь только карта «тема →
|
||||||
`skills/canon/references/canon.md`. Здесь только карта «тема → её дом → что
|
её дом → что оттуда берётся».
|
||||||
оттуда берётся».
|
|
||||||
|
|
||||||
## Карта тем
|
## Карта тем
|
||||||
|
|
||||||
@@ -119,7 +118,7 @@
|
|||||||
строка неотличима от «мы просто не стали» и перестаёт читаться на третьей задаче.
|
строка неотличима от «мы просто не стали» и перестаёт читаться на третьей задаче.
|
||||||
|
|
||||||
**Документов канона нет вовсе** — проект не приведён к канону. Это не повод
|
**Документов канона нет вовсе** — проект не приведён к канону. Это не повод
|
||||||
работать вслепую: скажи об этом строкой и предложи `av-dev-pm:canon`. Одна
|
работать вслепую: скажи об этом строкой и предложи `av-dev-docs:canon`. Одна
|
||||||
операция на проект против деградации на каждой задаче.
|
операция на проект против деградации на каждой задаче.
|
||||||
|
|
||||||
## Правило чтения
|
## Правило чтения
|
||||||
@@ -133,5 +132,6 @@
|
|||||||
на диск и на СУБД» экономит обязательный вопрос. Отсутствие строки — не факт,
|
на диск и на СУБД» экономит обязательный вопрос. Отсутствие строки — не факт,
|
||||||
а пробел, и его надо назвать в границах покрытия.
|
а пробел, и его надо назвать в границах покрытия.
|
||||||
- **Свойство, ставшее правилом линтера, из конвенций удалено** и лежит в
|
- **Свойство, ставшее правилом линтера, из конвенций удалено** и лежит в
|
||||||
перечне механизированного в `docs/conventions/README.md`. Проверять его
|
перечне механизированного — в `docs/conventions/README.md`, если конвенции
|
||||||
проходом — тратить внимание на уже проверенное.
|
каталогом, и отдельным разделом `docs/conventions.md`, если файлом. Проверять
|
||||||
|
его проходом — тратить внимание на уже проверенное.
|
||||||
+4
-3
@@ -18,7 +18,7 @@ flowchart TD
|
|||||||
no["промоуту не подлежит:<br/>место одно — комментарий в коде;<br/>вкусовщина — вон на триаже;<br/>нужен рантайм — в журнал ревью"]
|
no["промоуту не подлежит:<br/>место одно — комментарий в коде;<br/>вкусовщина — вон на триаже;<br/>нужен рантайм — в журнал ревью"]
|
||||||
conv["конвенция:<br/>проверяемое свойство + какой проход нашёл"]
|
conv["конвенция:<br/>проверяемое свойство + какой проход нашёл"]
|
||||||
rule["правило линтера, запретитель,<br/>тест-сканер или анализатор"]
|
rule["правило линтера, запретитель,<br/>тест-сканер или анализатор"]
|
||||||
clean["шаг 3: формулировка удалена из конвенций,<br/>строка — в conventions/README.md"]
|
clean["шаг 3: формулировка удалена из конвенций,<br/>строка — в перечень механизированного"]
|
||||||
|
|
||||||
f --> cond
|
f --> cond
|
||||||
cond -->|нет| no
|
cond -->|нет| no
|
||||||
@@ -85,8 +85,9 @@ flowchart TD
|
|||||||
- из файла конвенций убирается формулировка правила; остаётся, если нужно, одна
|
- из файла конвенций убирается формулировка правила; остаётся, если нужно, одна
|
||||||
строка «проверяется линтером `<имя>`» — но только там, где без неё раздел
|
строка «проверяется линтером `<имя>`» — но только там, где без неё раздел
|
||||||
теряет связность;
|
теряет связность;
|
||||||
- правило переезжает в **перечень механизированного в
|
- правило переезжает в **перечень механизированного в доме конвенций**
|
||||||
`docs/conventions/README.md`** — со ссылкой на место механизации: конфиг
|
(`docs/conventions/README.md` у каталога, отдельный раздел
|
||||||
|
`docs/conventions.md` у файла) — со ссылкой на место механизации: конфиг
|
||||||
линтера, собственный анализатор, тест-сканер исходников. Не названное место
|
линтера, собственный анализатор, тест-сканер исходников. Не названное место
|
||||||
означает, что проход будет добросовестно проверять уже проверенное;
|
означает, что проход будет добросовестно проверять уже проверенное;
|
||||||
- из контекста инструмента спек убирается дубль, если он там был.
|
- из контекста инструмента спек убирается дубль, если он там был.
|
||||||
+2
-3
@@ -1,7 +1,7 @@
|
|||||||
# Журнал дефектов
|
# Журнал дефектов
|
||||||
|
|
||||||
Артефакт проекта, а не плагина: файл живёт в репозитории — **`docs/review.md`**,
|
Артефакт проекта, а не плагина: файл живёт в репозитории — **`docs/review.md`**,
|
||||||
слот канона `av-dev-pm`. Здесь описано, зачем он и какой формы, потому что без
|
слот канона `av-dev-docs`. Здесь описано, зачем он и какой формы, потому что без
|
||||||
него конвейер не учится: находки закрываются, а почему их не поймали — забывается,
|
него конвейер не учится: находки закрываются, а почему их не поймали — забывается,
|
||||||
и один и тот же класс проскакивает второй раз.
|
и один и тот же класс проскакивает второй раз.
|
||||||
|
|
||||||
@@ -41,8 +41,7 @@
|
|||||||
## Форма записи
|
## Форма записи
|
||||||
|
|
||||||
**Это дом формы, и у него есть копия.** Скелет `docs/review.md`, который кладёт
|
**Это дом формы, и у него есть копия.** Скелет `docs/review.md`, который кладёт
|
||||||
в проект `av-dev-pm` (`skills/canon/references/skeletons.md`), повторяет её
|
в проект `av-dev-docs:canon`, повторяет её дословно — он уезжает в репозиторий и обязан там что-то говорить. Правка формы
|
||||||
дословно — он уезжает в репозиторий и обязан там что-то говорить. Правка формы
|
|
||||||
здесь **обязана** тянуть правку скелета и запись в журнал версий канона; иначе
|
здесь **обязана** тянуть правку скелета и запись в журнал версий канона; иначе
|
||||||
проекты продолжат писать по старой форме, а конвейер — ждать поля, которого нет.
|
проекты продолжат писать по старой форме, а конвейер — ждать поля, которого нет.
|
||||||
Дословность сверяет `scripts/copies.py` маркетплейса по маркерам ниже — но
|
Дословность сверяет `scripts/copies.py` маркетплейса по маркерам ниже — но
|
||||||
+4
-4
@@ -94,9 +94,9 @@
|
|||||||
костяк — гейт, спеки, код, триаж**. Разрезать задачу, обе половины которой
|
костяк — гейт, спеки, код, триаж**. Разрезать задачу, обе половины которой
|
||||||
остаются в одной метке, значит заплатить костяк дважды за ту же проверку.
|
остаются в одной метке, значит заплатить костяк дважды за ту же проверку.
|
||||||
Резать стоит там, где разрез **снимает доказательство с большей части диффа**.
|
Резать стоит там, где разрез **снимает доказательство с большей части диффа**.
|
||||||
Шов и правило нарезки живут у того, кто ведёт задачи, — скилл `av-dev-pm:tasks`,
|
Шов и правило нарезки живут у того, кто ведёт задачи, — скилл
|
||||||
его `references/split.md`. Пути туда конвейер не выносит: за пределы своего
|
`av-dev-tasks:tasks`, его раздел о нарезке. Пути туда конвейер не выносит: за
|
||||||
плагина он ходит вызовом скилла, а не файлом.
|
пределы своего плагина он ходит вызовом скилла, а не файлом.
|
||||||
|
|
||||||
Разметка в костяк не входит — она платится один раз на задачу, а не один раз на
|
Разметка в костяк не входит — она платится один раз на задачу, а не один раз на
|
||||||
прогон, и потому **разрез задачи её не удваивает**. Это единственное, что стало
|
прогон, и потому **разрез задачи её не удваивает**. Это единственное, что стало
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
дёшев.
|
дёшев.
|
||||||
|
|
||||||
Считается это по журналу дефектов и по отчётам, а не по ощущению: метка
|
Считается это по журналу дефектов и по отчётам, а не по ощущению: метка
|
||||||
напечатана в каждом отчёте, и посчитать её за спринт — работа на минуту.
|
напечатана в каждом отчёте, и посчитать её за месяц — работа на минуту.
|
||||||
|
|
||||||
**У дрейфа вниз есть свой стимул, и его стоит назвать.** `small` дешевле по
|
**У дрейфа вниз есть свой стимул, и его стоит назвать.** `small` дешевле по
|
||||||
времени и по деньгам, а выбирает метку хоть и не автор, но проход, читающий
|
времени и по деньгам, а выбирает метку хоть и не автор, но проход, читающий
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "av-dev-docs",
|
||||||
|
"description": "Документация проекта: канон раскладки (CLAUDE.md плюс docs/ — паспорт, архитектура, схема БД, безопасность, конвенции, разведка, ADR, журнал ревью), роли документов и правило единственного дома. Три операции одной машиной сравнения — check, adopt, upgrade — со скриптом docs.py; заведение нового проекта интервью по брифу; ведение содержимого по ходу разработки: синк после сделанной задачи, ADR промоутом из архивного design.md, записка разведки, запись дефекта в журнал ревью. Смысловое, чего скрипт не видит, судит скилл healthcheck двумя агентами разом — doc-consistency (документы между собой и с openspec) и doc-code-drift (факты против кода); язык документов вычитывает агент doc-wording. Задач не ведёт — это плагин av-dev-tasks, и он опционален.",
|
||||||
|
"author": {
|
||||||
|
"name": "Anton Vakhrushev",
|
||||||
|
"email": "anwinged@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: doc-code-drift
|
name: doc-code-drift
|
||||||
description: "Сверка документов канона с кодом по закрытому перечню проверяемых фактов: имя основной ветки и команды из CLAUDE.md, запреты с путями, testdata и временный каталог, путь миграций из .pm.json, внешние зависимости поимённо в architecture.md против манифеста, настройки с числовым значением в database.md против конфига и кода, единые точки проекта против реального числа реализаций, capability против существующих модулей. Отвечает на «этот факт ещё верен», а не «эта архитектура правильная». Читает весь репозиторий, гоняет только читающие команды. Отдаёт готовые формулировки и ничего не правит сам. Согласованность документов между собой смотрит агент doc-consistency. Использовать на сессии между спринтами, а также после приведения проекта к канону (adopt) и после повышения версии канона (upgrade). Только чтение."
|
description: "Сверка документов канона с кодом по закрытому перечню проверяемых фактов: имя основной ветки и команды из CLAUDE.md, запреты с путями, testdata и временный каталог, путь миграций из .pm.json, внешние зависимости поимённо в architecture.md против манифеста, настройки с числовым значением в database.md против конфига и кода, единые точки проекта против реального числа реализаций, capability против существующих модулей. Отвечает на «этот факт ещё верен», а не «эта архитектура правильная». Читает весь репозиторий, гоняет только читающие команды. Отдаёт готовые формулировки и ничего не правит сам. Согласованность документов между собой смотрит агент doc-consistency. Зовётся скиллом av-dev-docs:healthcheck — на весь канон разом; он же зовётся шагом adopt и шагом upgrade. Только чтение."
|
||||||
tools: Read, Grep, Glob, Bash
|
tools: Read, Grep, Glob, Bash
|
||||||
model: sonnet
|
model: sonnet
|
||||||
color: green
|
color: green
|
||||||
@@ -50,7 +50,7 @@ color: green
|
|||||||
проверить, — это **не находка, а строка в границах покрытия**.
|
проверить, — это **не находка, а строка в границах покрытия**.
|
||||||
|
|
||||||
1. **Имя основной ветки** (`CLAUDE.md`). От неё считается база диффа
|
1. **Имя основной ветки** (`CLAUDE.md`). От неё считается база диффа
|
||||||
(`git merge-base HEAD <ветка>`), в неё вливает батч, от неё ветвятся задачи.
|
(`git merge-base HEAD <ветка>`), в неё коммитит работу конвейер.
|
||||||
Проверка: `git symbolic-ref refs/remotes/origin/HEAD` либо перечень веток.
|
Проверка: `git symbolic-ref refs/remotes/origin/HEAD` либо перечень веток.
|
||||||
Угадывание между `master` и `main` ломает интеграцию целиком, и это самая
|
Угадывание между `master` и `main` ломает интеграцию целиком, и это самая
|
||||||
дешёвая находка из всех.
|
дешёвая находка из всех.
|
||||||
@@ -117,7 +117,8 @@ color: green
|
|||||||
домах, противоречие между документами, поведение в обзоре, ADR и провенанс.
|
домах, противоречие между документами, поведение в обзоре, ADR и провенанс.
|
||||||
Увидел — строкой в границы покрытия, находкой не оформляй.
|
Увидел — строкой в границы покрытия, находкой не оформляй.
|
||||||
|
|
||||||
**Язык** — у `doc-wording`. **Форму записи задач** — у `task-form`.
|
**Язык документов** — у `doc-wording`, **язык записей задач** — у
|
||||||
|
`task-wording`. **Форму записи задач** — у `task-form`.
|
||||||
|
|
||||||
**Машинной проверке — вообще ничего.** Всё, что ловят `docs.py check` и
|
**Машинной проверке — вообще ничего.** Всё, что ловят `docs.py check` и
|
||||||
`tasks.py check` (пути канона, имена файлов, битые ссылки, версия, плейсхолдеры,
|
`tasks.py check` (пути канона, имена файлов, битые ссылки, версия, плейсхолдеры,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: doc-consistency
|
name: doc-consistency
|
||||||
description: "Сверка документов канона между собой и с openspec: один факт, живущий в двух домах, прямое противоречие между документами (периметр, зависимости, обратимость), поведение системы, осевшее в architecture.md вместо спек, capability без обзора или с пересказом требований, число без провенанса в research, ADR без ссылки на архивный design.md и без парного статуса при замене, заглушка вместо честной строки в пустом слоте. Читает docs/ и openspec/, кода не читает. Отдаёт готовые формулировки и ничего не правит сам. Соответствие документов коду смотрит агент doc-code-drift, язык — doc-wording. Использовать на сессии между спринтами, а также после приведения проекта к канону (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
|
tools: Read, Grep, Glob
|
||||||
model: opus
|
model: opus
|
||||||
color: yellow
|
color: yellow
|
||||||
@@ -15,25 +15,25 @@ color: yellow
|
|||||||
машина, а что человек», и её правая колонка — твой устав дословно.
|
машина, а что человек», и её правая колонка — твой устав дословно.
|
||||||
|
|
||||||
Карта домов, по которой ты судишь о правиле 1, — дословная копия канона; дом её
|
Карта домов, по которой ты судишь о правиле 1, — дословная копия канона; дом её
|
||||||
`av-dev-pm/skills/canon/references/canon.md`, раздел «Правило единственного
|
`av-dev-docs/skills/canon/references/canon.md`, раздел «Правило единственного
|
||||||
дома», и правится она там. Здесь она стоит потому, что ты работаешь в
|
дома», и правится она там. Здесь она стоит потому, что ты работаешь в
|
||||||
репозитории проекта, где плагина может не быть вовсе.
|
репозитории проекта, где плагина может не быть вовсе.
|
||||||
|
|
||||||
<!-- копия: карта-домов из av-dev-pm/skills/canon/references/canon.md -->
|
<!-- копия: карта-домов из av-dev-docs/skills/canon/references/canon.md -->
|
||||||
| Факт | Дом |
|
| Факт | Дом |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| поведение системы | `openspec/specs/<capability>/spec.md` |
|
| поведение системы | `openspec/specs/<capability>/spec.md` |
|
||||||
| почему решено так | `adr/`, источник — архивный `design.md` |
|
| почему решено так | `adr/`, источник — архивный `design.md` |
|
||||||
| граница домена, «чем не является» | `passport.md` |
|
| граница домена, «чем не является» | `passport.md` |
|
||||||
| инвариант и его severity | `CLAUDE.md` |
|
| инвариант и его severity | `CLAUDE.md` |
|
||||||
| что приложение умеет и чего не умеет; порядок работ | `docs/tasks/ROADMAP.md` |
|
| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` |
|
||||||
| измеренное число | `research/` |
|
| измеренное число | `research/` |
|
||||||
| настройка с числовым значением | `database.md` |
|
| настройка с числовым значением | `database.md` |
|
||||||
| периметр и модель угроз | `security.md` |
|
| периметр и модель угроз | `security.md` |
|
||||||
| что необратимо | `CLAUDE.md` — **не** `architecture.md` |
|
| что необратимо | `CLAUDE.md` — **не** `architecture.md` |
|
||||||
| единые точки проекта | `architecture.md` |
|
| единые точки проекта | `architecture.md` |
|
||||||
| имя основной ветки, `testdata`, временный каталог | `CLAUDE.md` |
|
| имя основной ветки, `testdata`, временный каталог | `CLAUDE.md` |
|
||||||
| что уже механизировано правилом | `conventions/README.md` |
|
| что уже механизировано правилом | `conventions.*`, раздел «Механизировано» |
|
||||||
<!-- /копия: карта-домов -->
|
<!-- /копия: карта-домов -->
|
||||||
|
|
||||||
**Факта нет в карте — дома у него нет**, и это находка о самом каноне, а не о
|
**Факта нет в карте — дома у него нет**, и это находка о самом каноне, а не о
|
||||||
@@ -45,8 +45,10 @@ color: yellow
|
|||||||
|
|
||||||
## Что тебе дают
|
## Что тебе дают
|
||||||
|
|
||||||
Корень проекта. Твоё чтение — `docs/**` (кроме `docs/tasks/`, его ведёт
|
Корень проекта. Твоё чтение — `docs/**`, `CLAUDE.md`, `openspec/specs/**` и
|
||||||
`tasks.py`), `CLAUDE.md`, `openspec/specs/**` и `openspec/config.yaml`. Плюс
|
`openspec/config.yaml`. **Каталог задач не твой** — он лежит в `tasks/` (или в
|
||||||
|
`docs/tasks/` на непереехавшем проекте), принадлежит другому плагину и ведётся
|
||||||
|
своим скриптом; не открывай его ни в той форме, ни в другой. Плюс
|
||||||
`openspec/changes/archive/`, когда проверяешь ADR: там лежат `design.md`, из
|
`openspec/changes/archive/`, когда проверяешь ADR: там лежат `design.md`, из
|
||||||
которых записи промоутятся.
|
которых записи промоутятся.
|
||||||
|
|
||||||
@@ -178,6 +180,14 @@ color: yellow
|
|||||||
|
|
||||||
## Доклад
|
## Доклад
|
||||||
|
|
||||||
|
**Форма параллельна дому `вычитка-доклад` (`shared/language.md`), но копией не
|
||||||
|
является, и маркера здесь нет намеренно.** Копию того дома везут проходы вычитки
|
||||||
|
— `doc-wording` и `task-wording`; у судьи утверждений расходится каждое поле:
|
||||||
|
находка стоит на **паре** документов, а не на одном, несёт **дом по канону** и не
|
||||||
|
несёт «почему», а границы покрытия считают документы и спрашивают про спеки и
|
||||||
|
архив изменений, а не про термины. Одинаков только порядок разделов, и сверять
|
||||||
|
машиной в нём нечего.
|
||||||
|
|
||||||
Находки по одной, в порядке важности: прямые противоречия → факт в двух домах →
|
Находки по одной, в порядке важности: прямые противоречия → факт в двух домах →
|
||||||
поведение в обзоре → ADR и провенанс → пустые слоты. Первые ломают решения,
|
поведение в обзоре → ADR и провенанс → пустые слоты. Первые ломают решения,
|
||||||
которые по документам принимают; последние — только цену чтения.
|
которые по документам принимают; последние — только цену чтения.
|
||||||
@@ -0,0 +1,242 @@
|
|||||||
|
---
|
||||||
|
name: doc-wording
|
||||||
|
description: "Вычитка языка документов проекта по информационному стилю — паспорт, архитектура, конвенции, безопасность, решения ADR, записки разведки, CLAUDE.md. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в имени файла. Отдаёт готовые формулировки на замену и ничего не правит сам. Записи каталога задач вычитывает отдельный агент task-wording, их форму — task-form. Зовётся по названной пачке правленных документов, а не на весь канон: последним шагом синка документации (av-dev-docs:docs), шагом заведения проекта (av-dev-docs:init), шагами adopt и upgrade скилла av-dev-docs:canon. Скилл healthcheck его не зовёт — там сверка утверждений, а не языка. Только чтение."
|
||||||
|
tools: Read, Grep, Glob
|
||||||
|
model: sonnet
|
||||||
|
color: green
|
||||||
|
---
|
||||||
|
|
||||||
|
Ты — **вычитка языка документов проекта**: паспорта, архитектуры, конвенций,
|
||||||
|
модели угроз, решений ADR, записок разведки, `CLAUDE.md`. Оптика — слова и
|
||||||
|
фразы, а не то, что текст описывает: ты не судишь, верно ли решение, полна ли
|
||||||
|
архитектура и согласованы ли документы между собой.
|
||||||
|
|
||||||
|
Границу держи твёрдо. **Записи каталога задач — не твои**: их язык вычитывает
|
||||||
|
`task-wording`, их форму — `task-form`. Открыл файл задачи по ссылке из
|
||||||
|
документа и увидел язык — скажи одной строкой в конце доклада, не находкой. Две
|
||||||
|
проверки одного места расходятся и начинают спорить.
|
||||||
|
|
||||||
|
Ты **ничего не правишь**. Каждая находка — готовая формулировка на замену,
|
||||||
|
которую зовущий впишет сам. Файлы ты только читаешь.
|
||||||
|
|
||||||
|
## Что тебе дают
|
||||||
|
|
||||||
|
Список файлов или каталог: документы канона (`docs/*.md`), конвенции
|
||||||
|
(`docs/conventions/`), решения (`docs/adr/`), записки (`docs/research/`),
|
||||||
|
`CLAUDE.md` — вперемешку тоже.
|
||||||
|
|
||||||
|
По этим же документам проверяется, **известен ли термин**. Дали неполный набор —
|
||||||
|
считай известными только те слова, что встречаются в поданных файлах, и говори
|
||||||
|
об этом в границах покрытия.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
Дом — `shared/language.md` в репозитории плагинов, и там же объяснено, зачем
|
||||||
|
стиль вообще нужен. Здесь только то, что нужно тебе для работы.
|
||||||
|
|
||||||
|
<!-- копия: язык-правила из shared/language.md -->
|
||||||
|
|
||||||
|
У каждого правила названа причина: она же говорит, где правило **не**
|
||||||
|
применяется.
|
||||||
|
|
||||||
|
1. **Глагол вместо отглагольного существительного, активный залог.** «Обработчик
|
||||||
|
не проверяет владельца», а не «проверка владельца не осуществляется»;
|
||||||
|
«скрипт переписывает индекс», а не «индекс переписывается скриптом».
|
||||||
|
Отглагольное существительное прячет того, кто действует, — а в техническом
|
||||||
|
тексте важен именно он. Страдательный залог **остаётся**, когда деятель
|
||||||
|
неизвестен или неважен: «файл удаляется» верно, если удаляет любая из трёх
|
||||||
|
команд.
|
||||||
|
|
||||||
|
2. **Факт вместо оценки.** «Время ответа доходит до 800 мс», а не «работает
|
||||||
|
медленно»; «тело 40 МиБ держит блокировку 5 секунд», а не «большие тела
|
||||||
|
тормозят». Оценка допустима, когда факт стоит рядом, в той же фразе. Без
|
||||||
|
факта это настроение, а не сведение, — и находка тем ценнее, что оценку
|
||||||
|
потом не проверить.
|
||||||
|
|
||||||
|
3. **Стоп-слова.** Канцелярит (является, осуществляется, в целях, в рамках,
|
||||||
|
данный, вышеуказанный), вводные-паразиты (в общем, как известно, стоит
|
||||||
|
отметить), усилители (очень, крайне, достаточно, абсолютно, максимально),
|
||||||
|
синонимы одного качества («понятный и простой»), неопределённое
|
||||||
|
(соответствующий, определённый, некоторый).
|
||||||
|
|
||||||
|
Проверка одна: **вычеркни слово — смысл изменился, оставляй.** И осторожно с
|
||||||
|
вводными: «если», «иначе», «при таком-то условии», «в отличие от» несут
|
||||||
|
условие и противопоставление, то есть сведения, — их не трогают.
|
||||||
|
|
||||||
|
4. **Одна мысль — одно предложение.** Предложение с двумя независимыми
|
||||||
|
утверждениями делится. **Причинную связь не режут**: «поэтому», «иначе», «раз
|
||||||
|
так» — смысл, а не длина; рубленые фразы ради краткости тут вредят.
|
||||||
|
|
||||||
|
**Поля меты не делятся.** «Зачем» в мете задачи по формату — одно
|
||||||
|
предложение: оно повторяется строкой индекса, и второму там не поместиться.
|
||||||
|
Тесно — сокращают, но не делят. То же с любым полем вида `- **Имя:** …`.
|
||||||
|
|
||||||
|
5. **Англицизм, у которого есть живое русское слово, заменяется.**
|
||||||
|
|
||||||
|
| Калька | Русский аналог |
|
||||||
|
| --- | --- |
|
||||||
|
| флоу | поток, процесс, сценарий |
|
||||||
|
| фикс, зафиксить | исправление, исправить, починить |
|
||||||
|
| чекать | проверять |
|
||||||
|
| апрув, заапрувить | согласование, согласовать |
|
||||||
|
| best-effort | по возможности |
|
||||||
|
| кейс | случай, сценарий |
|
||||||
|
| перформанс | производительность |
|
||||||
|
| матчинг, смэтчить | сопоставление, сопоставить |
|
||||||
|
| зарелизить | выпустить, выложить |
|
||||||
|
| отрефакторить | переписать, разделить, убрать второй путь |
|
||||||
|
|
||||||
|
Насильно не переводится то, что является **именем вещи**: термины технологий
|
||||||
|
и протоколов (`SQL`, `API`, `CSV`, `N+1`, `IDOR`), имена классов, методов,
|
||||||
|
полей, таблиц и команд, слаг, а также термин, у которого нет точного русского
|
||||||
|
эквивалента и который в команде уже прижился.
|
||||||
|
|
||||||
|
Цель — простой и точный текст, а не пуризм. Русский аналог звучит коряво или
|
||||||
|
искажает смысл — остаётся термин.
|
||||||
|
|
||||||
|
6. **Слово из своего словаря не трогается — список закрыт.** Оговорка «термин
|
||||||
|
прижился» без списка проверяема на глаз и потому не проверяема: прижившимся
|
||||||
|
выглядит любое слово, встреченное трижды.
|
||||||
|
|
||||||
|
| Термин | Что называет |
|
||||||
|
| --- | --- |
|
||||||
|
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
|
||||||
|
| триаж | стадия конвейера, сводящая находки в решение |
|
||||||
|
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
|
||||||
|
| дедуп, дедупликация | сверка нового против уже лежащего |
|
||||||
|
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
|
||||||
|
| дифф, `--base` | разница между состояниями в git |
|
||||||
|
| промпт | текст, которым зовут модель |
|
||||||
|
| change, capability, spec | сущности OpenSpec, имена вещей чужого инструмента |
|
||||||
|
| generative, applicative | роды проходов ревью, вводятся определением по месту |
|
||||||
|
|
||||||
|
**Список закрыт.** Слово не отсюда и не из таблицы имён вещей выше — находка,
|
||||||
|
а не «принятый стиль»: у него либо есть живой русский аналог, либо оно
|
||||||
|
требует ввода одной строкой при первом употреблении.
|
||||||
|
|
||||||
|
Отсюда же читается снятое. Эти слова из текстов убраны, и возвращать их не
|
||||||
|
надо: **конфляция** (смешение), **декорреляция** (разведённость, разведён с
|
||||||
|
кем-то), **непоймание** (почему не поймали), **эвал-сет** (проверочный
|
||||||
|
набор), **гайд** (руководство). Каждое было латинизмом или калькой при живом
|
||||||
|
русском слове, и каждое к моменту снятия жило в трёх-шести файлах разом — то
|
||||||
|
есть выглядело словарём, не будучи им.
|
||||||
|
|
||||||
|
7. **Жаргон и метафоры заменяются прямым называнием.** Автору образ понятен,
|
||||||
|
читателю — нет.
|
||||||
|
|
||||||
|
| Метафора-жаргон | Прямо |
|
||||||
|
| --- | --- |
|
||||||
|
| рычаг (кэша, отбора) | условие отбора, параметр |
|
||||||
|
| навешен не на тот счётчик | завязан не на тот счётчик |
|
||||||
|
| переширокий матчинг по имени | слишком грубое сопоставление по имени, слишком много слабых совпадений |
|
||||||
|
| костыль | временное решение, обходной путь — и в чём именно |
|
||||||
|
| просело, отвалилось | стало медленнее на столько-то, перестало отвечать |
|
||||||
|
|
||||||
|
Проверка: **фраза требует, чтобы читатель додумал образ, — заменяется
|
||||||
|
буквальным описанием того, что происходит.**
|
||||||
|
|
||||||
|
8. **Термин, которого нет в документах проекта, вводится одной строкой или не
|
||||||
|
употребляется.** Термин, не встречающийся ни в паспорте, ни в архитектуре, ни
|
||||||
|
в конвенциях, — свой словарь у отдельной записи, а это самый дешёвый способ
|
||||||
|
сделать беклог нечитаемым для того, кто вернётся к нему через квартал.
|
||||||
|
Заменять незнакомый термин догадкой нельзя: догадка о предметной области
|
||||||
|
дороже непонятного слова, потому что выглядит понятной.
|
||||||
|
|
||||||
|
**Слово, занятое в другом смысле, — то же нарушение.** Термин, который в
|
||||||
|
одном документе проекта значит одно, а здесь другое, ломает оба.
|
||||||
|
|
||||||
|
9. **Имя файла — английское слово по сути, а не транслит.** `queue-as-table`, а
|
||||||
|
не `ochered-tablicej`; `move-parse-strict`, а не `razbor-hoda`. Транслит
|
||||||
|
нечитаем тому, кто ищет по смыслу, и не сокращается, а имя стоит в ссылках,
|
||||||
|
коммитах и путях, которые набирают руками. Переименование — **перенос ссылок
|
||||||
|
одним проходом**, а не правка одного файла.
|
||||||
|
|
||||||
|
<!-- /копия: язык-правила -->
|
||||||
|
|
||||||
|
### Что из этих правил докладывается особым образом
|
||||||
|
|
||||||
|
**Правило 8, неизвестный термин.** Своей догадки не подставляй — ты не знаешь
|
||||||
|
предметную область. Пиши «термин «X» не встречается ни в паспорте, ни в
|
||||||
|
архитектуре, ни в конвенциях — введи строкой или назови известным словом».
|
||||||
|
Слово, занятое в другом смысле, — та же находка, и в ней **называются оба
|
||||||
|
места**: один документ канона, противоречащий другому словарём, ломает оба.
|
||||||
|
|
||||||
|
**Правило 9, имя файла.** Кириллицу в имени, не-kebab-case и форму имени ADR
|
||||||
|
ловит `docs.py` — про них молчи. Твоё — **транслит**, потому что машина
|
||||||
|
проверяет его эвристикой и ловит не всё: `sostoyanie-partii` проходит мимо неё.
|
||||||
|
Чаще всего он заводится в `docs/adr/` и `docs/research/`, где имя придумывают на
|
||||||
|
ходу. Находка — готовое английское имя на замену плюс напоминание про перенос
|
||||||
|
ссылок одним проходом.
|
||||||
|
|
||||||
|
## Чего ты не проверяешь
|
||||||
|
|
||||||
|
Не своё бывает двух разных родов, и поступают с ними по-разному.
|
||||||
|
|
||||||
|
**Чужому подрядчику — строкой в границах покрытия.** Согласованность документов
|
||||||
|
между собой (факт в двух домах, противоречие, поведение, осевшее в обзоре, ADR
|
||||||
|
без ссылки, число без провенанса) — у `doc-consistency`; соответствие документов
|
||||||
|
коду — у `doc-code-drift`; язык записей каталога задач — у `task-wording`, их
|
||||||
|
форма — у `task-form`. Увидел — назови в конце одной строкой, чтобы находка не
|
||||||
|
пропала, но находкой не оформляй.
|
||||||
|
|
||||||
|
**Машинной проверке — вообще ничего.** Всё, что ловит `docs.py check` (пути
|
||||||
|
канона, файлы вне канона, имена файлов, битые ссылки, версия канона, нетронутые
|
||||||
|
плейсхолдеры, маркеры долга) и что ловит `openspec.py check` скилла
|
||||||
|
`av-dev-code:openspec` (форма `openspec/config.yaml`), **не пиши даже
|
||||||
|
строкой**: это не потерянная находка, а уже проверенное. Повторять машинную
|
||||||
|
проверку словами — заводить второй дом для одного правила.
|
||||||
|
|
||||||
|
**Содержание**: верно ли решение, разумен ли инвариант, полна ли архитектура.
|
||||||
|
Это разбор, а не вычитка, — и о нём тоже молчи.
|
||||||
|
|
||||||
|
**Полезное действие, параллельность и работающий заголовок** — тоже не твои.
|
||||||
|
Они в доктрине языка, судит их человек: находка по ним требует увидеть текст
|
||||||
|
целиком, а не фразу.
|
||||||
|
|
||||||
|
## Порог вмешательства
|
||||||
|
|
||||||
|
<!-- копия: порог-правки из shared/language.md -->
|
||||||
|
|
||||||
|
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
||||||
|
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
||||||
|
перестают читать весь список, и вместе с ним пропадают настоящие находки.
|
||||||
|
Сомневаешься — не правь. Формулировка, которая просто **не твоя**, — не находка.
|
||||||
|
|
||||||
|
**Систематичность нарушения — не довод в его пользу.** Одна и та же ошибка в
|
||||||
|
пяти файлах не становится «принятым стилем»: чаще это значит, что правило не
|
||||||
|
применялось вовсе, — и находка тем важнее. «Так сделано везде» годится как
|
||||||
|
основание для **одной находки на весь набор** («правило N нарушено в пяти
|
||||||
|
записях, перечень: …»), но не как основание промолчать. Принятым считается
|
||||||
|
только то, что назвал зовущий или что записано в конвенциях проекта.
|
||||||
|
|
||||||
|
<!-- /копия: порог-правки -->
|
||||||
|
|
||||||
|
Один документ может дать несколько находок, но каждое место правится один раз:
|
||||||
|
не предлагай два варианта на выбор, предлагай лучший.
|
||||||
|
|
||||||
|
## Доклад
|
||||||
|
|
||||||
|
<!-- копия: вычитка-доклад из shared/language.md -->
|
||||||
|
|
||||||
|
Находки по одной, в порядке важности: залог и оценки → жаргон и англицизмы →
|
||||||
|
стоп-слова. Первые меняют, **что** читатель понимает; последние — только сколько
|
||||||
|
он на это тратит.
|
||||||
|
|
||||||
|
```
|
||||||
|
<файл>
|
||||||
|
правило: <номер и короткое имя>
|
||||||
|
сейчас: <как написано>
|
||||||
|
предложение: <готовая формулировка, подставляемая как есть>
|
||||||
|
почему: <одна фраза>
|
||||||
|
```
|
||||||
|
|
||||||
|
В конце — **границы покрытия**: сколько файлов просмотрено из скольких, какие не
|
||||||
|
смотрел и почему, и по чему проверялись термины (документы проекта названы или
|
||||||
|
нет). Отчёт без этой строки читается как «всё вычитано», не сообщая, какая часть
|
||||||
|
осталась нетронутой. Туда же — строка «замечено не по моей части»; машинно
|
||||||
|
проверяемое в неё **не идёт**.
|
||||||
|
|
||||||
|
Ничего не нашёл — так и скажи одной строкой. Пустой доклад с границами покрытия
|
||||||
|
полезнее выдуманной находки.
|
||||||
|
|
||||||
|
<!-- /копия: вычитка-доклад -->
|
||||||
@@ -24,7 +24,9 @@ description: Привести проект к канону документов
|
|||||||
информационный стиль, применённый к проектным текстам, таблицы англицизмов и
|
информационный стиль, применённый к проектным текстам, таблицы англицизмов и
|
||||||
жаргона. Раскладка отвечает, где текст лежит, — этот файл отвечает, каким он
|
жаргона. Раскладка отвечает, где текст лежит, — этот файл отвечает, каким он
|
||||||
должен быть. Правила общие для документов канона, задач, решений ADR и
|
должен быть. Правила общие для документов канона, задач, решений ADR и
|
||||||
записок разведки; вычитывает их отдельным проходом агент `doc-wording`.
|
записок разведки, и дом у них общий — `shared/language.md` в репозитории
|
||||||
|
плагинов, а этот файл его копия. Вычитывают их два прохода по охвату:
|
||||||
|
документы — `doc-wording`, записи каталога задач — `task-wording`.
|
||||||
- [references/changelog.md](references/changelog.md) — журнал версий канона.
|
- [references/changelog.md](references/changelog.md) — журнал версий канона.
|
||||||
|
|
||||||
## Три правила, из которых всё следует
|
## Три правила, из которых всё следует
|
||||||
@@ -47,18 +49,13 @@ ds="$CLAUDE_PLUGIN_ROOT/skills/canon/scripts/docs.py"
|
|||||||
|
|
||||||
python3 $ds check --dir <корень> [--base <rev>] # раскладка, ссылки, версия, сверки
|
python3 $ds check --dir <корень> [--base <rev>] # раскладка, ссылки, версия, сверки
|
||||||
python3 $ds version --dir <корень> # версия канона скрипта и проекта
|
python3 $ds version --dir <корень> # версия канона скрипта и проекта
|
||||||
python3 $ds openspec-form # форма config.yaml против живого OpenSpec
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**`openspec-form` зовут не на каждом прогоне, а когда о нём попросил `check`.**
|
**Формы `openspec/config.yaml` здесь больше нет.** Каталог принадлежит конвейеру,
|
||||||
Форма `openspec/config.yaml` описана в каноне слепком чужого инструмента — имя
|
и форму смотрит его скрипт — скилл `av-dev-code:openspec`, команда
|
||||||
схемы и перечень артефактов, — и слепок стареет молча: OpenSpec переименует
|
`openspec.py check`. Проект работает по OpenSpec, а плагина конвейера нет — форму
|
||||||
артефакт, правила под старым именем перестанут применяться, а конфиг останется
|
не проверяет никто, и это надо сказать строкой доклада, а не считать, что она
|
||||||
выглядеть написанным. Поэтому `check` каждым прогоном сравнивает `major.minor`
|
верна.
|
||||||
установленного OpenSpec с тем, на котором форма сверялась, и при расхождении
|
|
||||||
даёт замечание с этой командой. Команда ничего не правит: она спрашивает
|
|
||||||
инструмент и печатает, что разошлось. **Чинится это в плагине, а не в проекте** —
|
|
||||||
константы `docs.py`, скелет в `skeletons.md` и запись в журнал версий канона.
|
|
||||||
|
|
||||||
**Коды выхода — тот же словарь, что у `tasks.py`:** 0 сошлось, 1 дрейф, 2 ошибка
|
**Коды выхода — тот же словарь, что у `tasks.py`:** 0 сошлось, 1 дрейф, 2 ошибка
|
||||||
употребления, 3 окружение, 4 внутренний сбой. Ветвись на коде, а не на тексте.
|
употребления, 3 окружение, 4 внутренний сбой. Ветвись на коде, а не на тексте.
|
||||||
@@ -90,18 +87,53 @@ capability: незаполненный канон это переходное с
|
|||||||
формулировка казалась удачной при написании. Ни один из них ничего не правит —
|
формулировка казалась удачной при написании. Ни один из них ничего не правит —
|
||||||
оба возвращают готовые формулировки, подставляешь ты.
|
оба возвращают готовые формулировки, подставляешь ты.
|
||||||
|
|
||||||
|
## Обращение к соседним плагинам
|
||||||
|
|
||||||
|
`adopt` зовёт двоих: `av-dev-code:openspec` (шаг 4, пункт 3) и
|
||||||
|
`av-dev-tasks:tasks` (шаг 4, пункт 5). Каталоги `openspec/` и `tasks/` каноном не
|
||||||
|
ведутся, и трогать их этому скиллу нечем, кроме вызова.
|
||||||
|
|
||||||
|
**Копия.** Дом правила — `shared/plugin-boundary.md` в репозитории плагинов.
|
||||||
|
Правится дом, а не этот файл.
|
||||||
|
|
||||||
|
<!-- копия: граница-плагинов из shared/plugin-boundary.md -->
|
||||||
|
|
||||||
|
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
|
||||||
|
месте.
|
||||||
|
|
||||||
|
**Чужой скилл зовётся полным именем** — `av-dev-docs:canon`, `av-dev-tasks:tasks`,
|
||||||
|
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
|
||||||
|
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
|
||||||
|
|
||||||
|
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
|
||||||
|
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
|
||||||
|
прочитает его сам.
|
||||||
|
|
||||||
|
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
|
||||||
|
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
|
||||||
|
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
|
||||||
|
|
||||||
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||||
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
|
||||||
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
Чем оборачивается отсутствие каждого — на самих пунктах шага 4. `adopt` из-за
|
||||||
|
этого не останавливается ни в одном из двух случаев.
|
||||||
|
|
||||||
## `check`
|
## `check`
|
||||||
|
|
||||||
1. `docs.py check`, при наличии базы диффа — с `--base`.
|
1. `docs.py check`, при наличии базы диффа — с `--base`.
|
||||||
2. **Агентов на каждом `check` не зови.** Оба — `doc-consistency` и
|
2. **Судей документов на каждом `check` не зови.** Ими владеет отдельный скилл —
|
||||||
`doc-code-drift` — зовутся раз в спринт (шаг сессии), а также шагом 6 `adopt`
|
`av-dev-docs:healthcheck`, — и там же записано, когда его звать: он дорог, и
|
||||||
и шагом 6 `upgrade`, на весь канон разом. Они дороги: `doc-consistency` — тем,
|
прогон по каждому `check` не окупается. `check` отвечает на «сходится ли
|
||||||
что на `opus` (сличение утверждений это суждение), `doc-code-drift` — тем, что
|
форма», `healthcheck` — на «не разошлись ли утверждения».
|
||||||
читает репозиторий целиком, хотя сам идёт на `sonnet`. Позвал
|
3. Доклад: вывод скрипта строкой исхода и **граница покрытия** — что смотрели и
|
||||||
`doc-code-drift` — передай ему раздел запретов `CLAUDE.md`.
|
чего не смотрели. Если суждение здесь нужно, скажи это строкой и предложи
|
||||||
3. Доклад: вывод скрипта строкой исхода, находки агентов поимённо, **граница
|
`healthcheck`, а не зови агентов сам.
|
||||||
покрытия** — что смотрели и чего не смотрели, и **кого из двоих позвал**:
|
|
||||||
доклад, умолчавший об этом, читается как «сверено».
|
|
||||||
|
|
||||||
Дрейф раскладки чинится переносом; смысловые находки — это либо правка
|
Дрейф раскладки чинится переносом; смысловые находки — это либо правка
|
||||||
документа, либо задача, если работы больше чем на абзац.
|
документа, либо задача, если работы больше чем на абзац.
|
||||||
@@ -147,13 +179,14 @@ capability), `openspec/config.yaml`.
|
|||||||
1. `docs/.pm.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
|
1. `docs/.pm.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
|
||||||
2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**:
|
2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**:
|
||||||
незаполненное — одной честной информативной строкой, а не «TBD»;
|
незаполненное — одной честной информативной строкой, а не «TBD»;
|
||||||
3. **OpenSpec, если его нет** — `openspec init --tools claude`, и `config.yaml`
|
3. **OpenSpec, если его нет или `config.yaml` остался примером** — **вызови
|
||||||
по тому же скелету. Каталог есть, а `config.yaml` из коробки — тот же случай,
|
Skill `av-dev-code:openspec`**. Каталог принадлежит конвейеру, и команда
|
||||||
что отсутствие: закомментированный пример выглядит настройкой и не является
|
заведения с формой файла живут там. Пересказ инвариантов, конвенций и правил
|
||||||
ею. Пересказ инвариантов, конвенций и правил ревью из `context` вычисти
|
ревью из `context` вычисти ссылкой на дом — на переводимом проекте он там
|
||||||
ссылкой на дом — на переводимом проекте он там почти наверняка есть;
|
почти наверняка есть. Вызов не разрешился — `docs.py` о каталоге тогда тоже
|
||||||
|
молчит, и форму `config.yaml` не проверяет никто; скажи это строкой;
|
||||||
4. переносы содержимого;
|
4. переносы содержимого;
|
||||||
5. каталог задач — **вызови скилл `av-dev-pm:tasks`**, сценарий адаптации: он
|
5. каталог задач — **вызови скилл `av-dev-tasks:tasks`**, сценарий адаптации: он
|
||||||
владеет форматом задач. Он же переименует транслитные слаги в английские и
|
владеет форматом задач. Он же переименует транслитные слаги в английские и
|
||||||
тем же проходом починит перекрёстные ссылки;
|
тем же проходом починит перекрёстные ссылки;
|
||||||
6. починка ссылок на перенесённое во всём репозитории — `docs/`, `openspec/`,
|
6. починка ссылок на перенесённое во всём репозитории — `docs/`, `openspec/`,
|
||||||
@@ -164,16 +197,30 @@ capability), `openspec/config.yaml`.
|
|||||||
меняла `Taskfile`; шаг обязан **краснеть внятно**, если скрипт не найден, а не
|
меняла `Taskfile`; шаг обязан **краснеть внятно**, если скрипт не найден, а не
|
||||||
пропускаться. Передай ему базу диффа (`--base`) той же переменной, что и
|
пропускаться. Передай ему базу диффа (`--base`) той же переменной, что и
|
||||||
остальным шагам гейта: без неё сверка миграций со схемой не гоняется вовсе.
|
остальным шагам гейта: без неё сверка миграций со схемой не гоняется вовсе.
|
||||||
Пример строки покажи человеку — гейт принадлежит проекту, и правит его он;
|
Пример строки покажи человеку — гейт принадлежит проекту, и правит его он.
|
||||||
|
|
||||||
|
**Шагов в гейте три, и они независимы.** `docs.py check` не тянет за собой
|
||||||
|
ни задачи, ни конвейер: без своих строк дрейф каталога задач и формы
|
||||||
|
`openspec/config.yaml` перестаёт ловиться совсем. Ставь соседские шаги по
|
||||||
|
следу присутствия — `<каталог задач>/.tasks.json` есть, значит ставится
|
||||||
|
`tasks.py check --dir <каталог задач>`; `openspec/config.yaml` есть, значит
|
||||||
|
ставится `openspec.py check`. Следа нет — плагина в проекте нет, шаг не
|
||||||
|
ставится, и это **строка доклада**, а не поломка: назови, чего теперь не
|
||||||
|
проверяет никто. У каждого шага своя переменная пути с тем же умолчанием на
|
||||||
|
канонический путь маркетплейса; `$CLAUDE_PLUGIN_ROOT` в гейт не подставляй —
|
||||||
|
он ведёт только в свой плагин;
|
||||||
9. `docs.py check` — до **отсутствия дрейфа раскладки**. Замечания
|
9. `docs.py check` — до **отсутствия дрейфа раскладки**. Замечания
|
||||||
(незаполненные плейсхолдеры, слабое упоминание capability) остаются:
|
(незаполненные плейсхолдеры, слабое упоминание capability) остаются:
|
||||||
незаполненный канон это объявленное переходное состояние из шага 5, а не
|
незаполненный канон это объявленное переходное состояние из шага 5, а не
|
||||||
отказ. **Пункт «задачи без цели» из вложенной проверки `tasks.py` тоже
|
отказ. Пересчитай эти пункты в докладе переходного состояния — не выдавай
|
||||||
остаётся** и зелёным на этом шаге не станет: цели не сочиняются адаптацией
|
их за поломку и не молчи о них.
|
||||||
(запрет в [tasks/references/adopt.md](../tasks/references/adopt.md)), их
|
|
||||||
проставляет человек порциями переоценки на первой сессии. Пересчитай эти
|
**Задачи `docs.py` не проверяет** — их ведёт другой плагин, и согласованность
|
||||||
пункты в докладе переходного состояния — не выдавай их за поломку и не
|
каталога показывает только `tasks.py check`. Позвал на шаге 5 скилл задач —
|
||||||
молчи о них.
|
его отчёт идёт в доклад отдельной строкой, и пункт «задачи без цели» в нём
|
||||||
|
зелёным не станет: цели не сочиняются адаптацией (запрет записан у того, кто
|
||||||
|
ведёт задачи), их проставляет человек порциями переоценки на первом груминге —
|
||||||
|
скилл `av-dev-tasks:groom`.
|
||||||
|
|
||||||
### 5. Объяви переходное состояние
|
### 5. Объяви переходное состояние
|
||||||
|
|
||||||
@@ -192,12 +239,24 @@ capability), `openspec/config.yaml`.
|
|||||||
проекте обычно самый урожайный — правило единственного дома до адаптации никто не
|
проекте обычно самый урожайный — правило единственного дома до адаптации никто не
|
||||||
проверял.
|
проверял.
|
||||||
|
|
||||||
Зови **`doc-consistency`** (документы между собой и с openspec) и
|
Вызови Skill **`av-dev-docs:healthcheck`** — он зовёт обоих судей на весь канон
|
||||||
**`doc-code-drift`** (факты против кода). Разбирай порциями, а не одним заходом.
|
разом и держит разбор урожая порциями.
|
||||||
|
|
||||||
**Передай им объявленное переходное состояние из шага 5** — иначе честная строка
|
**Передай им объявленное переходное состояние из шага 5** — иначе честная строка
|
||||||
в незаполненном слоте вернётся находкой, а это не поломка, а объявленный долг.
|
в незаполненном слоте вернётся находкой, а это не поломка, а объявленный долг.
|
||||||
|
|
||||||
|
### 7. Вычитай написанное — агент `doc-wording`
|
||||||
|
|
||||||
|
Судьи смотрят утверждения, а `adopt` только что **писал текст**: честные строки
|
||||||
|
в пустые слоты, переписанные при переносе абзацы, шапки перенесённых документов.
|
||||||
|
Язык этого текста не проверяет никто другой, а зовущий здесь по определению тот,
|
||||||
|
кто его и написал.
|
||||||
|
|
||||||
|
Позови агента **по названной пачке** — документы, которые ты завёл или правил,
|
||||||
|
плюс перенесённые целиком. Весь канон ему не нужен: он работает по списку, и
|
||||||
|
список же служит ему словарём терминов. Находки — готовые формулировки,
|
||||||
|
подставляешь их ты.
|
||||||
|
|
||||||
## `upgrade` — канон вырос
|
## `upgrade` — канон вырос
|
||||||
|
|
||||||
1. `docs.py version` — версия проекта и версия скрипта.
|
1. `docs.py version` — версия проекта и версия скрипта.
|
||||||
@@ -208,7 +267,11 @@ capability), `openspec/config.yaml`.
|
|||||||
применяются по порядку.
|
применяются по порядку.
|
||||||
4. Подними `canon` в `docs/.pm.json` до текущей.
|
4. Подними `canon` в `docs/.pm.json` до текущей.
|
||||||
5. `docs.py check`.
|
5. `docs.py check`.
|
||||||
6. **Позови обоих судей** — `doc-consistency` и `doc-code-drift`.
|
6. **Позови судей** — Skill `av-dev-docs:healthcheck`.
|
||||||
|
7. **Позови вычитку** — агент `doc-wording`, но **только по тем документам,
|
||||||
|
которых записи журнала коснулись**, и только если правка была текстовой, а не
|
||||||
|
переименованием файла. Записи журнала пишутся руками в проектной прозе, и
|
||||||
|
дописанный по журналу раздел — такой же свежий текст, как на синке.
|
||||||
|
|
||||||
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
|
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
|
||||||
это дефект журнала, и о нём надо сказать, а не догадываться.
|
это дефект журнала, и о нём надо сказать, а не догадываться.
|
||||||
+87
-97
@@ -1,6 +1,6 @@
|
|||||||
# Канон документов проекта
|
# Канон документов проекта
|
||||||
|
|
||||||
**Версия 7.**
|
**Версия 12.**
|
||||||
|
|
||||||
Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs`
|
Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs`
|
||||||
читают его, а не пересказывают: три описания одной раскладки разъедутся, и
|
читают его, а не пересказывают: три описания одной раскладки разъедутся, и
|
||||||
@@ -24,7 +24,13 @@
|
|||||||
|
|
||||||
## Сопровождение и эксплуатация — целое и часть
|
## Сопровождение и эксплуатация — целое и часть
|
||||||
|
|
||||||
Одна тема живёт в трёх местах канона, и путать их слова нельзя.
|
**Копия.** Дом — `shared/operations.md` в репозитории плагинов: словарь
|
||||||
|
делят роадмап, архитектура и тема ревью `operations`, то есть три плагина,
|
||||||
|
и ни один из трёх им не владеет. Правится дом, а не этот файл.
|
||||||
|
|
||||||
|
<!-- копия: сопровождение-словарь из shared/operations.md -->
|
||||||
|
|
||||||
|
Одна тема живёт в трёх местах, и путать их слова нельзя.
|
||||||
|
|
||||||
**Сопровождение** — всё, чем держат проект: инструмент и сборка, процесс,
|
**Сопровождение** — всё, чем держат проект: инструмент и сборка, процесс,
|
||||||
выкладка, метрики и логи, инфраструктура, дежурство. **Эксплуатация** — его
|
выкладка, метрики и логи, инфраструктура, дежурство. **Эксплуатация** — его
|
||||||
@@ -45,6 +51,8 @@
|
|||||||
экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в разные
|
экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в разные
|
||||||
секции роадмапа, и это верно — секции отвечают на разные вопросы.
|
секции роадмапа, и это верно — секции отвечают на разные вопросы.
|
||||||
|
|
||||||
|
<!-- /копия: сопровождение-словарь -->
|
||||||
|
|
||||||
## Раскладка
|
## Раскладка
|
||||||
|
|
||||||
**Документ канона живёт файлом или каталогом.** `docs/security.md` и
|
**Документ канона живёт файлом или каталогом.** `docs/security.md` и
|
||||||
@@ -67,8 +75,8 @@ docs/
|
|||||||
adr.md | adr/ почему решено так; статусы, правило замены
|
adr.md | adr/ почему решено так; статусы, правило замены
|
||||||
review.md | review/ настройка конвейера под проект + журнал дефектов
|
review.md | review/ настройка конвейера под проект + журнал дефектов
|
||||||
<своя тема>.md | <своя тема>/ всё, что проект счёл нужным проверять
|
<своя тема>.md | <своя тема>/ всё, что проект счёл нужным проверять
|
||||||
tasks/ скилл tasks: items/, ROADMAP.md, BACKLOG.md,
|
tasks/ каталог задач — плагин av-dev-tasks, не канон;
|
||||||
SPRINT.md, REJECTED.md
|
лежит в корне, вне docs/, и канон его не требует
|
||||||
openspec/
|
openspec/
|
||||||
config.yaml только нужды генерации артефактов + ссылки
|
config.yaml только нужды генерации артефактов + ссылки
|
||||||
specs/<capability>/spec.md что система делает — нормативно
|
specs/<capability>/spec.md что система делает — нормативно
|
||||||
@@ -106,8 +114,8 @@ openspec/
|
|||||||
| `database.*` | источник | `operations` — схема и настройки с числами |
|
| `database.*` | источник | `operations` — схема и настройки с числами |
|
||||||
| `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные |
|
| `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные |
|
||||||
| `openspec/specs/` | источник | `requirements` |
|
| `openspec/specs/` | источник | `requirements` |
|
||||||
| `openspec/config.yaml` | процессный | — (настройка порождения артефактов, слой **до** тем) |
|
| `openspec/config.yaml` | процессный | — (настройка порождения артефактов, слой **до** тем; заводит конвейер) |
|
||||||
| `tasks/` | процессный | — |
|
| `tasks/` | процессный | — (чужое владение: плагин `av-dev-tasks`) |
|
||||||
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
|
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
|
||||||
| `adr.*` | процессный | — |
|
| `adr.*` | процессный | — |
|
||||||
| `research.*` | процессный | — |
|
| `research.*` | процессный | — |
|
||||||
@@ -141,8 +149,8 @@ openspec/
|
|||||||
Цена этого решения записана, а не подразумевается: **расхождение изменения с
|
Цена этого решения записана, а не подразумевается: **расхождение изменения с
|
||||||
записанным решением прогоном больше не ловится.** Раньше архитектурный проход
|
записанным решением прогоном больше не ловится.** Раньше архитектурный проход
|
||||||
читал `adr/` и мог сказать «здесь отменено решение ADR-2026-03-11, а парного
|
читал `adr/` и мог сказать «здесь отменено решение ADR-2026-03-11, а парного
|
||||||
статуса нет»; теперь это скажет только `doc-consistency` на сессии между
|
статуса нет»; теперь это скажет только `doc-consistency`, а зовёт его скилл
|
||||||
спринтами. Сделка сознательная — ADR объясняет прошлое, а не предъявляет
|
`healthcheck`. Сделка сознательная — ADR объясняет прошлое, а не предъявляет
|
||||||
требование к изменению, и чтение всего каталога решений на каждой задаче
|
требование к изменению, и чтение всего каталога решений на каждой задаче
|
||||||
оплачивалось на каждой, а срабатывало на единицах.
|
оплачивалось на каждой, а срабатывало на единицах.
|
||||||
|
|
||||||
@@ -176,7 +184,7 @@ kebab-case.** Причина не эстетическая: имя файла с
|
|||||||
какую тему питает. **Кто именно закрывает тему, здесь не указано намеренно**: это
|
какую тему питает. **Кто именно закрывает тему, здесь не указано намеренно**: это
|
||||||
зависит от метки прогона и меняется вместе с конвейером, а документ живёт
|
зависит от метки прогона и меняется вместе с конвейером, а документ живёт
|
||||||
дольше. Раскладку «тема → проход → глубина» держит скилл
|
дольше. Раскладку «тема → проход → глубина» держит скилл
|
||||||
`av-dev-pipeline:review-pipeline`.
|
`av-dev-code:review`.
|
||||||
|
|
||||||
**Общего словаря у канона с конвейером ровно три вида имён: имена категорий,
|
**Общего словаря у канона с конвейером ровно три вида имён: имена категорий,
|
||||||
имена тем и имена меток.** Категорий три — `тема`, `источник`, `процессный`;
|
имена тем и имена меток.** Категорий три — `тема`, `источник`, `процессный`;
|
||||||
@@ -345,9 +353,16 @@ kebab-case.** Причина не эстетическая: имя файла с
|
|||||||
|
|
||||||
### `tasks/`
|
### `tasks/`
|
||||||
|
|
||||||
Раскладку, форму записи и команды держит скилл `tasks` — канон фиксирует имена
|
**Каталог задач канону не принадлежит.** Его ведёт отдельный плагин
|
||||||
файлов (`items/`, `ROADMAP.md`, `BACKLOG.md`, `SPRINT.md`, `REJECTED.md`) и то,
|
`av-dev-tasks` — своим скриптом, своим конфигом `<каталог>/.tasks.json` и своей
|
||||||
от чего зависит, читается ли проект как продукт.
|
версией формата. Канон **резервирует место** в `docs/` и внутрь не смотрит:
|
||||||
|
`docs.py` каталог не открывает, его отсутствия не считает дрейфом и согласованность
|
||||||
|
задач не проверяет. Проект, поставивший только канон документов, задач не ведёт
|
||||||
|
вовсе, и отказом это быть не может.
|
||||||
|
|
||||||
|
Раскладку, форму записи и команды держит скилл `av-dev-tasks:tasks`. Ниже — то,
|
||||||
|
от чего зависит, читается ли проект как продукт: канон высказывается об этом
|
||||||
|
потому, что роадмап отвечает на вопрос о **системе**, а не о работах.
|
||||||
|
|
||||||
**`ROADMAP.md` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это
|
**`ROADMAP.md` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это
|
||||||
не очередь работ: цель — **возможность приложения**, задача — шаг к ней.
|
не очередь работ: цель — **возможность приложения**, задача — шаг к ней.
|
||||||
@@ -370,23 +385,25 @@ kebab-case.** Причина не эстетическая: имя файла с
|
|||||||
| 🔬 `research` | исход — знание, а не изменение |
|
| 🔬 `research` | исход — знание, а не изменение |
|
||||||
|
|
||||||
**Схемы записи здесь нет намеренно.** Какие разделы тип требует, нужна ли ему
|
**Схемы записи здесь нет намеренно.** Какие разделы тип требует, нужна ли ему
|
||||||
цель и берётся ли он в спринт — скилл `tasks`: сводка в его
|
цель и берётся ли он в работу — скилл `av-dev-tasks:tasks`, раздел «Тип
|
||||||
[SKILL.md](../../tasks/SKILL.md), раздел «Тип записи», подробно — по файлу на
|
записи», подробно — по файлу на тип в его `references/task-<тип>.md`. Ссылки в
|
||||||
тип в `tasks/references/task-<тип>.md`. Канон фиксирует **словарь**, потому что
|
дерево того плагина здесь нет намеренно: он ставится отдельно, и путь наружу
|
||||||
|
разрешился бы не всегда. Канон фиксирует **словарь**, потому что
|
||||||
от него зависит, читается ли проект как продукт; схема — механика ведения задач,
|
от него зависит, читается ли проект как продукт; схема — механика ведения задач,
|
||||||
и второй её экземпляр разошёлся бы с первым (он и разошёлся: канон успел
|
и второй её экземпляр разошёлся бы с первым (он и разошёлся: канон успел
|
||||||
объявить цель у `fix` запрещённой, хотя она там необязательна).
|
объявить цель у `fix` запрещённой, хотя она там необязательна).
|
||||||
|
|
||||||
Схема требуется **к взятию в спринт**, а не к заведению: беклог пополняется чаще,
|
Схема требуется **к взятию в работу**, а не к заведению: беклог пополняется чаще,
|
||||||
чем разбирается, и требование на входе выгоняло бы в заметки то, что должно
|
чем разбирается, и требование на входе выгоняло бы в заметки то, что должно
|
||||||
лежать задачей. Запись, не собравшая разделы своего типа, — законное состояние
|
лежать задачей. Запись, не собравшая разделы своего типа, — законное состояние
|
||||||
беклога; невзятой её делает `sprint take`.
|
беклога; невзятой её делает `tasks.py ready`.
|
||||||
|
|
||||||
Отдельного типа для незаполненной записи нет: «ещё не описано» — состояние, а не
|
Отдельного типа для незаполненной записи нет: «ещё не описано» — состояние, а не
|
||||||
род работы, и называется оно **`research` без раздела «Вопрос»**. Такая запись в
|
род работы, и называется оно **`research` без раздела «Вопрос»**. Такая запись в
|
||||||
спринт не берётся и лежит в конце своей категории.
|
работу не берётся и лежит в конце своей категории.
|
||||||
|
|
||||||
Раскладку, форму записи и алгоритм работы над каждым типом держит скилл `tasks`.
|
Раскладку, форму записи и алгоритм работы над каждым типом держит скилл
|
||||||
|
`av-dev-tasks:tasks`.
|
||||||
|
|
||||||
### `CLAUDE.md`
|
### `CLAUDE.md`
|
||||||
|
|
||||||
@@ -399,66 +416,37 @@ kebab-case.** Причина не эстетическая: имя файла с
|
|||||||
Плюс то, что нужно git-операциям и проходам и не выводится ниоткуда:
|
Плюс то, что нужно git-операциям и проходам и не выводится ниоткуда:
|
||||||
|
|
||||||
- **имя основной ветки** — от неё считается база диффа
|
- **имя основной ветки** — от неё считается база диффа
|
||||||
(`git merge-base HEAD <ветка>`), в неё вливает батч, от неё ветвятся задачи.
|
(`git merge-base HEAD <ветка>`), в неё коммитит работу конвейер.
|
||||||
Угадывание между `master` и `main` ломает интеграцию целиком;
|
Угадывание между `master` и `main` ломает интеграцию целиком;
|
||||||
- **что запускать запрещено, с путями** — рабочая БД, боевой каталог данных,
|
- **что запускать запрещено, с путями** — рабочая БД, боевой каталог данных,
|
||||||
внешние сервисы. Запретом с путями, а не «будь осторожен»;
|
внешние сервисы. Запретом с путями, а не «будь осторожен»;
|
||||||
- **где `testdata`** и что в них лежит; **куда писать временное**;
|
- **где `testdata`** и что в них лежит; **куда писать временное**;
|
||||||
- **что считается необратимым** — единственный дом: от обратимости зависит вся
|
- **что считается необратимым** — единственный дом: от обратимости зависит вся
|
||||||
шкала ранжирования триажа и право проходов на `critical`;
|
шкала ранжирования триажа и право проходов на `critical`;
|
||||||
- **общий станок**, врывающийся в замороженный спринт; **ориентир по размеру
|
- **что считается сломанным** — красная проверка, обгоняющая развитие;
|
||||||
спринта**.
|
**ориентир по размеру порции**, если он замерялся. Оба слота читает скилл
|
||||||
|
`av-dev-tasks:groom`, и имена их — его; названы они здесь потому, что дом
|
||||||
|
содержимого `CLAUDE.md` один и он тут.
|
||||||
|
|
||||||
### `openspec/config.yaml`
|
### `openspec/config.yaml`
|
||||||
|
|
||||||
**Только нужды генерации артефактов** — язык, правила именования capability,
|
**Файл канону не принадлежит, и проверяет его тоже не канон.** Каталог
|
||||||
придирки валидатора RFC 2119 — плюс **адреса** документов канона. Правило ревью,
|
`openspec/` — предпосылка конвейера: без него не работают ни `opsx:propose`, ни
|
||||||
пересказ конвенций и инварианты сюда не пишутся: у них есть свои дома, и второй
|
ревью дизайна, ни сверка требований. Заводит его, настраивает и **проверяет
|
||||||
дом разойдётся на первой же правке.
|
форму** скилл `av-dev-code:openspec`: там образец файла, там же скрипт
|
||||||
|
`openspec.py check`. `docs.py` о файле не говорит ничего.
|
||||||
|
|
||||||
**Каталог `openspec/` — часть канона, а не соседняя технология.** В нём дом темы
|
Канон называет его здесь по одной причине: `openspec/specs/` — **дом темы
|
||||||
`requirements`, и заводится он командой: `openspec init --tools claude`. Её
|
`requirements`**, и без этой строки карта тем неполна. На форму самого
|
||||||
выполняет `init` на новом проекте и `adopt` на переводимом; из канона она названа
|
`config.yaml` канон не высказывается.
|
||||||
поимённо потому, что её печатает отказ `docs.py`, а отказ без команды заставляет
|
|
||||||
искать её в другом месте.
|
|
||||||
|
|
||||||
**Файл из коробки настройкой не является.** `openspec init` кладёт `config.yaml`,
|
**Одно за каноном всё же остаётся, и это не форма, а единственный дом.** Блок
|
||||||
где и `context`, и `rules` лежат закомментированным примером. Такой файл читается
|
`context` — самое частое место для второго дома: он читается при порождении
|
||||||
как настроенный — он есть, он валиден, у него правильное имя, — а работает как
|
каждого артефакта, туда удобно дописать «чтобы агент знал», и так заводятся копии
|
||||||
пустой: предложение пишется без языка, без правил именования capability и без
|
инвариантов, конвенций, состава гейта и правил ревью. Расходятся они молча.
|
||||||
знания, где лежит граница домена. Это ровно тот класс, против которого написан
|
Разрез: **утверждение, которое можно опровергнуть, открыв другой файл проекта, —
|
||||||
весь канон, и потому здесь он проверяется машиной, а не чтением.
|
пересказ; строка, которая говорит, какой файл открыть, — ссылка.** Машина этого
|
||||||
|
не различает; судит агент `doc-consistency`, и `config.yaml` у него во входе.
|
||||||
Проверяется пять вещей, и каждая — про молчащий пробел, а не про вкус:
|
|
||||||
|
|
||||||
1. **`openspec/` есть.** Нет — нет и дома темы `requirements`.
|
|
||||||
2. **Имя файла `config.yaml`.** `config.yml` OpenSpec не читает и об этом не
|
|
||||||
сообщает: настройка, написанная в файл с таким именем, пропадает целиком.
|
|
||||||
3. **`context` и `rules.specs` не остались примером.** Правила для `specs`
|
|
||||||
обязаны называть `SHALL`: требование без этого литерала валидатор отвергает.
|
|
||||||
4. **`context` называет `passport` и `CLAUDE.md`.** Предложение пишется **до**
|
|
||||||
того, как кто-либо откроет `docs/`; без этих двух адресов его пишут, не зная
|
|
||||||
ни границы домена, ни инвариантов.
|
|
||||||
5. **Ключи под `rules:` — имена артефактов схемы** (`proposal`, `specs`,
|
|
||||||
`design`, `tasks`). Правило, адресованное несуществующему артефакту, не
|
|
||||||
применяется и об этом молчит: `rules.spec` вместо `rules.specs` — конфиг,
|
|
||||||
выглядящий написанным и не работающий.
|
|
||||||
|
|
||||||
**Схема и перечень артефактов — слепок чужого инструмента, и он стареет.**
|
|
||||||
OpenSpec переименует артефакт или сменит схему — правила под прежним именем
|
|
||||||
перестанут действовать молча, а канон будет продолжать требовать прежнее.
|
|
||||||
Поэтому за свежестью слепка следит машина: `check` сравнивает `major.minor`
|
|
||||||
установленного OpenSpec с версией, на которой форма сверялась, и при расхождении
|
|
||||||
даёт **замечание** (не отказ: патч-версии формы не меняют, а нагоняй на каждый
|
|
||||||
багфикс приучает пролистывать блок). Перепроверяет `docs.py openspec-form` — он
|
|
||||||
спрашивает сам инструмент и печатает, что разошлось. **Чинится это в плагине, а
|
|
||||||
не в проекте:** константы скрипта, скелет и запись в журнал версий канона.
|
|
||||||
|
|
||||||
Шестого — «нет ли здесь пересказа» — машина не проверяет: отличить ссылку от
|
|
||||||
пересказа она не умеет. Это работа `doc-consistency`, и раздел «Что проверяет
|
|
||||||
машина, а что человек» называет её строкой.
|
|
||||||
|
|
||||||
Форма — [skeletons.md](skeletons.md).
|
|
||||||
|
|
||||||
## Правило единственного дома
|
## Правило единственного дома
|
||||||
|
|
||||||
@@ -471,14 +459,14 @@ OpenSpec переименует артефакт или сменит схему
|
|||||||
| почему решено так | `adr/`, источник — архивный `design.md` |
|
| почему решено так | `adr/`, источник — архивный `design.md` |
|
||||||
| граница домена, «чем не является» | `passport.md` |
|
| граница домена, «чем не является» | `passport.md` |
|
||||||
| инвариант и его severity | `CLAUDE.md` |
|
| инвариант и его severity | `CLAUDE.md` |
|
||||||
| что приложение умеет и чего не умеет; порядок работ | `docs/tasks/ROADMAP.md` |
|
| что приложение умеет и чего не умеет; порядок работ | `tasks/ROADMAP.md` |
|
||||||
| измеренное число | `research/` |
|
| измеренное число | `research/` |
|
||||||
| настройка с числовым значением | `database.md` |
|
| настройка с числовым значением | `database.md` |
|
||||||
| периметр и модель угроз | `security.md` |
|
| периметр и модель угроз | `security.md` |
|
||||||
| что необратимо | `CLAUDE.md` — **не** `architecture.md` |
|
| что необратимо | `CLAUDE.md` — **не** `architecture.md` |
|
||||||
| единые точки проекта | `architecture.md` |
|
| единые точки проекта | `architecture.md` |
|
||||||
| имя основной ветки, `testdata`, временный каталог | `CLAUDE.md` |
|
| имя основной ветки, `testdata`, временный каталог | `CLAUDE.md` |
|
||||||
| что уже механизировано правилом | `conventions/README.md` |
|
| что уже механизировано правилом | `conventions.*`, раздел «Механизировано» |
|
||||||
<!-- /дом: карта-домов -->
|
<!-- /дом: карта-домов -->
|
||||||
|
|
||||||
## Пустое называется пустым
|
## Пустое называется пустым
|
||||||
@@ -506,9 +494,9 @@ OpenSpec переименует артефакт или сменит схему
|
|||||||
| `docs/review-brief.md` | документы канона и есть бриф; остаток — в `review.md` |
|
| `docs/review-brief.md` | документы канона и есть бриф; остаток — в `review.md` |
|
||||||
| `docs/specs/` | `openspec/specs/` (поведение) и `architecture.md` (обзор) |
|
| `docs/specs/` | `openspec/specs/` (поведение) и `architecture.md` (обзор) |
|
||||||
| `docs/drafts/` | идея → запись `research`; отказ → ADR; порядок → `ROADMAP.md`; размышление → `opsx:explore` |
|
| `docs/drafts/` | идея → запись `research`; отказ → ADR; порядок → `ROADMAP.md`; размышление → `opsx:explore` |
|
||||||
| `docs/plan.md` | `docs/tasks/ROADMAP.md` |
|
| `docs/plan.md` | `tasks/ROADMAP.md` |
|
||||||
| `BRIEF.md` | `passport.md` |
|
| `BRIEF.md` | `passport.md` |
|
||||||
| `docs/backlog/` | `docs/tasks/` |
|
| `docs/backlog/` | `tasks/` в корне репозитория |
|
||||||
| `docs/review-journal.md`, `docs/review/journal.md` | `docs/review.md` |
|
| `docs/review-journal.md`, `docs/review/journal.md` | `docs/review.md` |
|
||||||
|
|
||||||
## Что проверяет машина, а что человек
|
## Что проверяет машина, а что человек
|
||||||
@@ -527,17 +515,25 @@ OpenSpec переименует артефакт или сменит схему
|
|||||||
| маркеры долга — числом | **протухший факт, разошедшийся с кодом** | `doc-code-drift` |
|
| маркеры долга — числом | **протухший факт, разошедшийся с кодом** | `doc-code-drift` |
|
||||||
| миграция изменена, а `database.md` нет | зависимость в манифесте, не названная в обзоре | `doc-code-drift` |
|
| миграция изменена, а `database.md` нет | зависимость в манифесте, не названная в обзоре | `doc-code-drift` |
|
||||||
| capability без упоминания в `architecture.md` | второй способ там, где обзор обещал единственный | `doc-code-drift` |
|
| capability без упоминания в `architecture.md` | второй способ там, где обзор обещал единственный | `doc-code-drift` |
|
||||||
| `openspec/config.yaml`: имя, `schema`, незаменённый пример, адреса паспорта и `CLAUDE.md`, ключи `rules` против артефактов схемы | **пересказ документа канона в `context` вместо ссылки** | `doc-consistency` |
|
| | **пересказ документа канона в `context` вместо ссылки** | `doc-consistency` |
|
||||||
| версия OpenSpec разошлась с той, на которой сверена форма `config.yaml` | придирки валидатора: сменились ли они | никакой — проявляются отказом `openspec validate --strict` |
|
| | придирки валидатора: сменились ли они | никакой — проявляются отказом `openspec validate --strict` |
|
||||||
| | связность и читаемость | `doc-wording` |
|
| | связность и читаемость | `doc-wording` |
|
||||||
|
|
||||||
|
**Форма `openspec/config.yaml` в левой колонке отсутствует не по забывчивости.**
|
||||||
|
С канона 10 `docs.py` о файле не говорит ничего: имя, `schema`, незаменённый
|
||||||
|
пример, адреса паспорта и `CLAUDE.md`, ключи `rules` и сторож версии OpenSpec —
|
||||||
|
всё это смотрит `openspec.py check` скилла `av-dev-code:openspec`. Плагина
|
||||||
|
конвейера в проекте может не быть; тогда форму не проверяет никто, и это строка
|
||||||
|
доклада.
|
||||||
|
|
||||||
**Агентов двое, и разведены они по глубине, а не по охвату.** `doc-consistency`
|
**Агентов двое, и разведены они по глубине, а не по охвату.** `doc-consistency`
|
||||||
читает только `docs/` и `openspec/`, `doc-code-drift` — весь репозиторий и гоняет
|
читает только `docs/` и `openspec/`, `doc-code-drift` — весь репозиторий и гоняет
|
||||||
читающие команды. Слитый агент делал бы одну половину поверхностной; тот же
|
читающие команды. Слитый агент делал бы одну половину поверхностной; тот же
|
||||||
разрез, что между `task-form` и `doc-wording`.
|
разрез, что между `task-form` и `task-wording`.
|
||||||
|
|
||||||
**Зовутся оба одинаково — раз в спринт на сессии, а также после `adopt` и после
|
**Зовутся оба одинаково и одним скиллом — `av-dev-docs:healthcheck`, на весь
|
||||||
`upgrade`, на весь канон разом.** Не на синке документации: `doc-consistency` на
|
канон разом; шагом `adopt` и шагом `upgrade` его зовёт `canon`.** Не на синке
|
||||||
|
документации: `doc-consistency` на
|
||||||
`opus` по каждой сделанной задаче не окупается, а расхождение между двумя документами по
|
`opus` по каждой сделанной задаче не окупается, а расхождение между двумя документами по
|
||||||
определению требует двух, и на большинстве задач синк правит один. Пачка,
|
определению требует двух, и на большинстве задач синк правит один. Пачка,
|
||||||
отбираемая работой, вдобавок не видит того, чего работа не касалась, — а именно
|
отбираемая работой, вдобавок не видит того, чего работа не касалась, — а именно
|
||||||
@@ -554,30 +550,24 @@ OpenSpec переименует артефакт или сменит схему
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"canon": 7,
|
"canon": <текущая версия>,
|
||||||
"migrations": "internal/store/migrations",
|
"migrations": "internal/store/migrations"
|
||||||
"tasks": {
|
|
||||||
"backlog": "INDEX.md"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`canon` — версия канона, под которую проект приведён, целым числом: обратной
|
`canon` — версия канона, под которую проект приведён, целым числом: обратной
|
||||||
совместимости у канона нет, есть «приведён» и «не приведён». `migrations` — путь
|
совместимости у канона нет, есть «приведён» и «не приведён». Число подставляет
|
||||||
каталога миграций, если БД есть; по нему `docs.py` делает сверку с
|
`init`, `adopt` или `upgrade`, и берётся оно из `docs.py version`, а не из
|
||||||
`database.md`. `tasks` — настройки каталога задач, переехавшие сюда из прежнего
|
образца: литерал в образце протухает на первом же повышении канона.
|
||||||
`<tasks>/.tasks.json`: **один конфиг на весь канон, а не по одному на каталог**.
|
`migrations` — путь каталога миграций, если БД есть; по нему `docs.py` делает
|
||||||
Внутри `tasks` — **только имена файлов и заголовков** (`items`, `backlog`,
|
сверку с `database.md`.
|
||||||
`roadmap`, `sprint`, `rejected`, `sprint_section`, `oracle_word` и заголовки
|
|
||||||
разделов тела: `criteria_heading`, `surface_heading`, `questions_heading`,
|
**Ключа `tasks` здесь больше нет.** Настройки каталога задач вернулись в свой
|
||||||
`completion_heading`, `repro_heading`, `question_heading`, `answer_heading`,
|
файл `<каталог задач>/.tasks.json`, потому что ведёт их другой плагин: конфиг,
|
||||||
`scope_heading`), и ключ пишется, лишь когда имя отличается от умолчания.
|
лежащий в `docs/`, был бы домом, которого нет у проекта, поставившего учёт работ
|
||||||
**Словаря типов здесь нет** — он закрыт каноном, а не настраивается проектом:
|
без канона документов. Состав ключей описывает тот плагин, а не канон. Прежний
|
||||||
настраиваемый словарь типов разъехался бы на синонимах ровно так же, как
|
ключ читается, пока живы непереехавшие проекты, и `tasks.py` говорит о нём
|
||||||
открытый. **Категорий беклога здесь тоже нет:** их дом — заголовки `##` самого
|
замечанием на каждом прогоне — версия 8 журнала просит его убрать.
|
||||||
индекса, и второй список сразу разошёлся бы с первым. Неизвестный ключ `tasks.py`
|
|
||||||
отвергает кодом 3, поэтому лишнее слово в этом объекте останавливает работу с
|
|
||||||
задачами целиком.
|
|
||||||
|
|
||||||
Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py`
|
Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py`
|
||||||
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
|
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
|
||||||
+187
-1
@@ -13,6 +13,192 @@ upgrade` идёт по записям снизу вверх от версии п
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Версия 12 — 2026-08-09
|
||||||
|
|
||||||
|
Спринты отменены. Работа идёт задача за задачей, и замороженный набор перестал
|
||||||
|
что-либо удерживать: он отвечал на вопрос «что делать дальше», а между наборами
|
||||||
|
на этот вопрос не отвечал никто.
|
||||||
|
|
||||||
|
**Что изменилось.** Индексов задач два вместо трёх: `SPRINT.md` упразднён.
|
||||||
|
Приоритет стал тем, чем он и является, — **порядком строк в `BACKLOG.md`**:
|
||||||
|
первая строка секции это то, что делают следующим. Назначает порядок человек,
|
||||||
|
машина его не выводит; двигают его `move --after` и `move --first` с причиной.
|
||||||
|
|
||||||
|
Гейт готовности записи стоял на взятии задачи в спринт — единственном месте, где
|
||||||
|
её судили целиком. Момент нужен и без спринта: теперь это команда
|
||||||
|
`tasks.py ready <слаг>`, и зовёт её тот, кто берёт задачу в работу.
|
||||||
|
|
||||||
|
Ритуал между спринтами (`av-dev-tasks:session`) стал скиллом груминга
|
||||||
|
(`av-dev-tasks:groom`): два вопроса — что сейчас самое важное и что перестало
|
||||||
|
быть важным.
|
||||||
|
|
||||||
|
**Что сделать проекту.**
|
||||||
|
|
||||||
|
1. **Вернуть задачи из набора в беклог и снести `SPRINT.md`.** Порядок такой:
|
||||||
|
`git rm tasks/SPRINT.md`, затем `tasks.py check --dir tasks --fix`. Строки
|
||||||
|
набора после удаления файла становятся бездомными, и `--fix` возвращает их в
|
||||||
|
беклог **в конец своей секции** — с пометкой, что позицию назначает человек.
|
||||||
|
Наоборот делать нельзя: `check` без удалённого файла увидит третий индекс и
|
||||||
|
станет ругаться на него, а не чинить.
|
||||||
|
2. **Снять теги `sprint:<слаг>`** с записей — `tasks.py edit <слаг> --rm-tag
|
||||||
|
sprint:<слаг>`. Тег больше никем не читается, а `check` о нём молчит: он
|
||||||
|
законный свободный тег. Пропущенный вреда не сделает, но и пользы не несёт.
|
||||||
|
3. **Расставить порядок** — первый груминг: `av-dev-tasks:groom`. После шага 1
|
||||||
|
очередь состоит из того, что машина поставила в конец, то есть очереди нет
|
||||||
|
вовсе. Пока порядок не назначен, «что делать дальше» по-прежнему без ответа.
|
||||||
|
4. Поправить упоминания спринта в `CLAUDE.md` проекта, если они были: слот
|
||||||
|
«общий станок» переехал в груминг под именем «что считается сломанным»,
|
||||||
|
ориентир «5–8 задач в спринте» стал ориентиром размера порции разбора.
|
||||||
|
5. `docs/.pm.json`: `"canon": 12`.
|
||||||
|
|
||||||
|
**Чего делать не надо.** `REJECTED.md`, `ROADMAP.md` и файлы `items/` не
|
||||||
|
меняются: спринт жил только в собственном индексе и в тегах.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Версия 11 — 2026-08-09
|
||||||
|
|
||||||
|
Каталог задач уехал из `docs/` в корень репозитория. Версия 8 отпустила его из
|
||||||
|
канона — перестала требовать, перестала проверять, — но место он занимал всё то
|
||||||
|
же, `docs/tasks/`. Полдела: каталог, принадлежащий одному плагину, лежал внутри
|
||||||
|
дерева, которым владеет другой. Проекту, поставившему учёт работ без канона
|
||||||
|
документов, приходилось заводить `docs/` ради одной вложенной папки.
|
||||||
|
|
||||||
|
**Что изменилось.** Дом задач — `tasks/` в корне репозитория. `tasks.py` ищет его
|
||||||
|
там первым; `docs/tasks/` и `doc/tasks/` остаются в списке поиска для
|
||||||
|
непереехавших проектов, а `init` заводит только в корне. Настройки — там же,
|
||||||
|
`tasks/.tasks.json`.
|
||||||
|
|
||||||
|
**Что осталось терпимым.** `docs.py` по-прежнему не считает `docs/tasks/` файлом
|
||||||
|
вне канона: непереехавший проект не должен получать выдуманную ошибку вдобавок к
|
||||||
|
этой записи, которая и так велит ему переехать.
|
||||||
|
|
||||||
|
**Что сделать проекту.**
|
||||||
|
|
||||||
|
1. `git mv docs/tasks tasks` — одним коммитом вместе с шагом 2, чтобы ссылки не
|
||||||
|
жили битыми между коммитами.
|
||||||
|
2. **Починить относительные ссылки внутри записей.** Файл `tasks/items/x.md`
|
||||||
|
стал на уровень ближе к корню: `../../passport.md` в теле записи теперь
|
||||||
|
`../docs/passport.md`. Тот же сдвиг у ссылок из индексов. Это самая тихая
|
||||||
|
часть переезда: битая относительная ссылка не мешает `tasks.py check`, её
|
||||||
|
ловит только `docs.py check` и только у документов канона.
|
||||||
|
3. Проверить ссылки **на** задачи снаружи: `CLAUDE.md`, `README.md`, гейт,
|
||||||
|
`docs/review.md`. Путь `docs/tasks/...` в них теперь ведёт в никуда.
|
||||||
|
4. Поправить путь в гейте: `tasks.py check --dir tasks`.
|
||||||
|
5. `docs/.pm.json`: `"canon": 11`.
|
||||||
|
|
||||||
|
## Версия 10 — 2026-08-09
|
||||||
|
|
||||||
|
Проверка формы `config.yaml` ушла к тому, кто файл заводит. Версия 9 перенесла в
|
||||||
|
конвейер настройку OpenSpec и честно назвала остаток: форма и сторож версии
|
||||||
|
остались в `docs.py`, то есть у файла было два плагина — один заводит, другой
|
||||||
|
проверяет. Остаток закрыт.
|
||||||
|
|
||||||
|
**Что появилось.** Скрипт `openspec.py` в скилле `av-dev-code:openspec`, две
|
||||||
|
команды: `check --dir <корень>` — форма в проекте, `form` — сверка слепка с живым
|
||||||
|
OpenSpec. Коды выхода те же, что у `docs.py` и `tasks.py`.
|
||||||
|
|
||||||
|
**Что удалено из `docs.py`.** Константы `OPENSPEC_*`, проверка формы, сторож
|
||||||
|
версии и подкоманда `openspec-form` — 252 строки. Скрипт канона про
|
||||||
|
`openspec/config.yaml` не говорит теперь ничего; `openspec/specs/` он по-прежнему
|
||||||
|
знает, потому что это дом темы `requirements` и часть карты тем.
|
||||||
|
|
||||||
|
**Что стало лучше по дороге.** Адреса `docs/passport.md` и `CLAUDE.md` требуются
|
||||||
|
теперь **только к тем документам, которые в проекте есть**. Прежняя проверка
|
||||||
|
требовала их безусловно, то есть на проекте без канона документов требовала
|
||||||
|
битую ссылку. Теперь отсутствие документа — строка «не проверялось» с указанием,
|
||||||
|
что без канона конвейер работает вслепую.
|
||||||
|
|
||||||
|
**Что осталось за каноном.** Один вопрос, и это не форма: не пересказан ли в
|
||||||
|
`context` документ, у которого есть свой дом. Разрез — утверждение, опровергаемое
|
||||||
|
открытием другого файла, против строки «открой такой-то файл»; машине он не
|
||||||
|
виден, судит агент `doc-consistency`, и `config.yaml` у него во входе.
|
||||||
|
|
||||||
|
**Что сделать проекту.**
|
||||||
|
|
||||||
|
1. Заменить в гейте и в скриптах `docs.py openspec-form` на `openspec.py form`.
|
||||||
|
Подкоманды больше нет: прежний вызов упадёт ошибкой употребления (код 2), а не
|
||||||
|
промолчит.
|
||||||
|
2. **Добавить в гейт шаг `openspec.py check`, если проект работает по OpenSpec.**
|
||||||
|
Форму раньше проверял `docs.py check` заодно; теперь он о ней молчит, и без
|
||||||
|
отдельного шага незаменённый пример в `config.yaml` перестанет ловиться. Это
|
||||||
|
главная потеря этого повышения, и она тихая.
|
||||||
|
3. Проект по OpenSpec без установленного `av-dev-code` — форму не проверяет
|
||||||
|
никто. Либо поставить плагин, либо назвать это принятым риском вслух.
|
||||||
|
4. `docs/.pm.json`: `"canon": 10`.
|
||||||
|
|
||||||
|
## Версия 9 — 2026-08-09
|
||||||
|
|
||||||
|
OpenSpec уехал в конвейер. Каталог `openspec/` версией 7 был объявлен слотом
|
||||||
|
канона: `init` его заводил, `adopt` тоже, образец `config.yaml` лежал в скелетах,
|
||||||
|
а отсутствие каталога `docs.py` считал отказом. Разрез был проведён не там. По
|
||||||
|
OpenSpec работает конвейер — без каталога не запускаются ни `opsx:propose`, ни
|
||||||
|
ревью дизайна, ни сверка требований, — а канон документов о нём только
|
||||||
|
высказывался. Проект, которому конвейер не нужен, получал отказ за отсутствие
|
||||||
|
того, чем не пользуется.
|
||||||
|
|
||||||
|
**Что появилось.** Скилл `av-dev-code:openspec`: заводит каталог, заменяет
|
||||||
|
закомментированный пример в `config.yaml` настройкой, объясняет разрез между
|
||||||
|
ссылкой и пересказом. Образец файла переехал туда же — в
|
||||||
|
`references/config-skeleton.md` того скилла.
|
||||||
|
|
||||||
|
**Что изменилось.** `init` и `canon adopt` OpenSpec больше не заводят, а **зовут
|
||||||
|
скилл конвейера**; вызов не разрешился — плагина конвейера нет, и это строка
|
||||||
|
доклада, а не поломка. Отсутствие `openspec/` для `docs.py check` стало
|
||||||
|
неприменимостью вместо отказа: остальные четыре проверки формы идут только при
|
||||||
|
живом каталоге.
|
||||||
|
|
||||||
|
**Что осталось на месте и почему.** Проверка формы `config.yaml` и сторож версии
|
||||||
|
(`docs.py openspec-form`) пока живут в скрипте канона — переносить их значит
|
||||||
|
заводить в конвейере свой скрипт, а этого у него нет ни одного. Разрез названного
|
||||||
|
это не отменяет, но и не завершает: **у файла сейчас два плагина — один заводит,
|
||||||
|
другой проверяет**, и это временное состояние, а не задуманное.
|
||||||
|
|
||||||
|
**Что сделать проекту.**
|
||||||
|
|
||||||
|
1. Ничего не переносить: файлы проекта эта версия не двигает. Меняется только то,
|
||||||
|
кто их заводит.
|
||||||
|
2. Проверить, что плагин `av-dev-code` установлен, если проект работает по
|
||||||
|
OpenSpec. Без него `docs.py check` про каталог промолчит — и молчание это
|
||||||
|
законное, так что отсутствие настройки перестанет ловиться само.
|
||||||
|
3. Проект **не** работает по OpenSpec: убедиться, что `openspec/` нет, и
|
||||||
|
перестать держать его пустым ради проверки. Она больше не требует каталога.
|
||||||
|
4. `docs/.pm.json`: `"canon": 9`.
|
||||||
|
|
||||||
|
## Версия 8 — 2026-08-09
|
||||||
|
|
||||||
|
Канон отпустил каталог задач. Плагин `av-dev-pm` расколот на `av-dev-docs`
|
||||||
|
(документы) и `av-dev-tasks` (учёт работ), и каждый теперь ставится сам по себе.
|
||||||
|
Пока владелец был один, `docs/tasks/` числился слотом канона: `docs.py` требовал
|
||||||
|
каталог, звал внутрь чужой скрипт и выдавал его дрейф за свой, а настройки задач
|
||||||
|
жили ключом `tasks` в `docs/.pm.json`. Для проекта, поставившего только документы,
|
||||||
|
всё это — отказ на ровном месте: задач он не ведёт, и требовать их не за что.
|
||||||
|
|
||||||
|
**Что изменилось.** Каталог задач канону не принадлежит; канон резервирует ему
|
||||||
|
место в `docs/` и внутрь не смотрит. `docs.py` больше не проверяет согласованность
|
||||||
|
задач вовсе — это делает `tasks.py` сам, командой своего плагина. Дом настроек
|
||||||
|
каталога задач — `<каталог задач>/.tasks.json`; ключ `tasks` в `docs/.pm.json`
|
||||||
|
читается, только пока своего файла нет, и об этом говорится замечанием.
|
||||||
|
|
||||||
|
**Что удалено.** Проверка `check_tasks` из `docs.py` и ключ `"tasks"` из скелета
|
||||||
|
`docs/.pm.json`.
|
||||||
|
|
||||||
|
**Что сделать проекту.**
|
||||||
|
|
||||||
|
1. Перенести настройки задач: содержимое ключа `"tasks"` из `docs/.pm.json` — в
|
||||||
|
`docs/tasks/.tasks.json` тем же объектом. Ключа в проекте нет (имена файлов
|
||||||
|
и заголовков умолчательные) — переносить нечего, шаг пропускается.
|
||||||
|
2. Удалить ключ `"tasks"` из `docs/.pm.json` после переноса. Оставленный он не
|
||||||
|
читается, и `tasks.py` скажет об этом замечанием на каждом прогоне.
|
||||||
|
3. Проверить, что согласованность задач по-прежнему кто-то гоняет: раньше её
|
||||||
|
тянул за собой `docs.py check`, теперь — только `tasks.py check`. **Если в
|
||||||
|
гейте проекта стоял один `docs.py`, добавить туда второй шаг** — иначе дрейф
|
||||||
|
индексов перестанет ловиться молча, и это самая вероятная потеря на этом
|
||||||
|
повышении.
|
||||||
|
4. Установить оба плагина, если нужны оба: `av-dev-docs` и `av-dev-tasks`
|
||||||
|
вместо прежнего `av-dev-pm`. Прежний из `enabledPlugins` убрать.
|
||||||
|
5. `docs/.pm.json`: `"canon": 8`.
|
||||||
|
|
||||||
## Версия 7 — 2026-08-07
|
## Версия 7 — 2026-08-07
|
||||||
|
|
||||||
`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил.
|
`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил.
|
||||||
@@ -510,5 +696,5 @@ ADR объясняет прошлое решение, а не предъявля
|
|||||||
|
|
||||||
| Что копируется | Дом определения |
|
| Что копируется | Дом определения |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| форма записи журнала дефектов в `docs/review.md` | `av-dev-pipeline/skills/review-pipeline/references/review-journal.md` |
|
| форма записи журнала дефектов в `docs/review.md` | `av-dev-code/skills/review/references/review-journal.md` |
|
||||||
| правило заведения ADR в `docs/adr/README.md` | [canon.md](canon.md), раздел `adr/` |
|
| правило заведения ADR в `docs/adr/README.md` | [canon.md](canon.md), раздел `adr/` |
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
# Язык проектных текстов
|
||||||
|
|
||||||
|
**Копия.** Дом — `shared/language.md` в репозитории плагинов; язык общий для
|
||||||
|
документов канона и для задач, и потому не принадлежит ни одному плагину.
|
||||||
|
Правится дом, а не этот файл: расхождение ловит `copies.py` на гейте коммита.
|
||||||
|
|
||||||
|
<!-- копия: язык-доктрина из shared/language.md -->
|
||||||
|
|
||||||
|
Правила — для всего, что пишется словами: задачи и цели, документы канона,
|
||||||
|
решения ADR, записки разведки, сообщения коммитов. Не для кода и не для
|
||||||
|
сообщений программы пользователю — там свои конвенции проекта.
|
||||||
|
|
||||||
|
Основа — **информационный стиль** Максима Ильяхова ([учебник
|
||||||
|
бюро](https://bureau.ru/projects/book-text/), книга «Пиши, сокращай»). Он
|
||||||
|
написан для рекламы, статей и писем, поэтому взят не целиком.
|
||||||
|
|
||||||
|
## Зачем он здесь
|
||||||
|
|
||||||
|
Проектный текст читают в двух положениях, и оба неудобные: **выбирают, брать ли
|
||||||
|
задачу**, глядя в строку индекса и один экран тела; и **возвращаются через
|
||||||
|
квартал**, не помня контекста. Оба положения наказывают одно и то же — слова, не
|
||||||
|
несущие сведений. Информационный стиль ровно про это, и его польза здесь не
|
||||||
|
эстетическая: текст, из которого нельзя достать факт, заставляет открывать код,
|
||||||
|
а это и есть цена, которой мы избегаем.
|
||||||
|
|
||||||
|
## Что взято сверх правил вычитки
|
||||||
|
|
||||||
|
Эти три требования судит человек, а не проход вычитки: находка по ним требует
|
||||||
|
увидеть текст целиком, а не фразу.
|
||||||
|
|
||||||
|
**Полезное действие.** У каждого текста есть вопрос, на который он отвечает, и
|
||||||
|
читатель, который этот вопрос задаёт. Не отвечает — не пишется. У задачи это
|
||||||
|
«зачем она нужна» и «что станет наблюдаемо иначе»; у документа канона — его
|
||||||
|
собственный вопрос («что это за система», «как сложено», «почему так решили»).
|
||||||
|
Текст, который не отвечает ни на чей вопрос, сокращается до нуля — это законный
|
||||||
|
исход правки.
|
||||||
|
|
||||||
|
**Параллельность.** Однородное пишется одинаково: пункты списка — одной
|
||||||
|
грамматической формой, разделы одного вида — одним порядком, заголовки одного
|
||||||
|
уровня — одним типом фразы. Разнобой читатель принимает за разницу по существу и
|
||||||
|
ищет её.
|
||||||
|
|
||||||
|
**Заголовок работает.** Заголовок называет содержание раздела, а не тему
|
||||||
|
вообще: «Что проверяет `check`», а не «О проверках». Заголовков ставится
|
||||||
|
столько, чтобы длинный текст можно было просматривать, а не только читать
|
||||||
|
подряд.
|
||||||
|
|
||||||
|
## Что отброшено намеренно
|
||||||
|
|
||||||
|
Инфостиль написан для текстов, где читателя надо удержать. Проектный текст
|
||||||
|
читают потому, что надо, и держать его нечем. Отсюда три расхождения:
|
||||||
|
|
||||||
|
- **Парцелляция и рубленые фразы — нет.** Приём «Коротко. Ещё короче. Вот так»
|
||||||
|
ломает причинную связь, а в решении и в задаче ценность именно в ней:
|
||||||
|
«поэтому», «иначе», «раз так» несут смысл и остаются.
|
||||||
|
- **Не всякое вводное — мусор.** «Если», «иначе», «при таком-то условии»,
|
||||||
|
«в отличие от» — это условия и противопоставления, то есть сведения. Режутся
|
||||||
|
вводные, которые не меняют смысл предложения.
|
||||||
|
- **Скобки и точка с запятой остаются.** В технической записи скобки несут
|
||||||
|
уточнение — имя команды, единицы, слаг, — и запрет на них удлинил бы текст, а
|
||||||
|
не сократил. Запрет на многоточие принимаем: в проектном тексте оно значит
|
||||||
|
«дописать позже», и такой текст лучше не публиковать.
|
||||||
|
|
||||||
|
И общее: инфостиль призывает «снять корону с себя и надеть на читателя». Здесь
|
||||||
|
читатель — **ты сам через квартал** и тот, кто возьмёт задачу. Писать для них
|
||||||
|
значит называть состояние и остаток, а не пересказывать, как было интересно
|
||||||
|
разбираться.
|
||||||
|
|
||||||
|
<!-- /копия: язык-доктрина -->
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
<!-- копия: язык-правила из shared/language.md -->
|
||||||
|
|
||||||
|
У каждого правила названа причина: она же говорит, где правило **не**
|
||||||
|
применяется.
|
||||||
|
|
||||||
|
1. **Глагол вместо отглагольного существительного, активный залог.** «Обработчик
|
||||||
|
не проверяет владельца», а не «проверка владельца не осуществляется»;
|
||||||
|
«скрипт переписывает индекс», а не «индекс переписывается скриптом».
|
||||||
|
Отглагольное существительное прячет того, кто действует, — а в техническом
|
||||||
|
тексте важен именно он. Страдательный залог **остаётся**, когда деятель
|
||||||
|
неизвестен или неважен: «файл удаляется» верно, если удаляет любая из трёх
|
||||||
|
команд.
|
||||||
|
|
||||||
|
2. **Факт вместо оценки.** «Время ответа доходит до 800 мс», а не «работает
|
||||||
|
медленно»; «тело 40 МиБ держит блокировку 5 секунд», а не «большие тела
|
||||||
|
тормозят». Оценка допустима, когда факт стоит рядом, в той же фразе. Без
|
||||||
|
факта это настроение, а не сведение, — и находка тем ценнее, что оценку
|
||||||
|
потом не проверить.
|
||||||
|
|
||||||
|
3. **Стоп-слова.** Канцелярит (является, осуществляется, в целях, в рамках,
|
||||||
|
данный, вышеуказанный), вводные-паразиты (в общем, как известно, стоит
|
||||||
|
отметить), усилители (очень, крайне, достаточно, абсолютно, максимально),
|
||||||
|
синонимы одного качества («понятный и простой»), неопределённое
|
||||||
|
(соответствующий, определённый, некоторый).
|
||||||
|
|
||||||
|
Проверка одна: **вычеркни слово — смысл изменился, оставляй.** И осторожно с
|
||||||
|
вводными: «если», «иначе», «при таком-то условии», «в отличие от» несут
|
||||||
|
условие и противопоставление, то есть сведения, — их не трогают.
|
||||||
|
|
||||||
|
4. **Одна мысль — одно предложение.** Предложение с двумя независимыми
|
||||||
|
утверждениями делится. **Причинную связь не режут**: «поэтому», «иначе», «раз
|
||||||
|
так» — смысл, а не длина; рубленые фразы ради краткости тут вредят.
|
||||||
|
|
||||||
|
**Поля меты не делятся.** «Зачем» в мете задачи по формату — одно
|
||||||
|
предложение: оно повторяется строкой индекса, и второму там не поместиться.
|
||||||
|
Тесно — сокращают, но не делят. То же с любым полем вида `- **Имя:** …`.
|
||||||
|
|
||||||
|
5. **Англицизм, у которого есть живое русское слово, заменяется.**
|
||||||
|
|
||||||
|
| Калька | Русский аналог |
|
||||||
|
| --- | --- |
|
||||||
|
| флоу | поток, процесс, сценарий |
|
||||||
|
| фикс, зафиксить | исправление, исправить, починить |
|
||||||
|
| чекать | проверять |
|
||||||
|
| апрув, заапрувить | согласование, согласовать |
|
||||||
|
| best-effort | по возможности |
|
||||||
|
| кейс | случай, сценарий |
|
||||||
|
| перформанс | производительность |
|
||||||
|
| матчинг, смэтчить | сопоставление, сопоставить |
|
||||||
|
| зарелизить | выпустить, выложить |
|
||||||
|
| отрефакторить | переписать, разделить, убрать второй путь |
|
||||||
|
|
||||||
|
Насильно не переводится то, что является **именем вещи**: термины технологий
|
||||||
|
и протоколов (`SQL`, `API`, `CSV`, `N+1`, `IDOR`), имена классов, методов,
|
||||||
|
полей, таблиц и команд, слаг, а также термин, у которого нет точного русского
|
||||||
|
эквивалента и который в команде уже прижился.
|
||||||
|
|
||||||
|
Цель — простой и точный текст, а не пуризм. Русский аналог звучит коряво или
|
||||||
|
искажает смысл — остаётся термин.
|
||||||
|
|
||||||
|
6. **Слово из своего словаря не трогается — список закрыт.** Оговорка «термин
|
||||||
|
прижился» без списка проверяема на глаз и потому не проверяема: прижившимся
|
||||||
|
выглядит любое слово, встреченное трижды.
|
||||||
|
|
||||||
|
| Термин | Что называет |
|
||||||
|
| --- | --- |
|
||||||
|
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
|
||||||
|
| триаж | стадия конвейера, сводящая находки в решение |
|
||||||
|
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
|
||||||
|
| дедуп, дедупликация | сверка нового против уже лежащего |
|
||||||
|
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
|
||||||
|
| дифф, `--base` | разница между состояниями в git |
|
||||||
|
| промпт | текст, которым зовут модель |
|
||||||
|
| change, capability, spec | сущности OpenSpec, имена вещей чужого инструмента |
|
||||||
|
| generative, applicative | роды проходов ревью, вводятся определением по месту |
|
||||||
|
|
||||||
|
**Список закрыт.** Слово не отсюда и не из таблицы имён вещей выше — находка,
|
||||||
|
а не «принятый стиль»: у него либо есть живой русский аналог, либо оно
|
||||||
|
требует ввода одной строкой при первом употреблении.
|
||||||
|
|
||||||
|
Отсюда же читается снятое. Эти слова из текстов убраны, и возвращать их не
|
||||||
|
надо: **конфляция** (смешение), **декорреляция** (разведённость, разведён с
|
||||||
|
кем-то), **непоймание** (почему не поймали), **эвал-сет** (проверочный
|
||||||
|
набор), **гайд** (руководство). Каждое было латинизмом или калькой при живом
|
||||||
|
русском слове, и каждое к моменту снятия жило в трёх-шести файлах разом — то
|
||||||
|
есть выглядело словарём, не будучи им.
|
||||||
|
|
||||||
|
7. **Жаргон и метафоры заменяются прямым называнием.** Автору образ понятен,
|
||||||
|
читателю — нет.
|
||||||
|
|
||||||
|
| Метафора-жаргон | Прямо |
|
||||||
|
| --- | --- |
|
||||||
|
| рычаг (кэша, отбора) | условие отбора, параметр |
|
||||||
|
| навешен не на тот счётчик | завязан не на тот счётчик |
|
||||||
|
| переширокий матчинг по имени | слишком грубое сопоставление по имени, слишком много слабых совпадений |
|
||||||
|
| костыль | временное решение, обходной путь — и в чём именно |
|
||||||
|
| просело, отвалилось | стало медленнее на столько-то, перестало отвечать |
|
||||||
|
|
||||||
|
Проверка: **фраза требует, чтобы читатель додумал образ, — заменяется
|
||||||
|
буквальным описанием того, что происходит.**
|
||||||
|
|
||||||
|
8. **Термин, которого нет в документах проекта, вводится одной строкой или не
|
||||||
|
употребляется.** Термин, не встречающийся ни в паспорте, ни в архитектуре, ни
|
||||||
|
в конвенциях, — свой словарь у отдельной записи, а это самый дешёвый способ
|
||||||
|
сделать беклог нечитаемым для того, кто вернётся к нему через квартал.
|
||||||
|
Заменять незнакомый термин догадкой нельзя: догадка о предметной области
|
||||||
|
дороже непонятного слова, потому что выглядит понятной.
|
||||||
|
|
||||||
|
**Слово, занятое в другом смысле, — то же нарушение.** Термин, который в
|
||||||
|
одном документе проекта значит одно, а здесь другое, ломает оба.
|
||||||
|
|
||||||
|
9. **Имя файла — английское слово по сути, а не транслит.** `queue-as-table`, а
|
||||||
|
не `ochered-tablicej`; `move-parse-strict`, а не `razbor-hoda`. Транслит
|
||||||
|
нечитаем тому, кто ищет по смыслу, и не сокращается, а имя стоит в ссылках,
|
||||||
|
коммитах и путях, которые набирают руками. Переименование — **перенос ссылок
|
||||||
|
одним проходом**, а не правка одного файла.
|
||||||
|
|
||||||
|
<!-- /копия: язык-правила -->
|
||||||
|
|
||||||
|
## Порог правки
|
||||||
|
|
||||||
|
<!-- копия: порог-правки из shared/language.md -->
|
||||||
|
|
||||||
|
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
||||||
|
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
||||||
|
перестают читать весь список, и вместе с ним пропадают настоящие находки.
|
||||||
|
Сомневаешься — не правь. Формулировка, которая просто **не твоя**, — не находка.
|
||||||
|
|
||||||
|
**Систематичность нарушения — не довод в его пользу.** Одна и та же ошибка в
|
||||||
|
пяти файлах не становится «принятым стилем»: чаще это значит, что правило не
|
||||||
|
применялось вовсе, — и находка тем важнее. «Так сделано везде» годится как
|
||||||
|
основание для **одной находки на весь набор** («правило N нарушено в пяти
|
||||||
|
записях, перечень: …»), но не как основание промолчать. Принятым считается
|
||||||
|
только то, что назвал зовущий или что записано в конвенциях проекта.
|
||||||
|
|
||||||
|
<!-- /копия: порог-правки -->
|
||||||
|
|
||||||
|
И обратное: язык правится **по ходу той операции, которая записи касается**.
|
||||||
|
Беклог не переписывают ради языка.
|
||||||
+35
-90
@@ -19,9 +19,12 @@
|
|||||||
`<!-- дом: <id> -->` … `<!-- /дом: <id> -->`, копия —
|
`<!-- дом: <id> -->` … `<!-- /дом: <id> -->`, копия —
|
||||||
`<!-- копия: <id> из <путь> -->` … `<!-- /копия: <id> -->`;
|
`<!-- копия: <id> из <путь> -->` … `<!-- /копия: <id> -->`;
|
||||||
`scripts/copies.py` маркетплейса требует дословного
|
`scripts/copies.py` маркетплейса требует дословного
|
||||||
совпадения. Комментарии невидимы в отрендеренном markdown и уезжают в проект
|
совпадения. Правишь текст внутри маркеров — правь дом, а не копию.
|
||||||
вместе со скелетом — там они говорят читателю, что у текста есть дом. Правишь
|
|
||||||
текст внутри маркеров — правь дом, а не копию.
|
**Сама пара маркеров в проект не переносится.** Это машинерия маркетплейса:
|
||||||
|
путь в ней ведёт в дерево плагина, и в репозитории проекта он не разрешится ни
|
||||||
|
во что. Кладя скелет, копируй содержимое между маркерами, а строки
|
||||||
|
`<!-- копия: … -->` и `<!-- /копия: … -->` оставляй здесь.
|
||||||
|
|
||||||
## `docs/passport.md`
|
## `docs/passport.md`
|
||||||
|
|
||||||
@@ -29,7 +32,7 @@
|
|||||||
# Паспорт проекта
|
# Паспорт проекта
|
||||||
|
|
||||||
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
|
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
|
||||||
устроено», [tasks/ROADMAP.md](tasks/ROADMAP.md) — «в каком порядке», паспорт —
|
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «в каком порядке», паспорт —
|
||||||
«зачем и для кого».
|
«зачем и для кого».
|
||||||
|
|
||||||
## Цель
|
## Цель
|
||||||
@@ -205,7 +208,7 @@
|
|||||||
|
|
||||||
Верно одно из трёх:
|
Верно одно из трёх:
|
||||||
|
|
||||||
<!-- копия: adr-когда-заводить из av-dev-pm/skills/canon/references/canon.md -->
|
<!-- копия: adr-когда-заводить из av-dev-docs/skills/canon/references/canon.md -->
|
||||||
- **дорогой откат** — переделка стоит дороже переписывания одного файла;
|
- **дорогой откат** — переделка стоит дороже переписывания одного файла;
|
||||||
- **намеренный отказ** от очевидного подхода;
|
- **намеренный отказ** от очевидного подхода;
|
||||||
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус
|
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус
|
||||||
@@ -340,7 +343,7 @@
|
|||||||
|
|
||||||
Форма:
|
Форма:
|
||||||
|
|
||||||
<!-- копия: журнал-дефектов-форма из av-dev-pipeline/skills/review-pipeline/references/review-journal.md -->
|
<!-- копия: журнал-дефектов-форма из av-dev-code/skills/review/references/review-journal.md -->
|
||||||
## ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман]
|
## ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман]
|
||||||
|
|
||||||
- **Где:** путь:строка либо «конвейер, а не код»
|
- **Где:** путь:строка либо «конвейер, а не код»
|
||||||
@@ -354,6 +357,9 @@
|
|||||||
<!-- /копия: журнал-дефектов-форма -->
|
<!-- /копия: журнал-дефектов-форма -->
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Пара маркеров `копия:` внутри — машинерия маркетплейса; в `docs/review.md`
|
||||||
|
проекта уезжает только содержимое между ними (см. выше).
|
||||||
|
|
||||||
Новый проект: «Дефектов пока не было. Настройка конвейера появится с первым
|
Новый проект: «Дефектов пока не было. Настройка конвейера появится с первым
|
||||||
ревью.»
|
ревью.»
|
||||||
|
|
||||||
@@ -401,9 +407,10 @@ severity стоит здесь, а не выводится каждым прох
|
|||||||
|
|
||||||
- **Основная ветка:** <имя>
|
- **Основная ветка:** <имя>
|
||||||
- **Необратимое** (спрашивается у человека всегда):
|
- **Необратимое** (спрашивается у человека всегда):
|
||||||
- **Общий станок** — какая проверка, покраснев, врывается в замороженный спринт:
|
- **Что считается сломанным** — какая красная проверка обгоняет развитие,
|
||||||
- **Ориентир по размеру спринта:** 5–8 задач, ориентир а не закон
|
то есть останавливает текущую работу:
|
||||||
- **Что такое «сделана»:** пайплайн проекта пройден + критерии приёмки проверены
|
- **Ориентир по размеру порции:** своё число, если замерялось
|
||||||
|
- **Что такое «сделана»:** конвейер проекта пройден + критерии приёмки проверены
|
||||||
поимённо
|
поимённо
|
||||||
|
|
||||||
## Язык
|
## Язык
|
||||||
@@ -419,93 +426,31 @@ severity стоит здесь, а не выводится каждым прох
|
|||||||
|
|
||||||
## `openspec/config.yaml`
|
## `openspec/config.yaml`
|
||||||
|
|
||||||
Каталог `openspec/` заводится командой — `openspec init --tools claude`, — и она
|
**Образец переехал.** Файл заводит и заполняет плагин конвейера — скилл
|
||||||
кладёт `config.yaml` с закомментированным примером внутри. Пример **заменяется
|
`av-dev-code:openspec`, — потому что по OpenSpec работает он, а не канон
|
||||||
целиком**: нетронутый файл выглядит настроенным, а работает как пустой.
|
документов. Проект без конвейера каталога `openspec/` не имеет вовсе, и образец
|
||||||
|
файла, которого у него нет, в скелетах канона лежал бы мёртвым грузом.
|
||||||
|
|
||||||
**Это маршрутизатор, а не второй дом фактов.** Сюда пишут ровно то, что нужно
|
**Форму не проверяет и `docs.py`** — с канона 10 он о файле молчит вовсе.
|
||||||
**в момент порождения артефакта** и чего в этот момент ещё никто не открыл:
|
Проверяет её тот же владелец: скилл `av-dev-code:openspec`, команда
|
||||||
язык, правила именования capability, придирки валидатора и **адреса** документов
|
`openspec.py check`. Плагина конвейера в проекте может не быть — тогда форму не
|
||||||
канона. Пересказ паспорта, инвариантов, конвенций и правил ревью сюда не
|
смотрит никто, и это строка доклада, а не поломка. Что канон о файле всё же
|
||||||
переносится: расходится он молча, а замечают это в уже написанном предложении.
|
говорит (единственный дом, а не форма) — [canon.md](canon.md), раздел
|
||||||
|
`openspec/config.yaml`.
|
||||||
```yaml
|
|
||||||
schema: spec-driven
|
|
||||||
|
|
||||||
context: |
|
|
||||||
Language: Russian
|
|
||||||
Пиши на русском, но:
|
|
||||||
- Структурные заголовки оставляй на английском:
|
|
||||||
## ADDED/MODIFIED/REMOVED Requirements, ### Requirement:, #### Scenario:
|
|
||||||
- Ключевые слова GIVEN/WHEN/THEN и RFC 2119 (SHALL/MUST/SHOULD) — на английском
|
|
||||||
- Технические термины, пути и код — на английском
|
|
||||||
|
|
||||||
Имена capabilities:
|
|
||||||
- Capability — это ПОВЕДЕНИЕ или домен системы, а не пакет кода (совпадение с
|
|
||||||
именем пакета допустимо, но не критерий).
|
|
||||||
- Существительное, понятное без знания кода: ingest, parsing, storage,
|
|
||||||
read-api. НЕ store/httpapi — это реализация.
|
|
||||||
- Гранулярность по принципу «требования меняются вместе». Дробить, когда в
|
|
||||||
одной спеке смешиваются разные заботы. Переименовать дёшево (RENAMED
|
|
||||||
Requirements) — не дроби преждевременно в маленьком проекте.
|
|
||||||
|
|
||||||
RFC 2119 — требование валидатора, не стиль:
|
|
||||||
- Каждое ### Requirement ОБЯЗАНО содержать литерал SHALL или MUST, иначе
|
|
||||||
`openspec validate` падает. Поэтому эти слова и WHEN/THEN не русифицируем.
|
|
||||||
|
|
||||||
Что это за проект — читай перед предложением, а не отсюда:
|
|
||||||
- docs/passport.md — цель, её граница (чем проект НЕ является), потребители,
|
|
||||||
типовые сценарии, референсы;
|
|
||||||
- CLAUDE.md — инварианты с severity и семантика гейта;
|
|
||||||
- docs/architecture.md — устройство; docs/security.md — периметр;
|
|
||||||
docs/adr/ — почему решено так; docs/research/ — что уже измерено.
|
|
||||||
Пересказа этих документов здесь нет намеренно: второй дом факта расходится с
|
|
||||||
первым молча, и заметно это становится в предложении, которое уже написано.
|
|
||||||
|
|
||||||
Ревью: правило выбора метки и состав проходов здесь не пересказываем — их дом
|
|
||||||
скилл av-dev-pipeline:review-pipeline, проектная настройка — docs/review.md.
|
|
||||||
|
|
||||||
Конвенции кода: механизированное проверяет гейт, прозой остаётся
|
|
||||||
docs/conventions/. Ни состав шагов гейта, ни перечень конвенций здесь не
|
|
||||||
пересказываем: и то и другое растёт по ходу задач.
|
|
||||||
|
|
||||||
Развилка или блокер — сперва prior art. Готовые решения смотрим в референсах
|
|
||||||
паспорта, отвергаем — с названной причиной, и причина идёт в design.md этого
|
|
||||||
же изменения.
|
|
||||||
|
|
||||||
rules:
|
|
||||||
proposal:
|
|
||||||
- Capabilities называй по поведению или домену системы, не по пакету кода
|
|
||||||
specs:
|
|
||||||
# Кавычки обязательны: без них YAML обрежет строку на первом '#'.
|
|
||||||
- "Каждое ### Requirement обязано содержать SHALL или MUST (иначе валидация падает)"
|
|
||||||
- "Сценарий — ровно #### (четыре решётки); три или список молча теряются"
|
|
||||||
- "SHALL/MUST должно стоять в ПЕРВОМ абзаце требования: валидатор смотрит только его"
|
|
||||||
- "Заголовки и WHEN/THEN/GIVEN — на английском, остальной текст на русском"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Четыре правила для `specs` сняты отказами валидатора, а не выведены из
|
|
||||||
документации** — потому и записаны дословно: без них каждое второе предложение
|
|
||||||
узнаёт их падением `openspec validate --strict`. Блок `context` проект
|
|
||||||
дополняет своим (стек, разведка, особенности домена), но **адреса паспорта и
|
|
||||||
`CLAUDE.md` обязательны** — их отсутствие `docs.py check` называет отказом.
|
|
||||||
|
|
||||||
**Ключи под `rules:` — имена артефактов схемы**, а не свободные слова:
|
|
||||||
`proposal`, `specs`, `design`, `tasks`. Правило под чужим именем не применяется
|
|
||||||
и об этом не сообщает, поэтому `rules.spec` вместо `rules.specs` даёт конфиг,
|
|
||||||
выглядящий написанным и не работающий; `docs.py check` такой ключ называет.
|
|
||||||
Перечень артефактов задаёт OpenSpec, а не канон, — за его актуальностью следит
|
|
||||||
`docs.py openspec-form`.
|
|
||||||
|
|
||||||
## `docs/.pm.json`
|
## `docs/.pm.json`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"canon": 7
|
"canon": <текущая версия>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Плюс `"migrations": "<путь>"`, если есть БД. Ключ `"tasks"` заводится **только**
|
`<текущая версия>` подставляет `init` или `adopt`, целым числом; берётся она из
|
||||||
когда имя файла или заголовка отличается от умолчания (`{"backlog":
|
`docs.py version` (строка «канон скрипта»), а не из памяти. Литерал здесь
|
||||||
"INDEX.md"}`); секций беклога в нём нет — их дом заголовки `##` индекса. Состав
|
протухает при каждом повышении канона, поэтому его тут и нет: незамещённый
|
||||||
ключей — [canon.md](canon.md).
|
плейсхолдер ломает разбор JSON громко, а отставшее число дало бы дрейф молча.
|
||||||
|
|
||||||
|
Плюс `"migrations": "<путь>"`, если есть БД. Ключа `"tasks"` здесь **нет**:
|
||||||
|
настройки каталога задач переехали в свой файл `<каталог задач>/.tasks.json`,
|
||||||
|
потому что ведёт их другой плагин. Состав ключей — [canon.md](canon.md).
|
||||||
+17
-292
@@ -25,7 +25,7 @@ from dataclasses import dataclass, field
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
CANON_VERSION = 7
|
CANON_VERSION = 12
|
||||||
|
|
||||||
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||||
|
|
||||||
@@ -76,42 +76,15 @@ DOC_EXTRA = {
|
|||||||
"adr": {"template.md": "шаблон записи ADR"},
|
"adr": {"template.md": "шаблон записи ADR"},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Настройка OpenSpec. Команда заведения — она же в скилле init; здесь потому,
|
# Служебное в docs/ и каталог задач, оставшийся там от прежней раскладки. Формы
|
||||||
# что её печатает отказ, а отказ без команды заставляет искать её в другом месте.
|
# у них скрипт не проверяет, и по разным причинам: `.pm.json` не markdown, а
|
||||||
OPENSPEC_INIT = "openspec init --tools claude"
|
# задачи **принадлежат другому плагину** — `av-dev-tasks`, со своим скриптом,
|
||||||
|
# своим конфигом и своей версией формата.
|
||||||
# Адреса, которые обязан назвать блок context. Не пересказ документов, а именно
|
|
||||||
# ссылки: предложение пишется до того, как кто-либо откроет docs/, и без этих
|
|
||||||
# двух строк его пишут, не зная ни границы домена, ни инвариантов. Список
|
|
||||||
# короткий намеренно — длинный превращает context во второй дом фактов.
|
|
||||||
OPENSPEC_POINTERS = [
|
|
||||||
("passport", "граница домена и «чем НЕ является» останутся непрочитанными"),
|
|
||||||
("CLAUDE.md", "инварианты и семантика гейта останутся непрочитанными"),
|
|
||||||
]
|
|
||||||
|
|
||||||
# --- Форма config.yaml сверена с живым OpenSpec ------------------------------
|
|
||||||
#
|
#
|
||||||
# Три константы ниже — **слепок чужого инструмента**, а не наше решение. Схема,
|
# Дом задач с версии 11 — `tasks/` в корне репозитория, то есть вне `docs/`
|
||||||
# перечень артефактов и версия, на которой это проверено, живут в OpenSpec и
|
# вовсе. `docs/tasks/` здесь терпится потому, что непереехавший проект не должен
|
||||||
# меняются без нашего участия; здесь они записаны, чтобы проверка шла без запуска
|
# получать «файл вне канона» вдобавок к записи журнала, которая и так велит ему
|
||||||
# node на каждом прогоне.
|
# переехать. Внутрь скрипт не смотрит ни в том, ни в другом случае.
|
||||||
#
|
|
||||||
# Слепок стареет, и потому есть кто, кто это замечает: `check` сравнивает
|
|
||||||
# major.minor установленного OpenSpec с OPENSPEC_CHECKED и, если они разошлись,
|
|
||||||
# говорит замечанием «форма не перепроверена». Перепроверяет `docs.py
|
|
||||||
# openspec-form` — он спрашивает сам инструмент и печатает, что разошлось.
|
|
||||||
# Патч-версия сравнением намеренно не берётся: форма конфига в ней не меняется, а
|
|
||||||
# замечание на каждый багфикс приучило бы пролистывать весь блок.
|
|
||||||
OPENSPEC_CHECKED = "1.5"
|
|
||||||
OPENSPEC_SCHEMA = "spec-driven"
|
|
||||||
|
|
||||||
# Артефакты схемы. Ключ `rules:` адресуется артефакту, и адресованный
|
|
||||||
# несуществующему **молча не действует** — ровно тот класс, ради которого вся
|
|
||||||
# проверка и заведена.
|
|
||||||
OPENSPEC_ARTIFACTS = ("proposal", "specs", "design", "tasks")
|
|
||||||
|
|
||||||
# Служебное в docs/ и каталог, который ведёт tasks.py. Оба процессные, но
|
|
||||||
# проверок формы у них нет: .pm.json не markdown, tasks/ ведёт другой скрипт.
|
|
||||||
NOT_DOCS = {".pm.json", "tasks"}
|
NOT_DOCS = {".pm.json", "tasks"}
|
||||||
|
|
||||||
# Слоты, которых в каноне нет, — с адресом, куда уезжает содержимое. Имена,
|
# Слоты, которых в каноне нет, — с адресом, куда уезжает содержимое. Имена,
|
||||||
@@ -120,11 +93,11 @@ NOT_DOCS = {".pm.json", "tasks"}
|
|||||||
RETIRED = {
|
RETIRED = {
|
||||||
"review-brief.md": "документы канона и есть бриф; остаток — в review",
|
"review-brief.md": "документы канона и есть бриф; остаток — в review",
|
||||||
"review-journal.md": "→ документ review",
|
"review-journal.md": "→ документ review",
|
||||||
"plan.md": "→ docs/tasks/ROADMAP.md",
|
"plan.md": "→ tasks/ROADMAP.md (плагин av-dev-tasks)",
|
||||||
"local-research.md": "→ документ research",
|
"local-research.md": "→ документ research",
|
||||||
"specs": "поведение → openspec/specs/, обзор → тема architecture",
|
"specs": "поведение → openspec/specs/, обзор → тема architecture",
|
||||||
"drafts": "идея → запись research, отказ → ADR, порядок → ROADMAP.md",
|
"drafts": "идея → запись research, отказ → ADR, порядок → ROADMAP.md",
|
||||||
"backlog": "→ docs/tasks/",
|
"backlog": "→ tasks/ в корне репозитория (плагин av-dev-tasks)",
|
||||||
}
|
}
|
||||||
|
|
||||||
# --- Слаги в именах файлов --------------------------------------------------
|
# --- Слаги в именах файлов --------------------------------------------------
|
||||||
@@ -241,7 +214,7 @@ def strip_code(text: str) -> str:
|
|||||||
"""Выкинуть блоки кода и вставки в обратных кавычках.
|
"""Выкинуть блоки кода и вставки в обратных кавычках.
|
||||||
|
|
||||||
Путь в примере или в шаблоне — не ссылка, и краснеть на нём значит краснеть
|
Путь в примере или в шаблоне — не ссылка, и краснеть на нём значит краснеть
|
||||||
на каждом образце документа. Инлайн-код тоже: `[docs/backlog](docs/tasks/…)`
|
на каждом образце документа. Инлайн-код тоже: `[docs/backlog](tasks/…)`
|
||||||
в тексте про подписи ссылок — иллюстрация, а не ссылка."""
|
в тексте про подписи ссылок — иллюстрация, а не ссылка."""
|
||||||
out, inside = [], False
|
out, inside = [], False
|
||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
@@ -489,153 +462,6 @@ def doc_text(root: Path, name: str) -> str | None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def rules_keys(live: str) -> list[str]:
|
|
||||||
"""Имена артефактов, которым адресованы правила, — и только они.
|
|
||||||
|
|
||||||
Идём от строки `rules:` до следующего ключа нулевой колонки, а не ищем
|
|
||||||
отступ по всему файлу: блок `context: |` — литеральный скаляр, внутри него
|
|
||||||
строки вида «Language: Russian» и «av-dev-pm:review-pipeline» выглядят
|
|
||||||
ключами и дали бы находку на ровном месте. Проверено на живом конфиге,
|
|
||||||
который так и падал.
|
|
||||||
"""
|
|
||||||
out: list[str] = []
|
|
||||||
inside = False
|
|
||||||
for line in live.splitlines():
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
if not line[0].isspace():
|
|
||||||
inside = line.startswith("rules:")
|
|
||||||
continue
|
|
||||||
if not inside:
|
|
||||||
continue
|
|
||||||
m = re.fullmatch(r" ([A-Za-z_-]+):\s*", line)
|
|
||||||
if m:
|
|
||||||
out.append(m.group(1))
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def check_openspec(root: Path, rep: Report) -> None:
|
|
||||||
"""Настройка OpenSpec заведена и не осталась примером из коробки.
|
|
||||||
|
|
||||||
Разбираем текстом, а не YAML-парсером: у скриптов канона ноль внешних
|
|
||||||
зависимостей, а PyYAML в стандартной библиотеке нет. Всё, что проверяется
|
|
||||||
ниже, различимо построчно, и ложных срабатываний это не даёт: комментарии
|
|
||||||
отброшены, ключи верхнего уровня стоят в первой колонке.
|
|
||||||
"""
|
|
||||||
os_dir = root / "openspec"
|
|
||||||
if not os_dir.is_dir():
|
|
||||||
rep.error(
|
|
||||||
"нет openspec/ — там дом темы requirements (openspec/specs/) и "
|
|
||||||
f"настройка генерации артефактов; заводится `{OPENSPEC_INIT}`"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
if (os_dir / "config.yml").is_file():
|
|
||||||
rep.error(
|
|
||||||
"openspec/config.yml — читается только config.yaml, и этот файл "
|
|
||||||
"останется незамеченным: настройка будет пустой, а выглядеть будет "
|
|
||||||
"заполненной"
|
|
||||||
)
|
|
||||||
|
|
||||||
path = os_dir / "config.yaml"
|
|
||||||
if not path.is_file():
|
|
||||||
rep.error(
|
|
||||||
"нет openspec/config.yaml — язык, правила именования capability и "
|
|
||||||
"придирки валидатора будут заново угадываться на каждом предложении"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
text = path.read_text(encoding="utf-8")
|
|
||||||
live = "\n".join(
|
|
||||||
line for line in text.splitlines() if not line.lstrip().startswith("#")
|
|
||||||
)
|
|
||||||
keys = set(re.findall(r"(?m)^([A-Za-z_]+):", live))
|
|
||||||
|
|
||||||
schema = re.search(r"(?m)^schema:\s*(\S+)", live)
|
|
||||||
if schema is None:
|
|
||||||
rep.error(
|
|
||||||
f"в openspec/config.yaml нет ключа schema — ожидается {OPENSPEC_SCHEMA}"
|
|
||||||
)
|
|
||||||
elif schema.group(1) != OPENSPEC_SCHEMA:
|
|
||||||
rep.error(
|
|
||||||
f"schema в openspec/config.yaml — {schema.group(1)}, а канон описан "
|
|
||||||
f"для {OPENSPEC_SCHEMA}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if "context" not in keys:
|
|
||||||
rep.error(
|
|
||||||
"в openspec/config.yaml нет ключа context: файл остался примером из "
|
|
||||||
"коробки — предложение пишется без языка, правил именования "
|
|
||||||
"capability и адресов документов проекта"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
for pointer, why in OPENSPEC_POINTERS:
|
|
||||||
if pointer not in live:
|
|
||||||
rep.error(
|
|
||||||
f"openspec/config.yaml не называет {pointer} — {why}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if "rules" not in keys or "specs:" not in live:
|
|
||||||
rep.error(
|
|
||||||
"в openspec/config.yaml нет rules.specs — придирки валидатора "
|
|
||||||
"нигде не записаны, и каждое предложение узнаёт их отказом"
|
|
||||||
)
|
|
||||||
elif "SHALL" not in live:
|
|
||||||
rep.error(
|
|
||||||
"rules.specs в openspec/config.yaml не называет SHALL — "
|
|
||||||
"требование без этого литерала валидатор отвергает, а правило "
|
|
||||||
"проекта об этом молчит"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Ключ под rules: — имя артефакта схемы. Опечатка или устаревшее имя не
|
|
||||||
# ломает ничего видимого: правила просто не применяются, а конфиг выглядит
|
|
||||||
# написанным.
|
|
||||||
for name in rules_keys(live):
|
|
||||||
if name not in OPENSPEC_ARTIFACTS:
|
|
||||||
rep.error(
|
|
||||||
f"rules.{name} в openspec/config.yaml — такого артефакта у схемы "
|
|
||||||
f"{OPENSPEC_SCHEMA} нет ({', '.join(OPENSPEC_ARTIFACTS)}): правила "
|
|
||||||
f"под ним не применяются и молчат об этом"
|
|
||||||
)
|
|
||||||
|
|
||||||
check_openspec_fresh(rep)
|
|
||||||
|
|
||||||
|
|
||||||
def openspec_cli(args: list[str]) -> str | None:
|
|
||||||
"""Спросить сам инструмент. None — его нет или он не ответил."""
|
|
||||||
try:
|
|
||||||
out = subprocess.run(
|
|
||||||
["openspec", *args], capture_output=True, text=True, timeout=30
|
|
||||||
)
|
|
||||||
except (FileNotFoundError, OSError, subprocess.SubprocessError):
|
|
||||||
return None
|
|
||||||
return out.stdout.strip() if out.returncode == 0 else None
|
|
||||||
|
|
||||||
|
|
||||||
def check_openspec_fresh(rep: Report) -> None:
|
|
||||||
"""Не устарел ли наш слепок формы config.yaml.
|
|
||||||
|
|
||||||
Стоит один запуск `openspec --version` — десятые доли секунды. Перечень
|
|
||||||
артефактов и имя схемы отсюда не спрашиваются намеренно: они стоят втрое
|
|
||||||
дороже, а меняются только вместе с версией, и потому за ними ходит отдельная
|
|
||||||
команда `openspec-form`, а эта проверка говорит, когда её звать.
|
|
||||||
"""
|
|
||||||
got = openspec_cli(["--version"])
|
|
||||||
if got is None:
|
|
||||||
rep.skip(
|
|
||||||
"openspec не отвечает (нет на PATH?) — актуальность формы "
|
|
||||||
"config.yaml не проверялась"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
installed = ".".join(got.split(".")[:2])
|
|
||||||
if installed != OPENSPEC_CHECKED:
|
|
||||||
rep.note(
|
|
||||||
f"форма openspec/config.yaml сверена с OpenSpec {OPENSPEC_CHECKED}, "
|
|
||||||
f"установлен {got}: перепроверить — `docs.py openspec-form`. Пока не "
|
|
||||||
f"перепроверено, проверки формы судят по прежней схеме"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def check_capabilities(root: Path, rep: Report) -> None:
|
def check_capabilities(root: Path, rep: Report) -> None:
|
||||||
specs = root / "openspec" / "specs"
|
specs = root / "openspec" / "specs"
|
||||||
text = doc_text(root, "architecture")
|
text = doc_text(root, "architecture")
|
||||||
@@ -722,35 +548,6 @@ def check_migrations(root: Path, cfg: dict, base: str | None, rep: Report) -> No
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_tasks(root: Path, rep: Report) -> None:
|
|
||||||
tasks = root / "docs" / "tasks"
|
|
||||||
if not tasks.is_dir():
|
|
||||||
rep.error("нет docs/tasks/ — каталог задач часть канона")
|
|
||||||
return
|
|
||||||
script = Path(__file__).resolve().parents[2] / "tasks" / "scripts" / "tasks.py"
|
|
||||||
if not script.exists():
|
|
||||||
rep.skip(f"tasks.py не найден по пути {script} — согласованность задач не проверена")
|
|
||||||
return
|
|
||||||
# cwd=root обязателен: tasks.py отвергает --dir вне текущего каталога, и без
|
|
||||||
# этого его отказ окружения (код 3) схлопнулся бы в наш дрейф (код 1).
|
|
||||||
proc = subprocess.run(
|
|
||||||
[sys.executable, str(script), "check", "--dir", "docs/tasks"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
cwd=str(root),
|
|
||||||
)
|
|
||||||
if proc.returncode == 0:
|
|
||||||
return
|
|
||||||
if proc.returncode == 1:
|
|
||||||
rep.error("tasks.py check нашёл дрейф в docs/tasks/ — разбирать его командой tasks.py")
|
|
||||||
else:
|
|
||||||
# Чужой код выхода не выдаём за свой: 3 это окружение, а не дрейф.
|
|
||||||
rep.skip(
|
|
||||||
f"tasks.py check не отработал (код {proc.returncode}): "
|
|
||||||
f"{(proc.stderr or proc.stdout).strip().splitlines()[0] if (proc.stderr or proc.stdout).strip() else 'без сообщения'}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# --- Отчёт ------------------------------------------------------------------
|
# --- Отчёт ------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -769,10 +566,11 @@ def report(rep: Report) -> int:
|
|||||||
print(f" {msg}")
|
print(f" {msg}")
|
||||||
|
|
||||||
print(
|
print(
|
||||||
"\nМашина проверила раскладку, имена файлов, ссылки, версию, форму\n"
|
"\nМашина проверила раскладку, имена файлов, ссылки, версию и две\n"
|
||||||
"openspec/config.yaml и две сверки с кодом. Согласованность документов\n"
|
"сверки с кодом. Форму openspec/config.yaml она не проверяет: каталог\n"
|
||||||
"между собой и с кодом она не проверяет — как и то, ссылается ли\n"
|
"принадлежит конвейеру, и форму смотрит его скрипт\n"
|
||||||
"config.yaml на документы или пересказывает их. Это суждение агентов\n"
|
"(`av-dev-code:openspec`, команда `openspec.py check`). Согласованность\n"
|
||||||
|
"документов между собой и с кодом — тоже не её: это суждение агентов\n"
|
||||||
"`doc-consistency` (документ ↔ документ ↔ openspec) и `doc-code-drift`\n"
|
"`doc-consistency` (документ ↔ документ ↔ openspec) и `doc-code-drift`\n"
|
||||||
"(документ ↔ код)."
|
"(документ ↔ код)."
|
||||||
)
|
)
|
||||||
@@ -798,10 +596,8 @@ def cmd_check(args: argparse.Namespace) -> int:
|
|||||||
check_slugs(root, rep)
|
check_slugs(root, rep)
|
||||||
check_links(root, rep)
|
check_links(root, rep)
|
||||||
check_placeholders_and_debt(root, rep)
|
check_placeholders_and_debt(root, rep)
|
||||||
check_openspec(root, rep)
|
|
||||||
check_capabilities(root, rep)
|
check_capabilities(root, rep)
|
||||||
check_migrations(root, cfg, args.base, rep)
|
check_migrations(root, cfg, args.base, rep)
|
||||||
check_tasks(root, rep)
|
|
||||||
return report(rep)
|
return report(rep)
|
||||||
|
|
||||||
|
|
||||||
@@ -814,71 +610,6 @@ def cmd_version(args: argparse.Namespace) -> int:
|
|||||||
return OK
|
return OK
|
||||||
|
|
||||||
|
|
||||||
def cmd_openspec_form(args: argparse.Namespace) -> int:
|
|
||||||
"""Перепроверить слепок формы config.yaml по живому OpenSpec.
|
|
||||||
|
|
||||||
Ничего не правит и не трогает проект: спрашивает инструмент и печатает, что
|
|
||||||
разошлось с константами скрипта. Чинит человек — правкой констант, скелета в
|
|
||||||
skeletons.md и записью в журнал версий канона, если форма действительно
|
|
||||||
поменялась.
|
|
||||||
"""
|
|
||||||
version = openspec_cli(["--version"])
|
|
||||||
if version is None:
|
|
||||||
fail(
|
|
||||||
ENV,
|
|
||||||
"openspec не отвечает: поставь его или проверь PATH — "
|
|
||||||
"перепроверять форму нечем",
|
|
||||||
)
|
|
||||||
raw = openspec_cli(["templates", "--json"])
|
|
||||||
if raw is None:
|
|
||||||
fail(ENV, "`openspec templates --json` не отработал — схему не спросить")
|
|
||||||
try:
|
|
||||||
artifacts = tuple(json.loads(raw))
|
|
||||||
except json.JSONDecodeError as exc:
|
|
||||||
fail(ENV, f"`openspec templates --json` отдал неразбираемое: {exc}")
|
|
||||||
|
|
||||||
print(f"OpenSpec установлен: {version}")
|
|
||||||
print(f"форма сверена с: {OPENSPEC_CHECKED}")
|
|
||||||
print(f"артефакты схемы: {', '.join(artifacts)}")
|
|
||||||
print(f"записано в скрипте: {', '.join(OPENSPEC_ARTIFACTS)}")
|
|
||||||
|
|
||||||
diffs: list[str] = []
|
|
||||||
if ".".join(version.split(".")[:2]) != OPENSPEC_CHECKED:
|
|
||||||
diffs.append(
|
|
||||||
f"версия: поднять OPENSPEC_CHECKED до "
|
|
||||||
f"{'.'.join(version.split('.')[:2])} — но только после того, как "
|
|
||||||
f"остальные строки этого отчёта сойдутся"
|
|
||||||
)
|
|
||||||
for name in artifacts:
|
|
||||||
if name not in OPENSPEC_ARTIFACTS:
|
|
||||||
diffs.append(
|
|
||||||
f"новый артефакт {name}: решить, нужны ли ему правила в rules, "
|
|
||||||
f"и добавить имя в OPENSPEC_ARTIFACTS"
|
|
||||||
)
|
|
||||||
for name in OPENSPEC_ARTIFACTS:
|
|
||||||
if name not in artifacts:
|
|
||||||
diffs.append(
|
|
||||||
f"артефакта {name} у схемы больше нет: правила под ним в конфигах "
|
|
||||||
f"проектов молчат — убрать из OPENSPEC_ARTIFACTS, из скелета и "
|
|
||||||
f"записать в журнал версий канона"
|
|
||||||
)
|
|
||||||
|
|
||||||
print()
|
|
||||||
if not diffs:
|
|
||||||
print("Слепок сходится. Осталось глазами: не изменились ли придирки")
|
|
||||||
print("валидатора — их скрипт проверить не может, они проявляются только")
|
|
||||||
print("отказом `openspec validate --strict` на живой спеке.")
|
|
||||||
return OK
|
|
||||||
print("Разошлось:")
|
|
||||||
for line in diffs:
|
|
||||||
print(f" - {line}")
|
|
||||||
print()
|
|
||||||
print("Правится в трёх местах сразу: константы этого скрипта, скелет")
|
|
||||||
print("`openspec/config.yaml` в skeletons.md и запись в changelog.md —")
|
|
||||||
print("иначе проекты останутся на прежней форме молча.")
|
|
||||||
return DRIFT
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog="docs.py",
|
prog="docs.py",
|
||||||
@@ -895,12 +626,6 @@ def main() -> int:
|
|||||||
p_ver.add_argument("--dir", default=".", help="корень проекта")
|
p_ver.add_argument("--dir", default=".", help="корень проекта")
|
||||||
p_ver.set_defaults(func=cmd_version)
|
p_ver.set_defaults(func=cmd_version)
|
||||||
|
|
||||||
p_form = sub.add_parser(
|
|
||||||
"openspec-form",
|
|
||||||
help="перепроверить форму config.yaml по живому OpenSpec",
|
|
||||||
)
|
|
||||||
p_form.set_defaults(func=cmd_openspec_form)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
try:
|
try:
|
||||||
return args.func(args)
|
return args.func(args)
|
||||||
@@ -9,8 +9,8 @@ description: Вести содержимое документов канона
|
|||||||
Определение канона и роли документов — [канон](../canon/references/canon.md),
|
Определение канона и роли документов — [канон](../canon/references/canon.md),
|
||||||
здесь не пересказывается.
|
здесь не пересказывается.
|
||||||
|
|
||||||
Главный вызывающий — **шаг синка документации в пайплайне задачи**. Пайплайн
|
Главный вызывающий — **шаг синка документации в конвейере задачи**. Конвейер
|
||||||
живёт в другом плагине и зовёт этот скилл по имени; проект без пайплайна ведёт
|
живёт в другом плагине и зовёт этот скилл по имени; проект без конвейера ведёт
|
||||||
документацию тем же скиллом вручную.
|
документацию тем же скиллом вручную.
|
||||||
|
|
||||||
## Правило, из которого всё следует
|
## Правило, из которого всё следует
|
||||||
@@ -55,25 +55,39 @@ description: Вести содержимое документов канона
|
|||||||
- passport, security, conventions, review — не требуется: изменение внутреннее
|
- passport, security, conventions, review — не требуется: изменение внутреннее
|
||||||
```
|
```
|
||||||
|
|
||||||
## Сверка — не здесь, а на сессии
|
## Сверка — не здесь, а в `av-dev-docs:healthcheck`
|
||||||
|
|
||||||
Синк правит документы поодиночке, а расходятся они **между собой**: факт,
|
Синк правит документы поодиночке, а расходятся они **между собой**: факт,
|
||||||
дописанный в `architecture.md`, уже живёт в `CLAUDE.md`; периметр в
|
дописанный в `architecture.md`, уже живёт в `CLAUDE.md`; периметр в
|
||||||
`security.md` не знает про новый эндпоинт. Поймать это на своей же правке нельзя,
|
`security.md` не знает про новый эндпоинт. Поймать это на своей же правке нельзя,
|
||||||
и судит это агент `doc-consistency`.
|
и судит это агент `doc-consistency`.
|
||||||
|
|
||||||
**Но синк его не зовёт.** Оба судьи документов — `doc-consistency` и
|
**Но синк его не зовёт.** Обоими судьями документов владеет скилл
|
||||||
`doc-code-drift` — зовутся раз в спринт, шагом сессии, на весь канон разом.
|
`av-dev-docs:healthcheck`, и зовут их на весь канон разом, а не на пачку,
|
||||||
Причина в цене: `doc-consistency` на `opus` по каждой сделанной задаче — самая
|
отобранную работой. Причина в цене: `doc-consistency` на `opus` по каждой
|
||||||
дорогая церемония процесса, а `doc-code-drift` хоть и на `sonnet`, но читает
|
сделанной задаче — самая дорогая церемония процесса, а `doc-code-drift` хоть и на
|
||||||
репозиторий целиком. К тому же расхождение между двумя документами по определению
|
`sonnet`, но читает репозиторий целиком. К тому же расхождение между двумя
|
||||||
требует двух документов, а на большинстве задач синк правит один.
|
документами по определению требует двух документов, а на большинстве задач синк
|
||||||
|
правит один.
|
||||||
|
|
||||||
Что теряется: привязка находки к задаче, которая её породила. Что выигрывается,
|
Что теряется: привязка находки к задаче, которая её породила. Что выигрывается,
|
||||||
кроме денег: синк перестаёт отбирать пачку, и в неё попадают документы,
|
кроме денег: синк перестаёт отбирать пачку, и в неё попадают документы,
|
||||||
которых работа не касалась, — расхождение, внесённое правкой в одном месте, там
|
которых работа не касалась, — расхождение, внесённое правкой в одном месте, там
|
||||||
и живёт.
|
и живёт.
|
||||||
|
|
||||||
|
## Вычитка — наоборот, здесь
|
||||||
|
|
||||||
|
**Язык правленого вычитывается на синке, и зовёшь агента `doc-wording` ты.**
|
||||||
|
Довод обратный доводу про судей: он читает **только названную пачку**, стоит
|
||||||
|
дёшево и ищет ровно то, что портится в момент письма, — залог, оценку без факта,
|
||||||
|
жаргон, термин без ввода. Ждать `healthcheck` здесь нечего: через месяц никто уже
|
||||||
|
не помнит, какую фразу имел в виду автор.
|
||||||
|
|
||||||
|
Позови его **последним шагом синка**, отдав список файлов, которых чек-лист
|
||||||
|
коснулся, — и назови этот список в промпте: по нему же он судит, известен ли
|
||||||
|
термин. Ничего не правивший синк агента не зовёт. Находки он отдаёт готовыми
|
||||||
|
формулировками, подставляешь их ты.
|
||||||
|
|
||||||
## ADR — промоут, а не второе сочинение
|
## ADR — промоут, а не второе сочинение
|
||||||
|
|
||||||
Обоснование уже написано: `opsx:propose` кладёт `design.md` в каждый change, и
|
Обоснование уже написано: `opsx:propose` кладёт `design.md` в каждый change, и
|
||||||
@@ -116,13 +130,50 @@ description: Вести содержимое документов канона
|
|||||||
дать этому остаться в контексте. Признак: ты правил разбор, опираясь на то, чего
|
дать этому остаться в контексте. Признак: ты правил разбор, опираясь на то, чего
|
||||||
нет ни в одном документе.
|
нет ни в одном документе.
|
||||||
|
|
||||||
|
## Обращение к соседним плагинам
|
||||||
|
|
||||||
|
Два раздела ниже — запись в `review.md` и промоут в конвенции — берут форму у
|
||||||
|
конвейера ревью: она принадлежит ему, а не канону. Берут **вызовом скилла**, а не
|
||||||
|
чтением файла по пути.
|
||||||
|
|
||||||
|
**Копия.** Дом правила — `shared/plugin-boundary.md` в репозитории плагинов.
|
||||||
|
Правится дом, а не этот файл.
|
||||||
|
|
||||||
|
<!-- копия: граница-плагинов из shared/plugin-boundary.md -->
|
||||||
|
|
||||||
|
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
|
||||||
|
месте.
|
||||||
|
|
||||||
|
**Чужой скилл зовётся полным именем** — `av-dev-docs:canon`, `av-dev-tasks:tasks`,
|
||||||
|
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
|
||||||
|
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
|
||||||
|
|
||||||
|
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
|
||||||
|
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
|
||||||
|
прочитает его сам.
|
||||||
|
|
||||||
|
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
|
||||||
|
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
|
||||||
|
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
|
||||||
|
|
||||||
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||||
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
|
||||||
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
Чем оборачивается отсутствие конвейера — в каждом из двух разделов отдельно: без
|
||||||
|
него работа не отменяется, отменяется только его процедура.
|
||||||
|
|
||||||
## Запись в `review.md`
|
## Запись в `review.md`
|
||||||
|
|
||||||
Файл держит два раздела с разными сроками жизни — журнал дефектов и настройку
|
Файл держит два раздела с разными сроками жизни — журнал дефектов и настройку
|
||||||
конвейера. **Что в каком и в какой форме — в
|
конвейера. **Что в каком и в какой форме — в
|
||||||
[каноне](../canon/references/canon.md), раздел `review.md`**; подробности формы
|
[каноне](../canon/references/canon.md), раздел `review.md`**; подробности формы
|
||||||
записи и выбор адреса, куда она ведёт, — у конвейера ревью проекта (при
|
записи и выбор адреса, куда она ведёт, — у конвейера ревью проекта (при
|
||||||
`av-dev-pipeline` — `Skill av-dev-pipeline:review-pipeline`, его
|
`av-dev-code` — `Skill av-dev-code:review`, его
|
||||||
`references/review-journal.md`). **Конвейера в проекте нет** — пиши по форме из
|
`references/review-journal.md`). **Конвейера в проекте нет** — пиши по форме из
|
||||||
скелета `review.md`, которую положил канон, и скажи в докладе, что подробностей
|
скелета `review.md`, которую положил канон, и скажи в докладе, что подробностей
|
||||||
формы взять негде.
|
формы взять негде.
|
||||||
@@ -130,13 +181,13 @@ description: Вести содержимое документов канона
|
|||||||
Твоя часть на синке: **дефект пишется сразу**, а не «потом, когда починим».
|
Твоя часть на синке: **дефект пишется сразу**, а не «потом, когда починим».
|
||||||
Со временем теряется не факт, а то, почему дефект не поймали, — единственное,
|
Со временем теряется не факт, а то, почему дефект не поймали, — единственное,
|
||||||
ради чего журнал есть. И решение сузить проверки (перестали звать проход, понизили
|
ради чего журнал есть. И решение сузить проверки (перестали звать проход, понизили
|
||||||
метка) обязано попасть в раздел настройки, а не остаться в отчёте ревью.
|
метку) обязано попасть в раздел настройки, а не остаться в отчёте ревью.
|
||||||
|
|
||||||
## Промоут в конвенции
|
## Промоут в конвенции
|
||||||
|
|
||||||
Находка → конвенция → правило линтера → **удаление из прозы**. Процедура целиком
|
Находка → конвенция → правило линтера → **удаление из прозы**. Процедура целиком
|
||||||
принадлежит конвейеру ревью проекта (при `av-dev-pipeline` — его
|
принадлежит конвейеру ревью проекта (при `av-dev-code` — его
|
||||||
`references/promote.md`, читается через `Skill av-dev-pipeline:review-pipeline`);
|
`references/promote.md`, читается через `Skill av-dev-code:review`);
|
||||||
роль каталога конвенций — в [каноне](../canon/references/canon.md). **Конвейера
|
роль каталога конвенций — в [каноне](../canon/references/canon.md). **Конвейера
|
||||||
нет** — три шага всё равно твои, просто без его процедуры: сформулируй правило,
|
нет** — три шага всё равно твои, просто без его процедуры: сформулируй правило,
|
||||||
поищи, чем оно механизируется, и вычеркни прозу, если механизировалось.
|
поищи, чем оно механизируется, и вычеркни прозу, если механизировалось.
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
---
|
||||||
|
name: healthcheck
|
||||||
|
description: "Проверка здоровья документации проекта судом, а не машиной: не разошлись ли документы между собой и с кодом. Зовёт двух агентов на весь канон разом — doc-consistency (один факт в двух домах, прямое противоречие, поведение в architecture.md вместо спек, ADR без парного статуса, число без провенанса) и doc-code-drift (протухший факт: имя ветки, команды, пути, зависимости поимённо, настройки с числом, единые точки, capability). Разбирает урожай порциями: строка на замену идёт в документ сразу, работа больше абзаца становится задачей. Использовать, когда с прошлой сверки сделан десяток задач, когда вернулись к проекту после перерыва, перед тем как опереться на документ в решении, а также шагом adopt и upgrade. Дорого — не на каждой задаче. Раскладку и версию канона проверяет скилл canon, язык документов — агент doc-wording."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Здоровье документации
|
||||||
|
|
||||||
|
Проверяет то, **чего машина не видит**: разошлись ли документы между собой и с
|
||||||
|
кодом. Раскладка, версия, битые ссылки, нетронутые плейсхолдеры — это `canon
|
||||||
|
check` и его скрипт; здесь начинается там, где кончается `docs.py`.
|
||||||
|
|
||||||
|
Разрез проверяемый: **машина сверяет форму, этот скилл — утверждения**. «В
|
||||||
|
`architecture.md` есть раздел» проверит скрипт. «В `architecture.md` написано,
|
||||||
|
что зависимость одна, а в манифесте их три» — суждение, и его выносит агент.
|
||||||
|
|
||||||
|
## Когда звать
|
||||||
|
|
||||||
|
**Зовёт человек**, но признак наблюдаемый, а не календарный:
|
||||||
|
|
||||||
|
- **с прошлой сверки сделан десяток задач.** Документы протухают ровно от
|
||||||
|
сделанной работы: переименованная цель сборки, ушедшая зависимость, второй
|
||||||
|
способ делать то, что обзор объявил единственным, факт, дописанный в
|
||||||
|
`architecture.md` и уже живущий в `CLAUDE.md`;
|
||||||
|
- **вернулись к проекту после перерыва** — прежде чем опираться на написанное;
|
||||||
|
- **перед тем как опереться на документ в решении**, если оно дорогое;
|
||||||
|
- шагом `adopt` и шагом `upgrade` — их зовёт скилл `canon` сам.
|
||||||
|
|
||||||
|
**Не на каждой задаче и не на каждом синке документации.** Цена реальная:
|
||||||
|
`doc-consistency` идёт на `opus`, потому что сличение утверждений — суждение;
|
||||||
|
`doc-code-drift` хоть и на `sonnet`, но читает репозиторий целиком. Прогон по
|
||||||
|
каждой сделанной задаче был бы самой дорогой церемонией процесса, а находок дал
|
||||||
|
бы почти те же: документы расходятся не с одной задачи, а с десятка.
|
||||||
|
|
||||||
|
Прежде оба звались шагом сессии между спринтами. Спринтов нет, и **момент
|
||||||
|
пришлось назвать заново** — иначе их не звал бы никто, кроме разовых `adopt` и
|
||||||
|
`upgrade`, то есть на живом проекте никогда.
|
||||||
|
|
||||||
|
## Обращение к соседним плагинам
|
||||||
|
|
||||||
|
**Копия.** Дом — `shared/plugin-boundary.md` в репозитории плагинов. Правится
|
||||||
|
дом, а не этот файл.
|
||||||
|
|
||||||
|
<!-- копия: граница-плагинов из shared/plugin-boundary.md -->
|
||||||
|
|
||||||
|
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
|
||||||
|
месте.
|
||||||
|
|
||||||
|
**Чужой скилл зовётся полным именем** — `av-dev-docs:canon`, `av-dev-tasks:tasks`,
|
||||||
|
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
|
||||||
|
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
|
||||||
|
|
||||||
|
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
|
||||||
|
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
|
||||||
|
прочитает его сам.
|
||||||
|
|
||||||
|
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
|
||||||
|
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
|
||||||
|
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
|
||||||
|
|
||||||
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||||
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
|
||||||
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
Здесь сосед один: `av-dev-tasks:tasks`, когда находка тянет на задачу. Его нет —
|
||||||
|
находки остаются списком в докладе, и это говорится строкой.
|
||||||
|
|
||||||
|
## Пачка — весь канон, и это не расточительство
|
||||||
|
|
||||||
|
Оба агента зовутся **на весь канон разом**, а не на пачку, отобранную работой.
|
||||||
|
|
||||||
|
Когда пачку отбирала работа, без присмотра оставалось ровно то, чего работа не
|
||||||
|
касалась: правка, отменившая решение, живёт в одном документе, а парный статус
|
||||||
|
нужен в другом; факт, продублированный год назад, не попадёт ни в один диапазон
|
||||||
|
диффа. Канон мал — он читается целиком, и цена этого известна заранее.
|
||||||
|
|
||||||
|
## Кого зовёшь и что передаёшь
|
||||||
|
|
||||||
|
| Агент | Что смотрит | Читает | Модель |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `doc-consistency` | смысловой дубль, прямое противоречие между документами, поведение в `architecture.md` вместо спек, ADR без ссылки и парного статуса, число без провенанса, заглушка вместо честной строки | `docs/`, `openspec/` | `opus` |
|
||||||
|
| `doc-code-drift` | протухший факт: имя ветки, команды, пути, зависимости поимённо, настройки с числом, единые точки проекта, capability | весь репозиторий | `sonnet` |
|
||||||
|
|
||||||
|
**`doc-code-drift` обязан получить раздел запретов `CLAUDE.md`.** Он гоняет
|
||||||
|
команды — только читающие, — и без перечня запретов не знает, чего в этом
|
||||||
|
проекте запускать нельзя. Не передал — он либо остановится, либо тронет то, чего
|
||||||
|
трогать не следовало.
|
||||||
|
|
||||||
|
**Судит не тот, кто писал.** Ни один из двоих ничего не правит: оба возвращают
|
||||||
|
готовые формулировки, подставляешь ты. Самопроверка документа слабее всего ровно
|
||||||
|
там, где формулировка казалась удачной при написании.
|
||||||
|
|
||||||
|
Одного из двух можно позвать отдельно — но **скажи в докладе, кого именно
|
||||||
|
позвал**. Доклад, умолчавший об этом, читается как «сверено целиком».
|
||||||
|
|
||||||
|
## Разбор урожая
|
||||||
|
|
||||||
|
Находки — обычный материал правки, и разбирать их надо **порциями**, а не одним
|
||||||
|
заходом: тридцать находок подряд получают «принято» не потому, что верны, а
|
||||||
|
потому, что разбор затянулся.
|
||||||
|
|
||||||
|
По каждой находке ровно три исхода:
|
||||||
|
|
||||||
|
1. **Строка на замену** — правь документ сразу. Формулировка уже готова, спорить
|
||||||
|
не с чем, и откладывание превращает её в задачу дороже самой правки.
|
||||||
|
2. **Работа больше чем на абзац** — задача типа `chore`. Заводит её **не этот
|
||||||
|
скилл**: вызови Skill `av-dev-tasks:tasks`, у него свой формат, дедупликация
|
||||||
|
против беклога и кладбища. Плагина нет — отдай списком в докладе и скажи это
|
||||||
|
строкой.
|
||||||
|
3. **Не находка** — агент ошибся, документ прав. Скажи это прямо: неразобранная
|
||||||
|
находка и отклонённая различаются, и вторая экономит время на следующем
|
||||||
|
прогоне. Класс ошибок, который повторяется, идёт в `docs/review.*`, раздел
|
||||||
|
настройки, — там дом типовых ложноположительных.
|
||||||
|
|
||||||
|
## Доклад
|
||||||
|
|
||||||
|
- **Кого позвал** — обоих или одного, и почему одного.
|
||||||
|
- Находки по каждому агенту: сколько, что поправлено сразу, что стало задачей
|
||||||
|
(со слагами), что отклонено и почему.
|
||||||
|
- **Границы покрытия**: что смотрели и чего не смотрели. У `doc-code-drift` она
|
||||||
|
идёт из его собственного отчёта — перечень фактов у него закрытый, и он
|
||||||
|
называет, какие из них проверить было нечем.
|
||||||
|
- Канона в проекте нет вовсе — это исход, а не пустой прогон: скажи строкой и
|
||||||
|
предложи `av-dev-docs:canon`.
|
||||||
|
|
||||||
|
## Чего этот скилл не делает
|
||||||
|
|
||||||
|
- **Не проверяет раскладку, версию и ссылки** — это `canon check`, там машина.
|
||||||
|
- **Не судит язык** документов: залог, англицизмы, жаргон, термин без дома — это
|
||||||
|
агент `doc-wording`, и зовут его отдельно, по пачке правленных документов.
|
||||||
|
Звонящие у него названные — последний шаг синка в `av-dev-docs:docs`, шаг 9
|
||||||
|
`av-dev-docs:init` и шаг вычитки в обоих режимах `canon`, — просто ни один из
|
||||||
|
них не здесь. У него другой ритм: он нужен там, где текст только что писали, а
|
||||||
|
не там, где он год лежал. Оркестровать его нечем — он один и работает по
|
||||||
|
названному списку.
|
||||||
|
- **Не правит документы за агентов** — они возвращают формулировки, решение
|
||||||
|
подставить принимает человек или ты по его правилу.
|
||||||
|
- **Не заводит задачи** — этим владеет `av-dev-tasks:tasks`.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: init
|
name: init
|
||||||
description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром, первые цели в роадмапе и скелет остальных документов. Заводит и OpenSpec (openspec init) с настроенным openspec/config.yaml — дом темы requirements, без которого не работают ни propose, ни ревью. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon."
|
description: "Завести новый проект — сессия вопросов и ответов по свободному описанию замысла, из которой рождается первичная документация по канону av-dev: паспорт, CLAUDE.md с инвариантами и командами, модель угроз с периметром и скелет остальных документов; первые цели собирает интервью, а записывает их вызовом скилла av-dev-tasks:tasks — роадмап принадлежит плагину задач. OpenSpec заводит не сам, а вызовом скилла av-dev-code:openspec — каталог принадлежит конвейеру; плагина конвейера нет — шаг пропускается строкой доклада. Использовать, когда начинают новый проект с нуля, когда есть только текст «что мне нужно и почему» и надо превратить его в рабочую документацию, когда просят провести стартовое интервью по брифу. Проект, где документация уже как-то ведётся, переводит скилл canon."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Заведение нового проекта
|
# Заведение нового проекта
|
||||||
@@ -26,13 +26,17 @@ description: "Завести новый проект — сессия вопро
|
|||||||
| `passport.md` | `architecture.md` |
|
| `passport.md` | `architecture.md` |
|
||||||
| `CLAUDE.md` | `database.md` |
|
| `CLAUDE.md` | `database.md` |
|
||||||
| `security.md` | `conventions/` |
|
| `security.md` | `conventions/` |
|
||||||
| `docs/tasks/ROADMAP.md` — первые цели | `research/`, `adr/` |
|
| `docs/.pm.json` | `research/`, `adr/` |
|
||||||
| `docs/.pm.json` | `review.md` — журнал пуст, настройка появится с первым ревью |
|
| | `review.md` — журнал пуст, настройка появится с первым ревью |
|
||||||
| `openspec/config.yaml` | |
|
|
||||||
|
|
||||||
Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет,
|
Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет,
|
||||||
заводится первой задачей». Проход читает её как факт.
|
заводится первой задачей». Проход читает её как факт.
|
||||||
|
|
||||||
|
**`tasks/ROADMAP.md` в таблице нет намеренно.** Первые цели `init` собирает
|
||||||
|
интервью (блок 6), но записывает их не он: каталогом задач и формой целей владеет
|
||||||
|
`av-dev-tasks:tasks`, и это шаг 7. Плагина нет — цели остаются списком в докладе,
|
||||||
|
роадмапа в проекте не появляется, и это говорится строкой.
|
||||||
|
|
||||||
## Порядок интервью — зависимость, а не удобство
|
## Порядок интервью — зависимость, а не удобство
|
||||||
|
|
||||||
Каждый блок опирается на ответ предыдущего; переставлять нельзя.
|
Каждый блок опирается на ответ предыдущего; переставлять нельзя.
|
||||||
@@ -65,32 +69,71 @@ description: "Завести новый проект — сессия вопро
|
|||||||
- **Развилка замысла — человеку, механика — сама.** Имена файлов, слаги, порядок
|
- **Развилка замысла — человеку, механика — сама.** Имена файлов, слаги, порядок
|
||||||
строк не выноси.
|
строк не выноси.
|
||||||
|
|
||||||
|
## Обращение к соседним плагинам
|
||||||
|
|
||||||
|
Два шага порядка работы — вызовы чужого: OpenSpec заводит конвейер, каталог задач
|
||||||
|
ведёт плагин задач. Ни того, ни другого `init` не делает руками.
|
||||||
|
|
||||||
|
**Копия.** Дом правила — `shared/plugin-boundary.md` в репозитории плагинов.
|
||||||
|
Правится дом, а не этот файл.
|
||||||
|
|
||||||
|
<!-- копия: граница-плагинов из shared/plugin-boundary.md -->
|
||||||
|
|
||||||
|
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
|
||||||
|
месте.
|
||||||
|
|
||||||
|
**Чужой скилл зовётся полным именем** — `av-dev-docs:canon`, `av-dev-tasks:tasks`,
|
||||||
|
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
|
||||||
|
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
|
||||||
|
|
||||||
|
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
|
||||||
|
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
|
||||||
|
прочитает его сам.
|
||||||
|
|
||||||
|
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
|
||||||
|
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
|
||||||
|
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
|
||||||
|
|
||||||
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||||
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
|
||||||
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
Чем оборачивается отсутствие каждого — на самих шагах 3 и 7. Заведение проекта
|
||||||
|
из-за этого не останавливается: проект без конвейера и без учёта задач законен.
|
||||||
|
|
||||||
## Порядок работы
|
## Порядок работы
|
||||||
|
|
||||||
1. Прочитай бриф целиком. Выпиши, на какие блоки интервью ответ уже есть.
|
1. Прочитай бриф целиком. Выпиши, на какие блоки интервью ответ уже есть.
|
||||||
2. Проведи интервью итерациями по ≤3 вопроса.
|
2. Проведи интервью итерациями по ≤3 вопроса.
|
||||||
3. **Заведи OpenSpec: `openspec init --tools claude`.** Каталог `openspec/` —
|
3. **OpenSpec — вызови Skill `av-dev-code:openspec`.** Он заводит каталог и
|
||||||
часть канона, а не соседняя технология: в нём дом темы `requirements`, и без
|
заменяет пример в `config.yaml` настройкой. Делается это **до первого
|
||||||
него не работают ни `opsx:propose`, ни ревью дизайна, ни сверка требований.
|
документа**: без `openspec/` не работают ни `opsx:propose`, ни ревью дизайна,
|
||||||
Команда кладёт ещё `.claude/skills/openspec-*` и `.claude/commands/opsx/*` —
|
ни сверка требований. Каталог принадлежит конвейеру, а не канону, поэтому
|
||||||
это её нормальная работа, не трогай их.
|
здесь только вызов — ни команды, ни формы файла `init` не знает.
|
||||||
|
|
||||||
|
**Вызов не разрешился** — проект без конвейера живёт без OpenSpec законно:
|
||||||
|
строка доклада, и дальше; `docs.py check` о каталоге тоже промолчит.
|
||||||
4. Заведи `docs/.pm.json` с текущей версией канона.
|
4. Заведи `docs/.pm.json` с текущей версией канона.
|
||||||
5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и
|
5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и
|
||||||
отдельным файлом не остаётся: два дома для одного замысла разойдутся на
|
отдельным файлом не остаётся: два дома для одного замысла разойдутся на
|
||||||
первом же уточнении.
|
первом же уточнении.
|
||||||
6. Заведи скелет остальных по [скелетам](../canon/references/skeletons.md) —
|
6. Заведи скелет остальных по [скелетам](../canon/references/skeletons.md) —
|
||||||
каждый с честной строкой.
|
каждый с честной строкой.
|
||||||
7. **Заполни `openspec/config.yaml`** по тем же скелетам. Файл из коробки —
|
7. Каталог задач и первые цели — **вызови скилл `av-dev-tasks:tasks`**: он владеет
|
||||||
закомментированный пример на английском; он **заменяется целиком**, потому что
|
форматом целей и задач. Не разрешился — учёт задач остаётся владельцу, и это
|
||||||
нетронутый выглядит настроенным, а работает как пустой. Пиши туда только то,
|
тоже строка доклада.
|
||||||
что нужно **в момент порождения артефакта**: язык, правила именования
|
8. `docs.py check` из скилла `canon` — до отсутствия дрейфа. Замечания о
|
||||||
capability, придирки валидатора и **адреса** `docs/passport.md` и `CLAUDE.md`.
|
|
||||||
Инварианты, конвенции и правило ревью не пересказывай — у них есть дома, и
|
|
||||||
второй дом разойдётся с первым молча.
|
|
||||||
8. Каталог задач и первые цели — **вызови скилл `av-dev-pm:tasks`**: он владеет
|
|
||||||
форматом целей и задач.
|
|
||||||
9. `docs.py check` из скилла `canon` — до отсутствия дрейфа. Замечания о
|
|
||||||
незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа.
|
незаполненных плейсхолдерах остаются: их закрывает не `init`, а работа.
|
||||||
|
9. **Вычитай написанное — агент `doc-wording`**, по пачке заполненных документов
|
||||||
|
(`passport.md`, `CLAUDE.md`, `security.md`). Здесь он нужен сильнее, чем где
|
||||||
|
бы то ни было: весь текст сочинён только что и по свободному брифу человека, а
|
||||||
|
бриф — это как раз залог, оценки без факта и жаргон. Скелеты с честной строкой
|
||||||
|
в пачку не клади, вычитывать в них нечего. Находки — готовые формулировки,
|
||||||
|
подставляешь их ты.
|
||||||
10. Покажи человеку, что получилось, и **отдельным списком** — что выведено из
|
10. Покажи человеку, что получилось, и **отдельным списком** — что выведено из
|
||||||
брифа, что предположено, что осталось неизвестным. Правят по этим строкам.
|
брифа, что предположено, что осталось неизвестным. Правят по этим строкам.
|
||||||
|
|
||||||
@@ -98,7 +141,7 @@ description: "Завести новый проект — сессия вопро
|
|||||||
|
|
||||||
- Содержимое канона по ходу разработки ведёт скилл `docs`.
|
- Содержимое канона по ходу разработки ведёт скилл `docs`.
|
||||||
- Раскладку проверяет `canon check`.
|
- Раскладку проверяет `canon check`.
|
||||||
- Первую задачу берёт пайплайн проекта; `architecture.md` и `conventions/`
|
- Первую задачу берёт конвейер проекта; `architecture.md` и `conventions/`
|
||||||
наполняются его шагом синка, а не заранее.
|
наполняются его шагом синка, а не заранее.
|
||||||
|
|
||||||
## Чего этот скилл не делает
|
## Чего этот скилл не делает
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "av-dev-pipeline",
|
|
||||||
"description": "Проведение задачи через полный цикл Spec Driven Development и конвейер ревью с детерминированным гейтом, сверкой со спеками, враждебными постановками, эксплуатационным постмортемом, независимой реализацией и обязательным триажем; плюс прогон нескольких задач разом по одной в изолированном worktree. Требует OpenSpec. Задача принимается и обычным текстом. Плагин av-dev-pm опционален: он даёт документы канона, из которых проходы читают проектную конкретику, и учёт задач; без него прогон деградирует поразрядно и называет это строкой.",
|
|
||||||
"author": {
|
|
||||||
"name": "Anton Vakhrushev",
|
|
||||||
"email": "anwinged@gmail.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,487 +0,0 @@
|
|||||||
---
|
|
||||||
name: task-batch
|
|
||||||
description: Проводит несколько задач разом — планирует порядок и пересечения, гонит каждую задачу отдельным сабагентом в своём git worktree через task-pipeline (по умолчанию по одной задаче за раз; параллельно по графу зависимостей — по явной просьбе), интегрирует по одной ветке через rebase + fast-forward (линейная история), проверяет полноту ревью каждой ветки и в конце сверяет стыки, возникшие от слияния. Набор задач приходит извне. Использовать, когда просят сделать несколько задач сразу.
|
|
||||||
---
|
|
||||||
|
|
||||||
# Батч задач
|
|
||||||
|
|
||||||
Оркестратор **набора** задач. Планирует порядок, раскидывает задачи по
|
|
||||||
изолированным worktree, каждую проводит через полный цикл
|
|
||||||
`av-dev-pipeline:task-pipeline`, затем сводит в основную ветку линейной историей
|
|
||||||
и делает финальную сверку. Тонкая обёртка над пайплайном задачи — не
|
|
||||||
переизобретай её шаги, вызывай как есть.
|
|
||||||
|
|
||||||
**По умолчанию задачи идут по одной**, в порядке зависимостей. Параллельно — по
|
|
||||||
явной просьбе, и тогда параллельность **по графу зависимостей**: одновременно
|
|
||||||
гонится только то, между чем нет ни зависимости, ни пересечения. Правило и его
|
|
||||||
цена — в шаге 4.
|
|
||||||
|
|
||||||
Работай **максимально автономно**, по тому же принципу, что и одиночный пайплайн:
|
|
||||||
вопрос, который решать не тебе, записывается и не останавливает поток; спрашиваем
|
|
||||||
только про **необратимое** (деплой, выкладка наружу, удаление или перезапись
|
|
||||||
рабочих данных). Механику — планирование, worktree, rebase, интеграцию, чистку —
|
|
||||||
делаем без спроса.
|
|
||||||
|
|
||||||
## Предпосылки
|
|
||||||
|
|
||||||
- **OpenSpec и скиллы `opsx:*`** — на них стоит цикл внутри каждого сабагента и
|
|
||||||
проход `review-specs` финальной сверки. Проекта без OpenSpec это касается так
|
|
||||||
же, как одиночного пайплайна (см. его раздел «Предпосылки»).
|
|
||||||
- **Скиллы зовутся с пространством имён**: `av-dev-pipeline:task-pipeline`,
|
|
||||||
`av-dev-pipeline:review-pipeline`, `av-dev-pm:tasks`. Короткое имя
|
|
||||||
может разрешиться в устаревшую проектную копию, и это произойдёт молча — в
|
|
||||||
charter'е сабагента пиши полное имя, он твоего контекста не видит.
|
|
||||||
- **Проектные копии этих скиллов и агентов при установке плагина удаляются.**
|
|
||||||
|
|
||||||
Перед стартом прочитай `CLAUDE.md` проекта: оттуда берутся **имя основной
|
|
||||||
ветки** (оно подставляется в каждую команду git ниже), команда и семантика
|
|
||||||
гейта, инварианты и что запускать запрещено. Раскладка нумерованных артефактов —
|
|
||||||
`docs/database.md` и `docs/.pm.json` (ключ `migrations`).
|
|
||||||
|
|
||||||
**Документов канона нет — проект к нему не приведён.** Скажи это строкой и
|
|
||||||
предложи `av-dev-pm:canon` **до первой задачи**: иначе каждая задача батча
|
|
||||||
заплатит поразрядной деградацией ревью, а имя основной ветки придётся
|
|
||||||
угадывать.
|
|
||||||
|
|
||||||
## Границы
|
|
||||||
|
|
||||||
- **Набор задач приходит извне.** Батч его не формирует: не выбирает из беклога,
|
|
||||||
не приоритизирует, не решает, что важнее. Набор не задан — попроси его у
|
|
||||||
вызывающего и остановись.
|
|
||||||
- **Батч не владеет спринтом и целями.** Он сообщает исход по каждой задаче в тех
|
|
||||||
же трёх словах, что и `task-pipeline`: сделана / не доведена / оказалась крупнее
|
|
||||||
задачи.
|
|
||||||
- **Задачи закрывает пайплайн внутри каждого сабагента**, шагом 12 — после
|
|
||||||
коммита работы и **отдельным коммитом учёта**, вызовом Skill `av-dev-pm:tasks`.
|
|
||||||
Батч сам записей учёта не трогает: он не знает, чем кончилась приёмка, и
|
|
||||||
дублировать закрытие ему незачем. Но грязное дерево после сабагента — **его**
|
|
||||||
проблема: на нём откажут и `rebase`, и `worktree remove` (см. шаг 6). Урожай ревью батч
|
|
||||||
отдаёт списком, а задачи из него заводит тот, кто ведёт задачи проекта.
|
|
||||||
|
|
||||||
## Ключевое отличие от одиночного пайплайна
|
|
||||||
|
|
||||||
`task-pipeline` коммитит **в текущую ветку**, и при ручном запуске это основная
|
|
||||||
ветка. Здесь так нельзя, поэтому батч — **осознанное исключение**: временные
|
|
||||||
ветки и worktree заводятся лишь как средство изоляции, а конечное состояние — та
|
|
||||||
же линейная история основной ветки через rebase + fast-forward. Ветки после
|
|
||||||
вливания удаляются.
|
|
||||||
|
|
||||||
Изоляция нужна **в обоих режимах, а не только в параллельном**: батч не вливает
|
|
||||||
ветку, пока не проверил полноту её ревью (шаг 5), и упавшая задача обязана
|
|
||||||
остаться в своём worktree для ручного дожатия (шаг 6), не оставив следа в
|
|
||||||
основной ветке. В параллельном режиме к этому добавляется вторая причина —
|
|
||||||
задачи не должны видеть недоделанную работу друг друга.
|
|
||||||
|
|
||||||
## Модель исполнения
|
|
||||||
|
|
||||||
- Каждая задача = **один автономный сабагент** (`general-purpose`, чтобы иметь
|
|
||||||
доступ к Skill и Agent для вложенных чекпоинтов ревью), работающий **только в
|
|
||||||
своём worktree** и прогоняющий `task-pipeline` целиком на этой задаче.
|
|
||||||
- Оркестратор кода задач не пишет: он планирует, заводит worktree, запускает
|
|
||||||
сабагентов, проверяет полноту их ревью, интегрирует ветки и делает финальную
|
|
||||||
сверку.
|
|
||||||
- Стиль правок внутри — заточка под проект и конвенции, right-size, без
|
|
||||||
золочения.
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
### 1. Прочитать набор
|
|
||||||
|
|
||||||
Набор задан списком (слаги, файлы, описания) — прочитай файл каждой задачи и
|
|
||||||
связанные спеки и черновики. Сырьё (в терминах `av-dev-pm` — запись типа
|
|
||||||
`research` с пустым разделом «Вопрос») включается, но помни: сабагент проведёт
|
|
||||||
его сперва через `opsx:explore`, это тяжелее и чаще упирается в вопрос.
|
|
||||||
|
|
||||||
### 2. Спланировать порядок и пересечения (автономно)
|
|
||||||
|
|
||||||
Для каждой задачи определи:
|
|
||||||
|
|
||||||
- **затронутые capability** — по её описанию и по каталогу актуальных спек
|
|
||||||
(`openspec/specs/`);
|
|
||||||
- **жёсткие зависимости**: задача B строится на результате A → A строго раньше B;
|
|
||||||
- **замеряющая задача** — та, чьё ревью будет доказывать находки **числами**. В
|
|
||||||
последовательном прогоне это ничего не меняет: она и так идёт одна. В
|
|
||||||
параллельном она гонится в волне **одна** (обоснование — ниже, в шаге 4).
|
|
||||||
Помечается здесь, на планировании, а не во время прогона, и **независимо от
|
|
||||||
режима**: состав волны определяется сейчас, режим может смениться просьбой уже
|
|
||||||
после плана, а метка ревью назовёт разметчик уже внутри пайплайна задачи,
|
|
||||||
после `propose`, — ключевать волну на ещё не сделанный выбор нельзя. Триггеры — по фактам о задаче, каждый
|
|
||||||
сам по себе достаточен:
|
|
||||||
- трогает схему хранилища, миграцию, формат на диске или объём хранимого;
|
|
||||||
- трогает конкурентность: транзакции, блокировки, фоновые циклы, общее
|
|
||||||
состояние;
|
|
||||||
- трогает размер тела, буфер, память, сжатие, ретеншен, темп потока;
|
|
||||||
- её тема названа в журнале `docs/review.md` как место, где уже ломалось.
|
|
||||||
|
|
||||||
Ни один триггер не сработал — задача не замеряющая, даже если её ревью
|
|
||||||
окажется `large`. Метка про глубину проверки, замеряющая — про соревнование за
|
|
||||||
железо; это разные вопросы, и совпадают они не всегда. Обратное тоже бывает и
|
|
||||||
тоже законно: помеченная задача, чьё ревью пошло меткой `small` или
|
|
||||||
`medium`, машину не займёт вовсе — меряющие проходы живут только в `large`.
|
|
||||||
Пометка от этого не снимается: она ставится **до** разметки, и перестраховка
|
|
||||||
здесь стоит одной волны, а ошибка — испорченных чисел;
|
|
||||||
- **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект
|
|
||||||
нумерует миграции (путь — `docs/.pm.json`, ключ `migrations`), посмотри последний
|
|
||||||
номер и **раздай номера всем задачам, которые, вероятно, их добавят**, до
|
|
||||||
запуска. Номер уходит в charter сабагента, и он берёт назначенный, а не
|
|
||||||
«следующий свободный».
|
|
||||||
|
|
||||||
**Это отдельная механика от правила волны, и она ему не служит** — их раньше
|
|
||||||
путали, и они тянули в разные стороны. Правило волны отвечает на вопрос «кто с
|
|
||||||
кем гонится одновременно», предраздача — на вопрос «какой номер берёт задача».
|
|
||||||
Раздача нужна там, где **две задачи одной под-пачки** добавляют нумерованный
|
|
||||||
артефакт: каждая считает «следующий свободный» по основной ветке, которая ещё
|
|
||||||
не видела соседку, и обе берут один номер. Миграции под это почти не попадают —
|
|
||||||
миграция и так триггер замеряющей задачи, а замеряющая идёт одна; но
|
|
||||||
нумерованные артефакты бывают не только миграциями. Поэтому номера раздаются
|
|
||||||
**всем** задачам с таким артефактом, независимо от того, в какой волне они
|
|
||||||
окажутся: раздача ничего не стоит, а её отсутствие ловится только конфликтом на
|
|
||||||
интеграции. Между волнами проблемы нет — ветка следующей волны берётся от
|
|
||||||
вершины, уже включающей предыдущие; в последовательном прогоне проблемы нет по
|
|
||||||
той же причине, но номера всё равно раздай: режим может смениться просьбой, а
|
|
||||||
раздача бесплатна;
|
|
||||||
- **жёстко сериализуем** (не гоняем одновременно) настоящие пересечения:
|
|
||||||
- **одна capability на несколько задач** — две задачи, правящие одну спеку (тем
|
|
||||||
более одно и то же `### Requirement`), дают не текстовый, а **семантический**
|
|
||||||
конфликт при архивации; сериализуем по смыслу, а не только по файлам;
|
|
||||||
- пересечение по одним и тем же исходникам;
|
|
||||||
- **мягкие конфликты** сериализовать не надо: файлы-перечни, где каждая задача
|
|
||||||
правит **свою** строку (индексы, оглавления, списки записей), и спеки разных
|
|
||||||
capability — разные строки и файлы, сливаются сами.
|
|
||||||
|
|
||||||
Собери план как **граф зависимостей**, а не как плоский список: рёбра — жёсткие
|
|
||||||
зависимости и сериализуемые пересечения. Дальше по режиму:
|
|
||||||
|
|
||||||
- **последовательно (умолчание)** — линеаризуй граф в один порядок:
|
|
||||||
топологический, а там, где он оставляет свободу, — раньше то, от чего зависит
|
|
||||||
больше задач, и раньше то, что правит общие для набора места. Волн нет,
|
|
||||||
замеряющие задачи ничем не отличаются от прочих;
|
|
||||||
- **параллельно (по просьбе)** — нарежь граф на **волны**: в одну волну попадают
|
|
||||||
только задачи, между которыми нет ребра; замеряющие стоят отдельными волнами по
|
|
||||||
одной.
|
|
||||||
|
|
||||||
Рёбра у графа двух видов, и путать их не надо: **зависимость** направлена (B без
|
|
||||||
результата A не делается), **пересечение** — нет (кто первый, неважно, лишь бы не
|
|
||||||
разом). Тот же словарь у графа проходов ревью — см. «Порядок прогона» в
|
|
||||||
`av-dev-pipeline:review-pipeline`.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
A["A: схема хранилища<br/>(замеряющая)"]
|
|
||||||
B["B: эндпоинт поверх A"]
|
|
||||||
C["C: формат лога"]
|
|
||||||
D["D: правит ту же capability, что C"]
|
|
||||||
|
|
||||||
A -->|зависимость| B
|
|
||||||
C -. пересечение — одна capability .- D
|
|
||||||
```
|
|
||||||
|
|
||||||
Этот граф даёт: **последовательно** — `A → B → C → D` (или `A → C → B → D`, обе
|
|
||||||
линеаризации законны); **параллельно** — волна 1 `A` одна (замеряющая), волна 2
|
|
||||||
`B` и `C`, волна 3 `D`.
|
|
||||||
|
|
||||||
Схемы в этом скилле — **пример и сводка**, правила ставит текст: при расхождении
|
|
||||||
прав он. (В `av-dev-pipeline:review-pipeline` наоборот — там граф прогона и есть
|
|
||||||
алгоритм, и старший он.)
|
|
||||||
|
|
||||||
Покажи план короткой репликой — режим, порядок или состав волн, какие задачи
|
|
||||||
признаны замеряющими и по какому триггеру, — и иди дальше.
|
|
||||||
|
|
||||||
### 3. Свежая база
|
|
||||||
|
|
||||||
Убедись, что рабочее дерево чистое и основная ветка свежая. Зафиксируй базовый
|
|
||||||
коммит. Новые ветки бери от свежей вершины; ветку следующей задачи (в
|
|
||||||
параллельном режиме — ветки следующей волны) — от вершины, уже включающей
|
|
||||||
результат предыдущих.
|
|
||||||
|
|
||||||
### 4. Провести задачи
|
|
||||||
|
|
||||||
**Умолчание — по одной задаче за раз, в порядке из шага 2.** Следующая стартует,
|
|
||||||
когда предыдущая вернула отчёт и (если она зелёная) влилась. Обосновывать это не
|
|
||||||
надо — обосновывается отступление. Причина умолчания в том, что задача батча
|
|
||||||
дороже прохода ревью: каждая тянет полный цикл пайплайна с гейтом, поднятием
|
|
||||||
сервиса и вложенным ревью, и две такие на одной машине дерутся за порты, рабочие
|
|
||||||
каталоги, СУБД и само железо. Последовательный прогон к тому же оставляет ревью
|
|
||||||
внутри задачи его собственное умолчание — **параллельные проходы**: машина
|
|
||||||
свободна, и выигрыш берётся там, где он ничего не стоит.
|
|
||||||
|
|
||||||
**Параллельно — по явной просьбе, и параллельность идёт по графу зависимостей.**
|
|
||||||
Одновременно гонится только то, между чем на шаге 2 не нашлось ребра: ни жёсткой
|
|
||||||
зависимости, ни общей capability, ни общих исходников. «Гони параллельно» не
|
|
||||||
означает «гони всё разом» — граф остаётся в силе, просьба лишь разрешает
|
|
||||||
использовать его ширину.
|
|
||||||
|
|
||||||
В параллельном режиме действуют два ограничения:
|
|
||||||
|
|
||||||
- **потолок — 2–3 задачи одновременно.** Больше трёх разом душат машину и
|
|
||||||
провоцируют гонки. Волну шире трёх бей на под-пачки по ≤3 и **гони под-пачки
|
|
||||||
последовательно**: следующая стартует, когда предыдущая вернула отчёты. Иначе
|
|
||||||
потолок обходится тривиально — шесть задач, запущенных «двумя под-пачками» в
|
|
||||||
одном сообщении, это шесть задач разом;
|
|
||||||
- **волна из одной задачи обязательна для замеряющей.** Задача, признанная на
|
|
||||||
шаге 2 **замеряющей**, гонится одна: соседний прогон на той же машине портит
|
|
||||||
числа, а находка с испорченным оракулом хуже отсутствующей — она выглядит
|
|
||||||
доказанной. Если замеряющая задача всё же пошла в общей волне, её отчёт обязан
|
|
||||||
нести строку в границах покрытия: замеры сняты под соседней нагрузкой.
|
|
||||||
|
|
||||||
Для каждой задачи (в параллельном режиме — для каждой задачи под-пачки):
|
|
||||||
|
|
||||||
1. Заведи worktree и ветку от текущей вершины:
|
|
||||||
`git worktree add <path> -b task/<slug> <основная ветка>`. Путь — во временном
|
|
||||||
каталоге проекта (`./tmp`), не в системном `/tmp`.
|
|
||||||
2. Запусти сабагента, `subagent_type: general-purpose`: в последовательном режиме
|
|
||||||
— одного и дождись отчёта; в параллельном — **по одному на задачу под-пачки,
|
|
||||||
всех в одном сообщении**. Charter сабагента:
|
|
||||||
- работай **строго в своём worktree** `<path>`; в другие каталоги и в основную
|
|
||||||
ветку не лезь;
|
|
||||||
- прогони Skill **`av-dev-pipeline:task-pipeline`** ровно на этой задаче,
|
|
||||||
полный цикл SDD с обоими чекпоинтами ревью;
|
|
||||||
- если задаче назначен **номер артефакта** — используй строго его;
|
|
||||||
- **метка ревью выбирает разметчик конвейера, а не ты и не сабагент.** Он
|
|
||||||
идёт внутри пайплайна задачи, шагом 4, сразу после `propose`, и его план
|
|
||||||
обслуживает оба чекпоинта. Метка в вызов не передаётся вовсе. Батч не
|
|
||||||
повод её понижать: «нас много и
|
|
||||||
мы спешим» — ровно тот стимул, из-за которого проходы пропускают, и он снят
|
|
||||||
тем, что регулятор не в руках у автора;
|
|
||||||
- **режим прогона проходов ревью — от режима батча**, и его называет charter,
|
|
||||||
а не сабагент: батч идёт по одной задаче → режим умолчательный, **`по
|
|
||||||
графу`** (машина свободна); батч идёт волнами → **`линейно`**, твой worktree
|
|
||||||
не один на машине, и этой причиной ты обязан объяснить режим в отчёте.
|
|
||||||
Внутренние рёбра графа — цепочку проходов, держащих машину — конвейер
|
|
||||||
соблюдает сам, в любом режиме;
|
|
||||||
- **если вложенные сабагенты недоступны** (движок не даёт запускать агентов из
|
|
||||||
агента) — не пропускай ревью и не понижай метка: проведи его **инлайн** по
|
|
||||||
тем же charter'ам `av-dev-pipeline`, сохранив обязательное — разметку первой
|
|
||||||
(план с темами и меткой), гейт до опиниативных проходов, состав по плану,
|
|
||||||
триаж последним. И **скажи в отчёте прямым текстом, что ревью шло инлайн**:
|
|
||||||
инлайновый проход видит контекст автора и потому разведён с ним слабее — а
|
|
||||||
инлайновая разметка вдобавок означает, что метка выбрал автор, и это
|
|
||||||
отдельная строка;
|
|
||||||
- **вернуть отчёт**, в котором обязательно: исход задачи одним из трёх слов;
|
|
||||||
**план прогона: метка с обоснованием, темы и их глубины**, и режим; что сделано; какие вопросы
|
|
||||||
записаны и куда; изменённые файлы; добавлялся ли нумерованный артефакт и с
|
|
||||||
каким номером; затронутые capability; состояние гейта; **перечень
|
|
||||||
тем с исходом по каждой**; **путь к
|
|
||||||
сохранённому отчёту триажа** (`openspec/changes/<id>/review/`, после
|
|
||||||
архивации — `openspec/changes/archive/<id>/review/`); шло ли ревью
|
|
||||||
инлайн; границы покрытия.
|
|
||||||
|
|
||||||
**Шаги 5 и 6 отрабатываются на вернувшейся задаче до старта следующей** — в
|
|
||||||
параллельном режиме на вернувшейся волне до старта следующей. Иначе ветка
|
|
||||||
следующей возьмётся от вершины, не видевшей предыдущую работу, и весь смысл
|
|
||||||
порядка из шага 2 теряется.
|
|
||||||
|
|
||||||
Сабагент, упершийся в вопрос, **не останавливает батч**: он записывает вопрос,
|
|
||||||
режет задачу до остатка и доводит остаток — либо, если остатка нет, возвращает
|
|
||||||
исход «не доведена». Оркестратор собирает такие вопросы и выносит их в финальный
|
|
||||||
доклад пачкой.
|
|
||||||
|
|
||||||
### 5. Проверить полноту ревью — до интеграции
|
|
||||||
|
|
||||||
**Ветка, чей отчёт не называет план прогона, не вливается.** Пропуск не отличим
|
|
||||||
от прохода без находок, и на уровне батча это ещё опаснее: отчётов много, каждый
|
|
||||||
выглядит полным, а сверять их некому, кроме тебя.
|
|
||||||
|
|
||||||
Сверка идёт в три шага, и порядок важен:
|
|
||||||
|
|
||||||
1. **Возьми план прогона** из отчёта задачи — таблицу «тема → дом → глубина → кто
|
|
||||||
закрывает» с меткой и обоснованием; он затем и заказан в обязательных полях
|
|
||||||
шага 4. Плана в отчёте нет — сверять не с чем; это само по себе основание не
|
|
||||||
вливать, пока сабагент не покажет план разметки задачи.
|
|
||||||
2. **Сверяй с независимым артефактом, а не с прозой отчёта.** Перечень проходов
|
|
||||||
бери из **сохранённого отчёта триажа** (`openspec/changes/<id>/review/` или
|
|
||||||
`openspec/changes/archive/<id>/review/` — задача доведена, change заархивирован) —
|
|
||||||
пайплайн обязан его туда положить. Проза сабагента написана тем же, кто мог
|
|
||||||
проход и пропустить: она подтверждает сама себя. Отчёта триажа на месте нет —
|
|
||||||
считай, что состав неизвестен, и дозапускай ревью целиком.
|
|
||||||
3. **Сверь план с исходом**: против каждой темы плана обязан стоять отчёт либо
|
|
||||||
названная причина его отсутствия. Раскладка «тема → кто закрывает с этой меткой» — в скилле `av-dev-pipeline:review-pipeline`.
|
|
||||||
|
|
||||||
Расхождение — не повод отменять задачу: дозапусти недостающие проходы **на
|
|
||||||
ветке**, в её worktree, через `av-dev-pipeline:review-pipeline`, и только потом
|
|
||||||
интегрируй.
|
|
||||||
|
|
||||||
**Передай в дозапуск тот же план.** Триаж требует его обязательным входом — без
|
|
||||||
плана он не может сверить, все ли размеченные темы вернули отчёт, а эта сверка и
|
|
||||||
есть то, ради чего дозапуск затевается. Плана не осталось (сабагент не сохранил
|
|
||||||
его в отчёте) — пусть повторит разметку задачи: это самый дешёвый проход
|
|
||||||
конвейера, и он дешевле, чем прогон, который нечем сверить.
|
|
||||||
|
|
||||||
**Находки дозапуска — такие же находки, и зелёный гейт их не отменяет.** Правило
|
|
||||||
интеграции «вливаем только зелёные» смотрит на гейт, а дозапущенный `critical`
|
|
||||||
гейт не красит: он был бы пропущен молча, если это не сказать прямо. Поэтому:
|
|
||||||
|
|
||||||
- `critical` или `major` из дозапуска — **вливание этой ветки останавливается**.
|
|
||||||
Помеченное `инлайн` чинится в её worktree, после починки — гейт, затем
|
|
||||||
интеграция. Помеченное `развилка` — вопрос в запись, задача режется до остатка
|
|
||||||
ровно так же, как это сделал бы пайплайн внутри;
|
|
||||||
- остатка нет — ветка не вливается и уходит в доклад как провалившаяся, со своим
|
|
||||||
worktree;
|
|
||||||
- `minor` и `nit` из дозапуска — в урожай доклада, вливанию не мешают.
|
|
||||||
|
|
||||||
Отчёт дозапуска приложи к отчёту задачи и назови в докладе (шаг 9), почему он
|
|
||||||
понадобился: систематический пропуск одного и того же прохода — находка о самом
|
|
||||||
конвейере, а не о задаче.
|
|
||||||
|
|
||||||
### 6. Интегрировать — rebase + fast-forward, по одной ветке
|
|
||||||
|
|
||||||
Сводим ветки **строго последовательно** (линейная история), в порядке
|
|
||||||
зависимостей. Вливаем **только зелёные**.
|
|
||||||
|
|
||||||
**Ветка задачи занята её worktree, и это определяет форму команд.** Пока worktree
|
|
||||||
жив (а удаляется он последним, после зелёного гейта), ветка `task/<slug>`
|
|
||||||
checkout'нута в нём, и `git rebase <основная> task/<slug>` из главного worktree
|
|
||||||
**падает**: `fatal: 'task/<slug>' is already used by worktree at …`. Поэтому
|
|
||||||
rebase делается **внутри worktree задачи**, а ff-слияние — из главного.
|
|
||||||
|
|
||||||
Для каждой готовой ветки `task/<slug>`:
|
|
||||||
|
|
||||||
- `git -C <path> rebase <основная>` — перенос ветки задачи на текущую вершину,
|
|
||||||
выполняется в её собственном worktree;
|
|
||||||
- резолв конфликтов (их почти нет — конфликтоопасное сериализовано, номера
|
|
||||||
розданы заранее). Неавтоматический конфликт — **не форсируй**: прерви
|
|
||||||
(`git -C <path> rebase --abort`), оставь ветку и worktree как есть, вынеси это
|
|
||||||
в доклад как нераспознанное пересечение;
|
|
||||||
- **дерево сабагента обязано быть чистым.** `git -C <path> status --porcelain`
|
|
||||||
до `rebase`: непусто — значит сабагент не довёл шаг 12 до коммита учёта (или
|
|
||||||
оставил мусор). Не форсируй и не коммить за него: назови задачу в докладе
|
|
||||||
недоведённой и оставь ветку с worktree. Молчаливый `rebase` на грязном дереве
|
|
||||||
всё равно откажет, но с сообщением про unstaged changes — а причина другая;
|
|
||||||
- **ненулевой код `rebase` относится к этой ветке и только к ней.** Прерванный
|
|
||||||
rebase в чужом worktree не трогает ни главное дерево, ни остальные ветки:
|
|
||||||
проверь `git -C <path> status` и `git status` — обе чистые. Уводить весь батч
|
|
||||||
в провалившиеся из-за одного ненулевого кода запрещено: это ложная причина,
|
|
||||||
из-за которой зелёные задачи не доедут до основной ветки. Провалилась одна —
|
|
||||||
провалилась одна;
|
|
||||||
- из главного worktree (он стоит на основной ветке — проверь
|
|
||||||
`git rev-parse --abbrev-ref HEAD`): `git merge --ff-only task/<slug>`. Ветку в
|
|
||||||
главном дереве **не переключай** — `git checkout task/<slug>` тоже упрётся в
|
|
||||||
занятость;
|
|
||||||
- после каждой интеграции — **гейт на основной ветке**. Красное — **откати эту
|
|
||||||
интеграцию** (`git reset --hard` на прошлую вершину), ветку с worktree сохрани,
|
|
||||||
задачу перечисли в докладе. Основная ветка **никогда** не остаётся
|
|
||||||
полузелёной;
|
|
||||||
- только после зелёного: `git worktree remove <path>` и
|
|
||||||
`git branch -d task/<slug>` — в этом порядке, иначе ветка снова занята.
|
|
||||||
|
|
||||||
**Политика частичного провала.** Упавшая задача (исход «не доведена», красные
|
|
||||||
тесты в её worktree, конфликт при rebase, невлитая из-за находок дозапуска)
|
|
||||||
**не блокирует остальные**: интегрируем все зелёные, упавшую оставляем в её
|
|
||||||
worktree и ветке нетронутой — ничего не удаляем, — и перечисляем в докладе с
|
|
||||||
причиной, отчётом и путём к worktree. Причина называется **настоящая**: «конфликт
|
|
||||||
rebase в файле X», а не «нераспознанное пересечение» на всякий случай.
|
|
||||||
|
|
||||||
### 7. Финальный гейт
|
|
||||||
|
|
||||||
На основной ветке после всех интеграций — гейт целиком. Зелёное обязательно; пока
|
|
||||||
красное, шаг 8 не начинается.
|
|
||||||
|
|
||||||
### 8. Финальная сверка — только то, чего не видел никто
|
|
||||||
|
|
||||||
Каждая задача уже прошла полный конвейер в своём worktree. Повторять его на
|
|
||||||
интегрированном диффе бессмысленно: те же проходы на тех же файлах дадут те же
|
|
||||||
находки и удорожат триаж. Здесь проверяется **только то, что появилось от
|
|
||||||
слияния**:
|
|
||||||
|
|
||||||
- запусти **по одному `review-specs` на каждую затронутую capability**, все
|
|
||||||
разом — граф здесь плоский: машину эти проходы не держат, ребра между ними нет,
|
|
||||||
а сама машина к этому моменту свободна (все сабагенты вернулись). Линейно —
|
|
||||||
только по причине из раздела «Порядок прогона»
|
|
||||||
`av-dev-pipeline:review-pipeline`, и причину назови;
|
|
||||||
- **задание у этих проходов особое, и это надо сказать прямо.** Живого change
|
|
||||||
здесь нет — все заархивированы, дельта-спек не существует. Источник требований
|
|
||||||
— **актуальные** `openspec/specs/<capability>/spec.md`, а предмет — стык:
|
|
||||||
требование, которое одна задача выполнила, а соседняя незаметно отменила; два
|
|
||||||
архивных change, по-разному описавшие одно поведение. Скажи проходу это прямо,
|
|
||||||
иначе он пойдёт искать дельты и не найдёт ничего;
|
|
||||||
- если задачи пересекались по файлам, добавь один `review-architecture` на
|
|
||||||
интегрированный дифф с вопросом «не появился ли второй способ делать то, что
|
|
||||||
уже делается» — именно он возникает, когда две задачи независимо решали
|
|
||||||
похожее;
|
|
||||||
- **заверши триажем.** Он единственный, кто агрегирует, и без него у находок нет
|
|
||||||
ни оракула, ни пометки `инлайн`/`развилка` — а следующий абзац на неё
|
|
||||||
опирается. Прогон из двух проходов без триажа — это сырые находки, выданные за
|
|
||||||
разобранные;
|
|
||||||
- **план триажу собери сам, здесь, — разметчика на этой сверке нет.** Триаж
|
|
||||||
требует план обязательным входом: он сверяет размеченное с пришедшим, и без
|
|
||||||
плана эта сверка не выполняется вовсе. Разметка задачи сюда не годится — она
|
|
||||||
описывала одну задачу, а сверка идёт по интегрированной ветке. План здесь
|
|
||||||
короткий и составляется по факту запуска:
|
|
||||||
|
|
||||||
```
|
|
||||||
метка: не применяется — сверка стыка, а не ревью изменения
|
|
||||||
тема дом глубина закрывает
|
|
||||||
requirements openspec/specs/<capability-1>/ разбор specs (стык)
|
|
||||||
requirements openspec/specs/<capability-2>/ разбор specs (стык)
|
|
||||||
architecture docs/architecture.md разбор architecture
|
|
||||||
+ источник docs/passport.md
|
|
||||||
```
|
|
||||||
|
|
||||||
Темы, которых в этом списке нет (`autotests`, `conventions`, `security`,
|
|
||||||
`operations`, свои темы проекта), назови строкой «не проверяется на сверке
|
|
||||||
стыка: закрыто прогонами отдельных задач». Это не формальность — без такой
|
|
||||||
строки отчёт сверки читается как полное ревью ветки.
|
|
||||||
|
|
||||||
Граф этой сверки — веер в один сток, и он такой же, как у обычного прогона:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
merged["основная ветка после всех интеграций<br/>(финальный гейт зелёный)"]
|
|
||||||
s1["review-specs: capability 1<br/>режим «стык после слияния»"]
|
|
||||||
s2["review-specs: capability 2<br/>режим «стык после слияния»"]
|
|
||||||
arch["review-architecture на интегрированном диффе<br/>(если задачи пересекались по файлам)"]
|
|
||||||
tri["review-triage — сток"]
|
|
||||||
|
|
||||||
merged --> s1 --> tri
|
|
||||||
merged --> s2 --> tri
|
|
||||||
merged --> arch --> tri
|
|
||||||
```
|
|
||||||
|
|
||||||
Замечания отрабатывай как одиночный пайплайн: `инлайн` чини сам, `развилка` —
|
|
||||||
вопросом в запись; после правок — снова гейт.
|
|
||||||
|
|
||||||
### 9. Прибраться и доложить
|
|
||||||
|
|
||||||
- Убери worktree и ветки **только успешно влитых** задач, в конце
|
|
||||||
`git worktree prune`. Worktree и ветки **провалившихся** не трогай — они нужны
|
|
||||||
для ручного дожатия.
|
|
||||||
- **Записей учёта батч не трогает** — их правит пайплайн внутри сабагента на
|
|
||||||
шаге 12. Батч сообщает исход по каждой задаче; если какой-то сабагент дошёл до
|
|
||||||
коммита, но закрытия не сделал (плагина нет, вызов не разрешился), скажи это
|
|
||||||
строкой — иначе задача останется открытой молча.
|
|
||||||
- Доложи кратко:
|
|
||||||
- **исход по каждой задаче** одним из трёх слов, с хешем коммита;
|
|
||||||
- **режим прогона** — по одной или волнами, и если волнами, то по чьей просьбе;
|
|
||||||
порядок задач или состав волн, порядок интеграции, с пометкой, какие задачи
|
|
||||||
шли по одной как замеряющие;
|
|
||||||
- вопросы, записанные сабагентами, пачкой;
|
|
||||||
- что дозапускалось на шаге 5 и почему; шло ли где-то ревью инлайн;
|
|
||||||
- итог финальной сверки и ссылки на архивные change;
|
|
||||||
- **`Урожай`** — отложенные находки всех задач одним списком, с провенансом.
|
|
||||||
Задачи из него заводит тот, кто ведёт задачи проекта, а не батч;
|
|
||||||
- **отдельно — провалившиеся** задачи с настоящей причиной и путём к
|
|
||||||
оставленному worktree;
|
|
||||||
- **границы покрытия сводной строкой**, включая задачи, чьи замеры снимались в
|
|
||||||
общей волне, и ветки, где ревью шло инлайн.
|
|
||||||
|
|
||||||
## Тонкости
|
|
||||||
|
|
||||||
- **Изоляция параллельных тестов — цена параллельного режима, и проверяется она
|
|
||||||
до первой волны.** Прежде чем гнать несколько прогонов разом, убедись, что
|
|
||||||
тесты не делят фиксированный порт или файл БД (обычно берут временный каталог и
|
|
||||||
эфемерный порт — тогда ок). Делят — такие задачи гони по одной, даже если
|
|
||||||
просили параллельно, и скажи об этом строкой: просьба про параллельность, а не
|
|
||||||
про сломанные тесты. В умолчательном режиме вопрос не встаёт вовсе — это одна
|
|
||||||
из причин, по которым умолчание такое.
|
|
||||||
- Поведенческая верификация внутри сабагента поднимает изменение вживую: в
|
|
||||||
параллельном режиме следи, чтобы соседние worktree не дрались за порты и
|
|
||||||
рабочие каталоги. Если проект умеет поднимать только один экземпляр — такие
|
|
||||||
задачи в одну волну не ставь.
|
|
||||||
- Ревью выполненного — **до** интеграции; это забота
|
|
||||||
`av-dev-pipeline:task-pipeline` внутри каждого сабагента, дублировать не надо.
|
|
||||||
- `openspec validate --strict` тоже внутри пайплайна задачи — не пропускай его
|
|
||||||
своими правками на интеграции.
|
|
||||||
- Крупная переработка, предложенная ревью внутри задачи, — развилка: не вливай
|
|
||||||
молча, вынеси в доклад.
|
|
||||||
- Держи вызывающего в цикле короткими репликами на переходах фаз (план → прогон
|
|
||||||
задач → интеграция → финальная сверка), но не проси подтверждать механику.
|
|
||||||
@@ -1,478 +0,0 @@
|
|||||||
---
|
|
||||||
name: task-pipeline
|
|
||||||
description: "Автономно проводит одну задачу через полный цикл Spec Driven Development — от постановки до коммита (opsx explore→propose→разметка задачи→ревью дизайна→apply→ревью кода→archive→коммит), с обязательными чекпоинтами ревью и докладом об исходе. Разметка идёт один раз, сразу после propose: она называет размер, сложность и метка, и её план определяет состав обеих стадий ревью. Использовать, когда просят взять/сделать задачу или довести идею до реализации."
|
|
||||||
---
|
|
||||||
|
|
||||||
# Пайплайн задачи
|
|
||||||
|
|
||||||
Оркестратор **одной** задачи по Spec Driven Development: проводит её от
|
|
||||||
постановки до коммита максимально автономно. Механику не согласовываем — делаем.
|
|
||||||
|
|
||||||
Это тонкая обёртка над каноническими скиллами `opsx:explore` / `opsx:propose` /
|
|
||||||
`opsx:apply` / `opsx:archive` — вызывай их через Skill, не переизобретай их шаги.
|
|
||||||
Ревью — скилл `av-dev-pipeline:review-pipeline`; он же держит правило выбора
|
|
||||||
метки, а называет её агент `review-scope` на шаге 4 — один раз на задачу, для
|
|
||||||
обеих стадий ревью.
|
|
||||||
|
|
||||||
## Предпосылки
|
|
||||||
|
|
||||||
- **OpenSpec и скиллы `opsx:*` — жёсткая предпосылка, а не опция.** На них стоят
|
|
||||||
шаги 2, 3, 7 и 9, проход `review-specs` и ревью дизайна (они завязаны на
|
|
||||||
`openspec/changes/<id>/specs/*/spec.md` и на `openspec validate --strict`).
|
|
||||||
**Проект без OpenSpec этим пайплайном не ведётся** — подключай OpenSpec, а не
|
|
||||||
вырождай цикл: ветка деградации здесь не пишется, потому что непроверенная
|
|
||||||
ветка деградации хуже честного отказа.
|
|
||||||
- **Скиллы зовутся с пространством имён** — `av-dev-pipeline:review-pipeline`,
|
|
||||||
`av-dev-pm:docs`, `av-dev-pm:tasks`. Короткое имя может разрешиться в
|
|
||||||
устаревшую проектную копию, и это произойдёт молча.
|
|
||||||
- **Проектные копии этих скиллов и агентов удаляются при установке плагина**
|
|
||||||
(`.claude/skills/` — и голые имена `task-pipeline`, `review-pipeline`,
|
|
||||||
`task-batch`, и с префиксом проекта: `<проект>-task-pipeline`,
|
|
||||||
`<проект>-review-pipeline`;
|
|
||||||
`.claude/agents/<проект>-review-*.md`). Две копии одного скилла расходятся, и
|
|
||||||
побеждает та, что короче названа.
|
|
||||||
|
|
||||||
Перед стартом прочитай `CLAUDE.md` проекта и то, на что он ссылается, если ещё
|
|
||||||
не в контексте. Проектные факты, нужные ревью — инварианты, семантика гейта,
|
|
||||||
объёмы, модель угроз, прецеденты, — живут в **документах канона** `av-dev-pm`;
|
|
||||||
карта «что где» — `references/project-facts.md` конвейера ревью.
|
|
||||||
|
|
||||||
**Документов канона нет — проект к нему не приведён.** Скажи это строкой и
|
|
||||||
предложи скилл `av-dev-pm:canon`: одна операция на проект против поразрядной
|
|
||||||
деградации на каждой задаче. Работу при этом не останавливай.
|
|
||||||
|
|
||||||
## Границы: чем пайплайн не владеет
|
|
||||||
|
|
||||||
- **Беклогом, спринтом, целями и приоритетами.** Задача приходит извне. Пайплайн
|
|
||||||
её не выбирает, не приоритизирует, не заводит и не переоценивает; если в
|
|
||||||
проекте есть свой процесс управления задачами — он и решает, что брать.
|
|
||||||
- **Форматом задач.** Пайплайн **не правит индексы руками и не выдумывает путь
|
|
||||||
к скрипту учёта**: он зовёт Skill `av-dev-pm:tasks`, который этим владеет
|
|
||||||
(шаг 12). Закрытие как таковое — его работа, и это осознанное решение с
|
|
||||||
названной ценой: **приёмщик и исполнитель совпали**. Закрытие поэтому **не
|
|
||||||
окончательно** — человек на сессии возвращает задачу `reopen` с причиной, а
|
|
||||||
доклад по критериям приёмки становится единственным, по чему приёмка вообще
|
|
||||||
возможна. Плагина `av-dev-pm` в проекте нет — вызов не разрешится, и тогда
|
|
||||||
учёт остаётся владельцу, о чём говорится в докладе.
|
|
||||||
- **Заведением задач из урожая ревью.** Отложенные находки отдаются **списком**
|
|
||||||
(см. шаг 8); превращать их в задачи — работа того, кто ведёт задачи проекта.
|
|
||||||
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос
|
|
||||||
пайплайна ни на одном шаге.
|
|
||||||
|
|
||||||
Пайплайн владеет **своим** определением готовности (ниже) и **сообщает
|
|
||||||
наблюдаемый исход**. Что с исходом делать дальше — не его дело.
|
|
||||||
|
|
||||||
## Наблюдаемые исходы
|
|
||||||
|
|
||||||
Ровно три, и каждый обязан быть назван в докладе прямо:
|
|
||||||
|
|
||||||
- **сделана** — определение готовности выполнено целиком;
|
|
||||||
- **не доведена** — с причиной и с записанным вопросом; что именно сделано и до
|
|
||||||
какой границы, названо явно;
|
|
||||||
- **оказалась крупнее задачи** — распознаётся **до заведения change**, иначе его
|
|
||||||
придётся выбрасывать. Дальше — декомпозиция, и это не работа пайплайна.
|
|
||||||
|
|
||||||
## Определение готовности
|
|
||||||
|
|
||||||
Задача сделана, когда верно всё:
|
|
||||||
|
|
||||||
1. гейт проекта зелёный;
|
|
||||||
2. ревью проведено, **план прогона сверен с исходом по каждой теме**, темы без
|
|
||||||
отчёта и без дома названы в границах покрытия;
|
|
||||||
3. change заархивирован, дельты влиты в актуальные спеки;
|
|
||||||
4. коммит сделан в текущую ветку;
|
|
||||||
5. **критерии приёмки, если проект их дал, выписаны поимённо, и по каждому назван
|
|
||||||
оракул и наблюдаемый исход** — «прогнал вот это, увидел вот то». Это **доклад,
|
|
||||||
а не сертификация: приёмка — не работа пайплайна.** Исполнитель, ставящий себе
|
|
||||||
галочку «принято», проверяет свою работу своим же взглядом — по границе это
|
|
||||||
может делать только приёмщик, разведённый с исполнителем. Критерии приходят снаружи;
|
|
||||||
пайплайн их не сочиняет и не занижает. Расхождение «по каждому критерию исход
|
|
||||||
есть, а суть задачи не достигнута» — дефект критериев, и о нём сообщается, а
|
|
||||||
не молча дорабатывается.
|
|
||||||
|
|
||||||
Пункты 1–4 — своё. Пункт 5 — внешнее: пайплайн доводит его до наблюдаемого
|
|
||||||
исхода и передаёт дальше.
|
|
||||||
|
|
||||||
## Принцип автономности
|
|
||||||
|
|
||||||
**Умолчание — делать, а не спрашивать.** Задача доводится до коммита без участия
|
|
||||||
человека; предполагается, что так пройдёт большинство задач.
|
|
||||||
|
|
||||||
Наткнулся на вопрос, который решать не тебе, — **не останавливайся и не
|
|
||||||
спрашивай**. Запиши его и продолжай:
|
|
||||||
|
|
||||||
1. **Запиши вопрос там, где проект держит вопросы** (секция беклога, файл
|
|
||||||
задачи, трекер — это знает проект). Если проект не сказал, куда, — отдельной
|
|
||||||
секцией `Вопросы` в своём докладе, и это тоже исход. Тело отвечает на три
|
|
||||||
вещи: **что именно решить**, **какие есть варианты и цена каждого**, **что
|
|
||||||
стоит, пока решения нет**. Плюс твоя рекомендация — человек чаще соглашается,
|
|
||||||
чем выбирает заново, и готовое суждение экономит ему весь контекст.
|
|
||||||
2. **Переформулируй задачу на остаток** — то, что делается без этого решения.
|
|
||||||
Назови границу: докуда доводим сейчас.
|
|
||||||
3. Доведи остаток до конца и закоммить. Задача не «висит на вопросе», она сделана
|
|
||||||
в объявленных границах.
|
|
||||||
|
|
||||||
**Что остатком не является — правило живёт не здесь.** Канонический текст с обеими
|
|
||||||
оговорками — в плагине `av-dev-pm`, скилл `av-dev-pm:session`, раздел
|
|
||||||
`## Вопрос, блокер, необратимое`, подраздел «Отличать вопрос от застревания».
|
|
||||||
Правило принадлежит управлению задачами, потому что решает **сделана задача или
|
|
||||||
вышла**, — это исход планирования, а не исполнения. **Ссылайся, не
|
|
||||||
пересказывай:** копия, заведённая здесь, уже однажды разошлась с оригиналом и
|
|
||||||
потеряла из перечня самое необратимое — запись **наружу**.
|
|
||||||
|
|
||||||
Коротко, чтобы знать, когда идти читать: остаток проверяется двумя порогами —
|
|
||||||
**материализация нерешённого** (запись состояния, зависящего от неотвеченного
|
|
||||||
вопроса) и **пол по пользе** (из остатка пропала польза, названная в постановке).
|
|
||||||
Оба порога — стоп: первый поднимает решение до начала записи, второй даёт исход
|
|
||||||
«не доведена».
|
|
||||||
|
|
||||||
Плагин `av-dev-pm` не подключён — правило не отменяется, а становится
|
|
||||||
осторожнее: прежде чем записать зависящее от нерешённого куда бы то ни было —
|
|
||||||
в хранилище, в журнал, в витрину или наружу, — спрашивай человека.
|
|
||||||
|
|
||||||
Нет полезного остатка — задача заканчивается исходом «не доведена», вопрос
|
|
||||||
записан, ничего не коммитится наполовину.
|
|
||||||
|
|
||||||
### Когда всё-таки спрашивать
|
|
||||||
|
|
||||||
Узко и по другому основанию — не «сложное решение», а **необратимое действие**:
|
|
||||||
|
|
||||||
- деплой, выкладка наружу, смена публичного адреса или токенов;
|
|
||||||
- удаление или перезапись рабочих данных, включая подрезку архивов;
|
|
||||||
- всё, что уходит за пределы машины.
|
|
||||||
|
|
||||||
Здесь ошибка не откатывается коммитом, поэтому спрашиваем даже когда решение
|
|
||||||
кажется очевидным. Развилка в дизайне — вопрос в запись; необратимое действие —
|
|
||||||
вопрос человеку сейчас.
|
|
||||||
|
|
||||||
Стиль правок — заточка под проект и конвенции, right-size, без золочения.
|
|
||||||
|
|
||||||
## Шаги
|
|
||||||
|
|
||||||
Двенадцать шагов с одной развилкой и одним досрочным исходом:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
s1["1. Прочитать задачу<br/>критерии приёмки выписать сразу"]
|
|
||||||
triv{"тривиальная?"}
|
|
||||||
big["исход «оказалась крупнее задачи»<br/>объявляется ДО заведения change"]
|
|
||||||
s2["2. opsx:explore — груминг идеи"]
|
|
||||||
s3["3. opsx:propose — change, дельта-спеки, tasks.md"]
|
|
||||||
s4["4. разметка задачи — review-scope:<br/>размер, сложность, метка, план тем"]
|
|
||||||
s5["5. ревью дизайна, состав по метке"]
|
|
||||||
s6["6. отработать замечания + validate --strict"]
|
|
||||||
s7["7. opsx:apply — код, гейт, поведенческая верификация"]
|
|
||||||
s8["8. ревью кода, состав по той же метки"]
|
|
||||||
s9["9. opsx:archive"]
|
|
||||||
s10["10. синк документации — av-dev-pm:docs"]
|
|
||||||
s11["11. коммит работы — av-dev-git:commit"]
|
|
||||||
s12["12. закрыть задачу — av-dev-pm:tasks,<br/>вторым коммитом учёта"]
|
|
||||||
|
|
||||||
s1 --> triv
|
|
||||||
s1 -.-> big
|
|
||||||
triv -->|"нет: идея или мутная постановка"| s2
|
|
||||||
s2 --> s3
|
|
||||||
triv -->|"да: шаг 2 пропускается"| s3
|
|
||||||
s3 --> s4 --> s5 --> s6 --> s7 --> s8 --> s9 --> s10 --> s11 --> s12
|
|
||||||
s4 -.->|"план задачи: та же метка"| s8
|
|
||||||
```
|
|
||||||
|
|
||||||
**Разметка стоит одна и обслуживает обе стадии ревью** — шаги 5 и 8. Это и есть
|
|
||||||
пунктирное ребро на схеме: план, посчитанный на шаге 4, доезжает до ревью кода
|
|
||||||
без пересчёта. Раньше разметка была первым проходом внутри шага ревью кода, а
|
|
||||||
состав ревью дизайна называл сам пайплайн — то есть одна и та же величина
|
|
||||||
считалась дважды, и один из двух раз тем, кто только что написал предложение.
|
|
||||||
|
|
||||||
Два чекпоинта ревью — шаги 5 и 8 — единственные места, где зовётся конвейер;
|
|
||||||
порядок «сперва коммит работы, потом коммит учёта» на схеме тоже ребро, и оно
|
|
||||||
обязательное (шаг 12).
|
|
||||||
|
|
||||||
Схема — **сводка**: содержание каждого шага в его разделе ниже, и при
|
|
||||||
расхождении прав текст.
|
|
||||||
|
|
||||||
### 1. Прочитать задачу
|
|
||||||
|
|
||||||
Задача задана извне (slug, файл, ссылка, описание) — прочитай её и связанные
|
|
||||||
спеки и черновики. Не задана — попроси у вызывающего; сам в беклог не лезь и
|
|
||||||
приоритеты не интерпретируй.
|
|
||||||
|
|
||||||
Если проект даёт задаче **критерии приёмки**, выпиши их сразу: на шаге 3 они
|
|
||||||
уезжают в `tasks.md` change. Файл задачи может быть удалён до коммита, а
|
|
||||||
критерии обязаны его пережить.
|
|
||||||
|
|
||||||
Оцени тривиальность — **теперь она влияет ровно на один шаг, второй**:
|
|
||||||
|
|
||||||
- **тривиальная** — локальная правка без изменения поведения, спек и схемы,
|
|
||||||
решение очевидно. Explore пропускается;
|
|
||||||
- **нетривиальная** — новое или изменённое поведение, дизайн-развилки, задеты
|
|
||||||
инварианты, схема или несколько capability. Полный цикл.
|
|
||||||
|
|
||||||
**На состав ревью тривиальность больше не влияет** — это работа шага 4. Раньше
|
|
||||||
она решала и то, звать ли ревью предложения вовсе; теперь глубину обеих стадий
|
|
||||||
называет метку, и тривиальная задача просто получает `small`. Разница
|
|
||||||
существенная: «пропустить ревью дизайна» и «пройти его одним самым дешёвым
|
|
||||||
проходом» — не одно и то же, а сверка дельта-спек стоит меньше, чем разбор того,
|
|
||||||
что она поймала бы.
|
|
||||||
|
|
||||||
Здесь же — проверка на «крупнее задачи»: если видно, что одним заходом это не
|
|
||||||
мерджится, объявляй исход **до** заведения change.
|
|
||||||
|
|
||||||
### 2. (Опц.) Груммить идею — `opsx:explore`
|
|
||||||
|
|
||||||
Только для идей и мутных постановок. Вызови Skill `opsx:explore`. Развилку
|
|
||||||
грумминга не выноси на человека — запиши вопросом и груми остаток. Выход: ясная
|
|
||||||
постановка, готовая к propose. **В explore не пишем код.**
|
|
||||||
|
|
||||||
### 3. Завести 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` отдельным блоком.
|
|
||||||
|
|
||||||
### 4. Разметка задачи — агент `review-scope`
|
|
||||||
|
|
||||||
**Один запуск на всю задачу, и он обслуживает оба чекпоинта ревью.** Запусти
|
|
||||||
агента `review-scope`, дав ему корень проекта, идентификатор change, базу диффа и
|
|
||||||
запись задачи. Кода на этот момент нет, и это условие его работы, а не помеха.
|
|
||||||
|
|
||||||
Он возвращает **план задачи**:
|
|
||||||
|
|
||||||
- **размер** (малое / среднее / крупное) и **сложность** (знакомое /
|
|
||||||
незнакомое), каждое с обоснованием по факту;
|
|
||||||
- **метка** как максимум по двум осям: `small`, `medium` или `large`;
|
|
||||||
- **состав ревью дизайна** — что звать на шаге 5;
|
|
||||||
- **таблицу тем** «тема → дом → глубина → кто закрывает» — для шага 8;
|
|
||||||
- разнесение документов проекта по трём категориям и строку про директивы.
|
|
||||||
|
|
||||||
**Метка выбираешь не ты.** Раньше состав ревью дизайна называл этот пайплайн
|
|
||||||
(«крупное или незнакомое?»), то есть тот же оркестратор, который только что
|
|
||||||
довёл предложение до `propose`. Разведённости с автором в этой точке не было
|
|
||||||
вовсе; теперь есть.
|
|
||||||
|
|
||||||
**План держи в контексте до конца задачи.** На диск он не пишется: файл-план стал
|
|
||||||
бы четвёртым артефактом рядом с `proposal.md`, `tasks.md` и `design.md`, пережил
|
|
||||||
бы задачу и разошёлся бы с ней молча. Прервался пайплайн — повтори шаг 4, это
|
|
||||||
самый дешёвый его проход.
|
|
||||||
|
|
||||||
**Разметка повторяется ровно в одном случае** — если на шаге 6 правки изменили
|
|
||||||
сами **дельта-спеки**: план выведен из них, и план по отменённым требованиям
|
|
||||||
назовёт не те темы. Во всех прочих случаях, включая переделку формы кода на шаге
|
|
||||||
8, метка остаётся прежней.
|
|
||||||
|
|
||||||
### 5. Ревью предложения — ДО кода, состав по метке
|
|
||||||
|
|
||||||
Первый чекпоинт. Вызови Skill **`av-dev-pipeline:review-pipeline`**, дав ссылку на
|
|
||||||
change `<id>`, **план разметки с шага 4** и указание, что это ревью дизайна.
|
|
||||||
|
|
||||||
Состав приходит планом, а не решается здесь:
|
|
||||||
|
|
||||||
| Метка | Проходы на предложении |
|
|
||||||
|---|---|
|
|
||||||
| `small` | `specs` |
|
|
||||||
| `medium` | `specs`, `rubric` |
|
|
||||||
| `large` | `specs`, `rubric`, `architecture` + вопрос автору о трёх формах решения |
|
|
||||||
|
|
||||||
`review-specs` в режиме «дизайн ДО кода» идёт **на каждой задаче**: это самый
|
|
||||||
дешёвый чекпоинт конвейера, и он ловит то, что на готовом коде уже не чинят.
|
|
||||||
Остальные включаются меткой, потому что чекпоинт стоит на каждой задаче и
|
|
||||||
каждый лишний проход здесь умножается на число задач.
|
|
||||||
|
|
||||||
Смысл стадии: архитектурная находка на готовом коде стоит переписывания и
|
|
||||||
потому игнорируется — та же находка здесь стоит абзаца обсуждения. Если
|
|
||||||
`review-rubric` запускался, перенеси его рубрику в `tasks.md` как приёмочные
|
|
||||||
критерии; там же уже лежат критерии от постановки, если они были.
|
|
||||||
|
|
||||||
### 6. Отработать замечания ревью предложения
|
|
||||||
|
|
||||||
- Мелочь и явные улучшения — правь сам в спеках и дизайне.
|
|
||||||
- Развилки (компромисс, scope, инвариант) — вопросом в запись, спеки урезаются на
|
|
||||||
остаток.
|
|
||||||
- После правок перепрогони `openspec validate --strict <id>`.
|
|
||||||
|
|
||||||
### 7. Написать код — `opsx:apply`
|
|
||||||
|
|
||||||
Вызови Skill `opsx:apply` для реализации `tasks.md`. Код — по конвенциям проекта
|
|
||||||
(каталог `docs/conventions/`). Меняешь схему — обнови её описание в
|
|
||||||
документации тем же change, если проект этого требует: гейт обычно это проверяет.
|
|
||||||
|
|
||||||
Прогони гейт и добейся зелёного — он же гейт следующего шага.
|
|
||||||
|
|
||||||
**Поведенческая верификация.** Если задача меняет реальное поведение (новый
|
|
||||||
эндпоинт, разбор входа, схема, форма ответа) — зелёных юнит-тестов мало. Подними
|
|
||||||
изменение вживую командой из раздела команд `CLAUDE.md` и прогони сценарий.
|
|
||||||
Пропусти только для чисто внутренних правок без наблюдаемого рантайма.
|
|
||||||
|
|
||||||
**Сервис не оставляем лежать.** Если запуск упал — почини или откати до конца
|
|
||||||
шага.
|
|
||||||
|
|
||||||
### 8. Ревью кода — Skill `av-dev-pipeline:review-pipeline`
|
|
||||||
|
|
||||||
Второй чекпоинт. Вызови Skill **`av-dev-pipeline:review-pipeline`**, дав ссылку
|
|
||||||
на change `<id>`, базу диффа, **план разметки с шага 4** и режим запуска.
|
|
||||||
|
|
||||||
**Метка ты не выбираешь, и это правило, а не упрощение.** Её назвал
|
|
||||||
`review-scope` ещё на шаге 4 — по размеру и сложности, с обоснованием по каждой
|
|
||||||
оси. Причина в разведённости: ты только что написал этот код, и решать, насколько
|
|
||||||
глубоко его проверять, тебе нельзя — под давлением «я почти закончил» решение
|
|
||||||
известно заранее. Правило выбора живёт в скилле конвейера —
|
|
||||||
`av-dev-pipeline:review-pipeline`, `references/review-levels.md`; проектные
|
|
||||||
триггеры — в `docs/review.*`, подраздел «Триггеры метки».
|
|
||||||
|
|
||||||
**Метка не пересматривается по факту диффа.** Дифф может выйти крупнее, чем
|
|
||||||
ожидалось при разметке, — это не повод её поднимать: пересмотр означал бы второй
|
|
||||||
запуск разметчика, ровно то, ради устранения чего он и переехал на шаг 4.
|
|
||||||
|
|
||||||
**Считаешь метку заниженной — скажи это в докладе строкой, а не переспорь.**
|
|
||||||
Разметчик вправе и поднять, и понизить; твоё несогласие это факт для человека, а
|
|
||||||
не команда конвейеру. Место, где такое несогласие превращается в изменение
|
|
||||||
правил, — журнал дефектов `docs/review.md`, и только постфактум.
|
|
||||||
|
|
||||||
**Плана нет — ревью кода не запускается.** Триаж требует план обязательным
|
|
||||||
входом: без него он не может сверить, все ли размеченные темы вернули отчёт, а
|
|
||||||
эта сверка — единственная защита от молчащего пропуска. Потерял план (прервалась
|
|
||||||
сессия, ушёл контекст) — повтори шаг 4, а не гони прогон без него.
|
|
||||||
|
|
||||||
**Режим по умолчанию — `по графу`, и обосновывать его не надо.** Конвейер сам
|
|
||||||
знает свои рёбра: гейт открывает опиниативные проходы, проходы с пометкой «держит
|
|
||||||
машину» идут цепочкой (иначе замеры портят друг друга и находка выглядит
|
|
||||||
доказанной), триаж — сток. Твоего участия это не требует.
|
|
||||||
|
|
||||||
Просить **`линейно`** нужно только по причине, и она называется строкой: так
|
|
||||||
сказал оператор; машина занята чем-то ещё (в том числе соседней задачей батча);
|
|
||||||
идёт разбор самого конвейера.
|
|
||||||
|
|
||||||
Скилл сам гоняет гейт, нужные проходы и обязательный триаж. Возвращает отчёт с
|
|
||||||
потолком 7 пунктов, разметкой `Действие: инлайн | развилка` и секцией границ
|
|
||||||
покрытия.
|
|
||||||
|
|
||||||
**Сверь план прогона с исходом, прежде чем коммитить.** Отчёт начинается планом
|
|
||||||
разметчика — таблицей «тема → дом → глубина → кто закрывает», — и против каждой
|
|
||||||
темы обязан стоять исход. Тема без отчёта и тема без дома — разные вещи, и обе
|
|
||||||
должны быть названы. Реестр короткий (шесть тем ядра плюс свои) — сверка стоит
|
|
||||||
одного взгляда. Почему это правило существует, объясняет раздел «Метки» скилла
|
|
||||||
конвейера; здесь — само требование.
|
|
||||||
|
|
||||||
Отработай так же, как шаг 6: помеченное `инлайн` чини сам и не логируй,
|
|
||||||
`развилка` — вопросом в запись (он уже сформулирован триажем, его остаётся
|
|
||||||
перенести). После правок — снова гейт.
|
|
||||||
|
|
||||||
**Урожай — списком, не задачами.** Отложенные находки (реальный `major` не для
|
|
||||||
этого мерджа, развилка, решённая «потом», пачка `nit`) собери в секцию доклада
|
|
||||||
`Урожай`: формулировка, оракул, провенанс. Задачи из него **заводит не пайплайн**
|
|
||||||
— у того, кто ведёт задачи проекта, своя нарезка, свой формат и свои правила
|
|
||||||
дублей. Твоя обязанность — не потерять и передать.
|
|
||||||
|
|
||||||
**Границы покрытия из отчёта не выбрасывай** — они уезжают в финальный доклад
|
|
||||||
сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно»,
|
|
||||||
превращается в ложное ощущение проверенности.
|
|
||||||
|
|
||||||
**Отчёт триажа сохрани вместе с change (`openspec/changes/<id>/review/`; шаг 9
|
|
||||||
унесёт его в `openspec/changes/archive/<id>/review/` вместе с change) — это
|
|
||||||
обязательно, а не «если удобно».** По нему потом видно, что было найдено и что из
|
|
||||||
этого осталось в урожае. И это единственный **независимый** артефакт о составе
|
|
||||||
прогона: под оркестратором `task-batch` именно по нему сверяют полноту ревью
|
|
||||||
ветки, а не по твоей прозе — она написана тем же, кто мог проход и пропустить.
|
|
||||||
|
|
||||||
### 9. Архивировать — `opsx:archive`
|
|
||||||
|
|
||||||
Вызови Skill `opsx:archive`: change уезжает в архив, дельты вливаются в
|
|
||||||
актуальные спеки.
|
|
||||||
|
|
||||||
### 10. Синк документации
|
|
||||||
|
|
||||||
Ревью выполненного — до этого шага. Затем **вызови Skill `av-dev-pm:docs`**: он
|
|
||||||
владеет содержимым документов канона и ведёт чек-лист синка. Плагина нет — шаг
|
|
||||||
всё равно делается, см. ниже.
|
|
||||||
|
|
||||||
**Правило одно и оно жёсткое: принуждённое отрицание.** Доклад обязан назвать
|
|
||||||
**каждый** документ канона — либо чем он обновлён, либо «не требуется, потому
|
|
||||||
что…». Нетронутые группируются одной строкой с общей причиной. Список триггеров
|
|
||||||
прозой уже проверен на живом проекте и дал 6 записей ADR на 43 изменения;
|
|
||||||
работает только обязательное отрицание.
|
|
||||||
|
|
||||||
**Список документов и их триггеров здесь не дублируется** — он в чек-листе
|
|
||||||
скилла `av-dev-pm:docs`, и копия уже однажды разошлась с оригиналом, потеряв два
|
|
||||||
триггера.
|
|
||||||
|
|
||||||
**Плагина `av-dev-pm` в проекте нет** — путь в его дерево не разрешится ниоткуда,
|
|
||||||
поэтому за списком иди в **свой** reference:
|
|
||||||
[references/project-facts.md](../review-pipeline/references/project-facts.md)
|
|
||||||
конвейера ревью перечисляет все документы канона с их предметом. Пройди по этому
|
|
||||||
перечню — каждый документ получает строку, отрицание остаётся обязательным.
|
|
||||||
Триггеры при этом ты знаешь хуже, и это называется в докладе строкой: «синк
|
|
||||||
сделан по перечню документов, без списка триггеров — плагина `av-dev-pm` нет».
|
|
||||||
Канона в проекте тоже нет — назови это исходом и предложи `av-dev-pm:canon`.
|
|
||||||
|
|
||||||
### 11. Коммит
|
|
||||||
|
|
||||||
Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не
|
|
||||||
создавай и не переключай, ничего не пушь. При ручном запуске HEAD обычно на
|
|
||||||
основной ветке — коммит идёт прямо в неё; под оркестратором `task-batch` HEAD на
|
|
||||||
ветке задачи в изолированном worktree, и делать дополнительно ничего не нужно.
|
|
||||||
|
|
||||||
Сообщение — по-русски, скиллом `av-dev-git:commit`, если он подключён (первая строка «что
|
|
||||||
сделано», тело списком 1–3 пункта, без трейлеров). Одна задача — один осмысленный
|
|
||||||
коммит.
|
|
||||||
|
|
||||||
### 12. Закрыть задачу — **после коммита, не раньше**
|
|
||||||
|
|
||||||
**Вызови Skill `av-dev-pm:tasks`** и попроси закрыть задачу как реализованную —
|
|
||||||
он владеет форматом и двигает строку из набора спринта сам. Путь к его скрипту не
|
|
||||||
выясняй и индексы руками не правь: мост между плагинами — вызов скилла, а не
|
|
||||||
путь.
|
|
||||||
|
|
||||||
**Порядок обязателен.** Закрытие удаляет файл задачи; сделанное до коммита оно
|
|
||||||
оставило бы задачу закрытой без единого следа работы, если шаг 11 упадёт.
|
|
||||||
|
|
||||||
**Закрытие тоже коммитится — вторым коммитом, тут же.** Удаление файла задачи и
|
|
||||||
правка индексов (их имена знает `av-dev-pm`, не ты) — это правки в рабочем
|
|
||||||
дереве, и оставить их незакоммиченными нельзя по трём причинам: `task-batch` следом делает `rebase`
|
|
||||||
и `worktree remove`, а те откажут на грязном дереве; закрытие, не доехавшее до
|
|
||||||
основной ветки, оставит задачу открытой молча; и опора «набор спринта под git
|
|
||||||
показывает, что и когда закрыто» без коммита — пустые слова. Сообщение короткое,
|
|
||||||
про учёт, а не про работу: `закрыта задача <slug>`. Это второй коммит осознанно:
|
|
||||||
правило «одна задача — один осмысленный коммит» про работу, а учёт — не работа.
|
|
||||||
|
|
||||||
Плагина в проекте нет — вызов не разрешится. Тогда **ничего не выдумывай**:
|
|
||||||
скажи в докладе, что учёт задач остаётся за владельцем, и назови исход.
|
|
||||||
|
|
||||||
**Приёмщик и исполнитель здесь совпадают**, и закрытие не окончательно: человек
|
|
||||||
на сессии может вернуть задачу (`reopen` с причиной). Поэтому доклад по критериям
|
|
||||||
приёмки — не формальность, а единственное, по чему приёмка вообще возможна.
|
|
||||||
|
|
||||||
Готово — доложи кратко.
|
|
||||||
|
|
||||||
- **исход** задачи одним из трёх слов и, если не «сделана», чем ограничен
|
|
||||||
результат;
|
|
||||||
- что сделано, какие вопросы записаны и куда;
|
|
||||||
- ссылка на архивный change и хеш коммита;
|
|
||||||
- по каждому критерию приёмки, если они были: **оракул и наблюдаемый исход** —
|
|
||||||
это доклад приёмщику, а не отметка «принято»;
|
|
||||||
- **`Урожай`** — отложенные находки списком (формулировка, оракул, провенанс).
|
|
||||||
Задачи из него заводит тот, кто ведёт задачи проекта;
|
|
||||||
- **одна строка границ покрытия**: какая метка и режим гонялись, какие проходы
|
|
||||||
не запускались и что проверить было невозможно. Доклад без неё сообщает
|
|
||||||
«проверено», не сообщая, что именно.
|
|
||||||
|
|
||||||
## Тонкости
|
|
||||||
|
|
||||||
- **Не завязывайся на основную ветку и корень репозитория.** Скилл работает в
|
|
||||||
текущем worktree и на текущей ветке: не делай `git checkout`/`switch`, не
|
|
||||||
создавай веток, не пушь.
|
|
||||||
- Не пропускай `openspec validate --strict` перед архивацией.
|
|
||||||
- Тривиальная задача: пропускается только шаг 2. Обе стадии ревью остаются, но
|
|
||||||
с меткой `small` — один проход на дизайне и четыре на коде, а при своих темах
|
|
||||||
проекта пять: приёмник тем запускается, если ему есть что принимать.
|
|
||||||
- Гейт блокирует: пока он красный, опиниативные проходы не запускаются. Чинить и
|
|
||||||
перезапускать, а не «посмотреть заодно».
|
|
||||||
- Если ревью предлагает крупную переработку — это развилка: не правь молча и не
|
|
||||||
спрашивай, запиши вопросом и доведи остаток.
|
|
||||||
- Держи вызывающего в цикле короткими репликами на переходах фаз, но не проси
|
|
||||||
подтверждать механику.
|
|
||||||
- **Занизить метка ревью или пропустить тему — самый дешёвый способ
|
|
||||||
«ускориться», и он же самый дорогой по последствиям.** Защита устроена так,
|
|
||||||
что регулятора у тебя нет: метку выбирает разметчик **до того**, как ты
|
|
||||||
написал код, план сверяется по темам, непокрытое называется в отчёте строкой.
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "av-dev-pm",
|
|
||||||
"description": "Управление продуктом: канон документов проекта (паспорт, архитектура, схема БД, безопасность, конвенции, разведка, ADR, журнал ревью), задачи и цели вместо приоритетов, спринт под одну цель с заморозкой набора, старт нового проекта интервью по брифу и приведение существующего к канону. Не выполняет задачи — этим занимается пайплайн проекта.",
|
|
||||||
"author": {
|
|
||||||
"name": "Anton Vakhrushev",
|
|
||||||
"email": "anwinged@gmail.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,226 +0,0 @@
|
|||||||
---
|
|
||||||
name: doc-wording
|
|
||||||
description: "Вычитка языка проектных текстов по информационному стилю — документы канона, решения ADR, записки разведки, задачи и цели, вперемешку тоже. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в имени файла. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи задачи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form. Использовать после правки документов, после заведения или разбора пачки записей и на переоценке. Только чтение."
|
|
||||||
tools: Read, Grep, Glob
|
|
||||||
model: sonnet
|
|
||||||
color: green
|
|
||||||
---
|
|
||||||
|
|
||||||
Ты — **вычитка языка** проектных текстов: документов канона, решений ADR,
|
|
||||||
записок разведки, задач и целей. Оптика — слова и фразы, а не то, что текст
|
|
||||||
описывает: ты не судишь, верно ли решение, нужна ли задача и правильно ли она
|
|
||||||
оформлена.
|
|
||||||
|
|
||||||
Границу держи твёрдо. **Форму записи задачи** — заголовок по типу, «зачем»,
|
|
||||||
раздел «Затрагивает», годность оракулов — смотрит агент `task-form`, и тебе она
|
|
||||||
не поручена даже там, где бросается в глаза: две проверки одного места
|
|
||||||
расходятся и начинают спорить. Увидел — скажи одной строкой в конце доклада, не
|
|
||||||
находкой.
|
|
||||||
|
|
||||||
Ты **ничего не правишь**. Каждая находка — готовая формулировка на замену,
|
|
||||||
которую зовущий подставит командой (у задач — `edit <слаг> --title …`, `--why …`)
|
|
||||||
или впишет сам. Файлы ты только читаешь.
|
|
||||||
|
|
||||||
## Что тебе дают
|
|
||||||
|
|
||||||
Список файлов или каталог: документы канона (`docs/*.md`), решения в
|
|
||||||
`docs/adr/`, записки в `docs/research/`, записи каталога задач
|
|
||||||
(`docs/tasks/items/<slug>.md`) — вперемешку тоже.
|
|
||||||
|
|
||||||
Плюс, если зовущий их назвал, документы проекта — паспорт, архитектура,
|
|
||||||
конвенции: по ним проверяется, известен ли термин. **Не назвали — считай
|
|
||||||
известными только те слова, что встречаются в других поданных файлах**, и говори
|
|
||||||
об этом в границах покрытия.
|
|
||||||
|
|
||||||
## Правила
|
|
||||||
|
|
||||||
Дом — `av-dev-pm/skills/canon/references/language.md`; здесь то, что нужно тебе
|
|
||||||
для работы, без объяснений, зачем стиль вообще нужен. У каждого правила названа
|
|
||||||
причина: она же говорит, где правило **не** применяется.
|
|
||||||
|
|
||||||
1. **Глагол вместо отглагольного существительного, активный залог.** «Обработчик
|
|
||||||
не проверяет владельца», а не «проверка владельца не осуществляется»;
|
|
||||||
«скрипт переписывает индекс», а не «индекс переписывается скриптом».
|
|
||||||
Отглагольное существительное прячет того, кто действует, — а в техническом
|
|
||||||
тексте важен именно он. Страдательный залог **остаётся**, когда деятель
|
|
||||||
неизвестен или неважен: «файл удаляется» верно, если удаляет любая из трёх
|
|
||||||
команд.
|
|
||||||
|
|
||||||
2. **Факт вместо оценки.** «Время ответа доходит до 800 мс», а не «работает
|
|
||||||
медленно»; «тело 40 МиБ держит блокировку 5 секунд», а не «большие тела
|
|
||||||
тормозят». Оценка допустима, когда факт стоит рядом, в той же фразе. Без
|
|
||||||
факта это настроение, а не сведение, — и находка тем ценнее, что оценку
|
|
||||||
потом не проверить.
|
|
||||||
|
|
||||||
3. **Стоп-слова.** Канцелярит (является, осуществляется, в целях, в рамках,
|
|
||||||
данный, вышеуказанный), вводные-паразиты (в общем, как известно, стоит
|
|
||||||
отметить), усилители (очень, крайне, достаточно, абсолютно, максимально),
|
|
||||||
синонимы одного качества («понятный и простой»), неопределённое
|
|
||||||
(соответствующий, определённый, некоторый).
|
|
||||||
|
|
||||||
Проверка одна: **вычеркни слово — смысл изменился, оставляй.** И осторожно с
|
|
||||||
вводными: «если», «иначе», «при таком-то условии», «в отличие от» несут
|
|
||||||
условие и противопоставление, то есть сведения, — их не трогай.
|
|
||||||
|
|
||||||
4. **Одна мысль — одно предложение.** Предложение с двумя независимыми
|
|
||||||
утверждениями делится. **Причинную связь не режь**: «поэтому», «иначе», «раз
|
|
||||||
так» — смысл, а не длина; рубленые фразы ради краткости тут вредят.
|
|
||||||
|
|
||||||
**Поля меты не делятся.** «Зачем» в мете задачи по формату — одно
|
|
||||||
предложение: оно повторяется строкой индекса, и второму там не поместиться.
|
|
||||||
Тесно — сокращай, но не дели. То же с любым полем вида `- **Имя:** …`.
|
|
||||||
|
|
||||||
5. **Англицизм, у которого есть живое русское слово, заменяется.**
|
|
||||||
|
|
||||||
<!-- копия: язык-англицизмы из av-dev-pm/skills/canon/references/language.md -->
|
|
||||||
|
|
||||||
| Калька | Русский аналог |
|
|
||||||
| --- | --- |
|
|
||||||
| флоу | поток, процесс, сценарий |
|
|
||||||
| фикс, зафиксить | исправление, исправить, починить |
|
|
||||||
| чекать | проверять |
|
|
||||||
| апрув, заапрувить | согласование, согласовать |
|
|
||||||
| best-effort | по возможности |
|
|
||||||
| кейс | случай, сценарий |
|
|
||||||
| перформанс | производительность |
|
|
||||||
| матчинг, смэтчить | сопоставление, сопоставить |
|
|
||||||
| зарелизить | выпустить, выложить |
|
|
||||||
| отрефакторить | переписать, разделить, убрать второй путь |
|
|
||||||
|
|
||||||
Насильно не переводится то, что является **именем вещи**: термины технологий и
|
|
||||||
протоколов (`SQL`, `API`, `CSV`, `N+1`, `IDOR`), имена классов, методов, полей,
|
|
||||||
таблиц и команд, слаг, а также термин, у которого нет точного русского
|
|
||||||
эквивалента и который в команде уже прижился.
|
|
||||||
|
|
||||||
Цель — простой и точный текст, а не пуризм. Русский аналог звучит коряво или
|
|
||||||
искажает смысл — остаётся термин.
|
|
||||||
|
|
||||||
<!-- /копия: язык-англицизмы -->
|
|
||||||
|
|
||||||
6. **Слово из своего словаря не трогается — список закрыт.**
|
|
||||||
|
|
||||||
<!-- копия: язык-словарь из av-dev-pm/skills/canon/references/language.md -->
|
|
||||||
| Термин | Что называет |
|
|
||||||
| --- | --- |
|
|
||||||
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
|
|
||||||
| триаж | стадия конвейера, сводящая находки в решение |
|
|
||||||
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
|
|
||||||
| дедуп, дедупликация | сверка нового против уже лежащего |
|
|
||||||
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
|
|
||||||
| дифф, `--base` | разница между состояниями в git |
|
|
||||||
| промпт | текст, которым зовут модель |
|
|
||||||
| change, capability, spec | сущности OpenSpec, имена вещей чужого инструмента |
|
|
||||||
| generative, applicative | роды проходов ревью, вводятся определением по месту |
|
|
||||||
|
|
||||||
**Список закрыт.** Слово не отсюда и не из таблицы имён вещей выше — находка, а
|
|
||||||
не «принятый стиль»: у него либо есть живой русский аналог, либо оно требует
|
|
||||||
ввода одной строкой при первом употреблении.
|
|
||||||
|
|
||||||
Отсюда же читается снятое. Эти слова из текстов убраны, и возвращать их не надо:
|
|
||||||
**конфляция** (смешение), **декорреляция** (разведённость, разведён с кем-то),
|
|
||||||
**непоймание** (почему не поймали), **эвал-сет** (проверочный набор), **гайд**
|
|
||||||
(руководство). Каждое было латинизмом или калькой при живом русском слове, и
|
|
||||||
каждое к моменту снятия жило в трёх-шести файлах разом — то есть выглядело
|
|
||||||
словарём, не будучи им.
|
|
||||||
|
|
||||||
<!-- /копия: язык-словарь -->
|
|
||||||
|
|
||||||
7. **Жаргон и метафоры заменяются прямым называнием.**
|
|
||||||
|
|
||||||
<!-- копия: язык-жаргон из av-dev-pm/skills/canon/references/language.md -->
|
|
||||||
|
|
||||||
| Метафора-жаргон | Прямо |
|
|
||||||
| --- | --- |
|
|
||||||
| рычаг (кэша, отбора) | условие отбора, параметр |
|
|
||||||
| навешен не на тот счётчик | завязан не на тот счётчик |
|
|
||||||
| переширокий матчинг по имени | слишком грубое сопоставление по имени, слишком много слабых совпадений |
|
|
||||||
| костыль | временное решение, обходной путь — и в чём именно |
|
|
||||||
| просело, отвалилось | стало медленнее на столько-то, перестало отвечать |
|
|
||||||
|
|
||||||
Проверка: **фраза требует, чтобы читатель додумал образ, — заменяется буквальным
|
|
||||||
описанием того, что происходит.**
|
|
||||||
|
|
||||||
<!-- /копия: язык-жаргон -->
|
|
||||||
|
|
||||||
8. **Термин, которого нет в документах проекта, вводится одной строкой или не
|
|
||||||
употребляется.** Заменять его своей догадкой нельзя: ты не знаешь предметную
|
|
||||||
область. Пиши «термин «X» не встречается ни в документах, ни в других
|
|
||||||
поданных файлах — введи строкой или назови известным словом».
|
|
||||||
|
|
||||||
**Слово, занятое в другом смысле, — та же находка.** Термин, который в одном
|
|
||||||
документе проекта значит одно, а здесь другое, ломает оба; назови оба места.
|
|
||||||
|
|
||||||
9. **Имя файла — английское слово по сути, а не транслит.** `queue-as-table`, а
|
|
||||||
не `ochered-tablicej`; `move-parse-strict`, а не `razbor-hoda`. Транслит
|
|
||||||
нечитаем тому, кто ищет по смыслу, и не сокращается, а имя стоит в ссылках,
|
|
||||||
коммитах и путях, которые набирают руками.
|
|
||||||
|
|
||||||
Кириллицу в имени и не-kebab-case ловят `docs.py` и `tasks.py` — про них
|
|
||||||
молчи. Твоё — **транслит**, потому что машина проверяет его эвристикой и
|
|
||||||
ловит не всё: `sostoyanie-partii` проходит мимо неё. Находка — готовое
|
|
||||||
английское имя на замену плюс напоминание, что переименование это **перенос
|
|
||||||
ссылок одним проходом**, а не правка одного файла.
|
|
||||||
|
|
||||||
## Чего ты не проверяешь
|
|
||||||
|
|
||||||
Не своё бывает двух разных родов, и поступают с ними по-разному.
|
|
||||||
|
|
||||||
**Чужому подрядчику — строкой в границах покрытия.** Форма записи задачи у
|
|
||||||
`task-form`; согласованность документов между собой (факт в двух домах,
|
|
||||||
противоречие, поведение в обзоре) у `doc-consistency`; соответствие документов
|
|
||||||
коду у `doc-code-drift`. Увидел — назови в конце одной строкой, чтобы находка не
|
|
||||||
пропала, но находкой не оформляй.
|
|
||||||
|
|
||||||
**Машинной проверке — вообще ничего.** Всё, что ловят `tasks.py check` и
|
|
||||||
`docs.py check` (состав и написание секций, наличие разделов, число критериев,
|
|
||||||
теги, тег `question` при непустом разделе «Вопросы», согласованность индексов,
|
|
||||||
битые ссылки), **не пиши даже строкой**: это не потерянная находка, а уже
|
|
||||||
проверенное. Повторять машинную проверку словами — заводить второй дом для
|
|
||||||
одного правила.
|
|
||||||
|
|
||||||
**Содержание**: верно ли решение, нужна ли задача, полна ли архитектура. Это
|
|
||||||
разбор, а не вычитка, — и о нём тоже молчи.
|
|
||||||
|
|
||||||
## Порог вмешательства
|
|
||||||
|
|
||||||
<!-- копия: порог-правки из av-dev-pm/skills/canon/references/language.md -->
|
|
||||||
|
|
||||||
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
|
||||||
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
|
||||||
перестают читать весь список, и вместе с ним пропадают настоящие находки.
|
|
||||||
Сомневаешься — не правь. Формулировка, которая просто **не твоя**, — не находка.
|
|
||||||
|
|
||||||
**Систематичность нарушения — не довод в его пользу.** Одна и та же ошибка в
|
|
||||||
пяти файлах не становится «принятым стилем»: чаще это значит, что правило не
|
|
||||||
применялось вовсе, — и находка тем важнее. «Так сделано везде» годится как
|
|
||||||
основание для **одной находки на весь набор** («правило N нарушено в пяти
|
|
||||||
записях, перечень: …»), но не как основание промолчать. Принятым считается
|
|
||||||
только то, что назвал зовущий или что записано в конвенциях проекта.
|
|
||||||
|
|
||||||
<!-- /копия: порог-правки -->
|
|
||||||
|
|
||||||
Одна запись может дать несколько находок, но заголовок правится один раз: не
|
|
||||||
предлагай два варианта на выбор, предлагай лучший.
|
|
||||||
|
|
||||||
## Доклад
|
|
||||||
|
|
||||||
Находки по одной, в порядке важности: залог и оценки → жаргон и англицизмы →
|
|
||||||
стоп-слова. Первые меняют, **что** читатель понимает; последние — только сколько
|
|
||||||
он на это тратит.
|
|
||||||
|
|
||||||
```
|
|
||||||
<файл>
|
|
||||||
правило: <номер и короткое имя>
|
|
||||||
сейчас: <как написано>
|
|
||||||
предложение: <готовая формулировка, подставляемая как есть>
|
|
||||||
почему: <одна фраза>
|
|
||||||
```
|
|
||||||
|
|
||||||
В конце — **границы покрытия**: сколько файлов просмотрено из скольких, какие не
|
|
||||||
смотрел и почему, и по чему проверялись термины (документы проекта названы или
|
|
||||||
нет). Отчёт без этой строки читается как «всё вычитано», не сообщая, какая часть
|
|
||||||
осталась нетронутой. Туда же — строка «замечено не по моей части», если бросилась
|
|
||||||
в глаза форма записи; машинно проверяемое в неё **не идёт**.
|
|
||||||
|
|
||||||
Ничего не нашёл — так и скажи одной строкой. Пустой доклад с границами покрытия
|
|
||||||
полезнее выдуманной находки.
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
# Язык проектных текстов
|
|
||||||
|
|
||||||
Правила для всего, что пишется словами: задачи и цели, документы канона,
|
|
||||||
решения ADR, записки разведки, сообщения коммитов. Не для кода и не для
|
|
||||||
сообщений программы пользователю — там свои конвенции проекта.
|
|
||||||
|
|
||||||
Основа — **информационный стиль** Максима Ильяхова ([учебник
|
|
||||||
бюро](https://bureau.ru/projects/book-text/), книга «Пиши, сокращай»). Он
|
|
||||||
написан для рекламы, статей и писем, поэтому взят не целиком: ниже сказано, что
|
|
||||||
взято и что отброшено намеренно.
|
|
||||||
|
|
||||||
## Зачем он здесь
|
|
||||||
|
|
||||||
Проектный текст читают в двух положениях, и оба неудобные: **выбирают, брать ли
|
|
||||||
задачу**, глядя в строку индекса и один экран тела; и **возвращаются через
|
|
||||||
квартал**, не помня контекста. Оба положения наказывают одно и то же — слова, не
|
|
||||||
несущие сведений. Информационный стиль ровно про это, и его польза здесь не
|
|
||||||
эстетическая: текст, из которого нельзя достать факт, заставляет открывать код,
|
|
||||||
а это и есть цена, которой мы избегаем.
|
|
||||||
|
|
||||||
## Что взято
|
|
||||||
|
|
||||||
**Полезное действие.** У каждого текста есть вопрос, на который он отвечает, и
|
|
||||||
читатель, который этот вопрос задаёт. Не отвечает — не пишется. У задачи это
|
|
||||||
«зачем она нужна» и «что станет наблюдаемо иначе»; у документа канона — его
|
|
||||||
собственный вопрос («что это за система», «как сложено», «почему так решили»).
|
|
||||||
Текст, который не отвечает ни на чей вопрос, сокращается до нуля — это законный
|
|
||||||
исход правки.
|
|
||||||
|
|
||||||
**Глагол вместо отглагольного существительного, действие вместо состояния.**
|
|
||||||
«Обработчик не проверяет владельца», а не «проверка владельца не
|
|
||||||
осуществляется»; «сопоставляет по имени», а не «осуществляет сопоставление по
|
|
||||||
имени». Отглагольное существительное прячет того, кто действует, — а в
|
|
||||||
техническом тексте именно он и важен.
|
|
||||||
|
|
||||||
**Активный залог.** «Скрипт переписывает индекс», а не «индекс переписывается
|
|
||||||
скриптом». Страдательный залог остаётся там, где деятель неизвестен или
|
|
||||||
неважен, и это не поблажка: «файл удаляется» верно, когда удаляет любая из трёх
|
|
||||||
команд.
|
|
||||||
|
|
||||||
**Конкретика вместо оценок.** Факты, имена, цифры: «время ответа доходит до
|
|
||||||
800 мс», а не «работает медленно»; «тело 40 МиБ держит блокировку 5 секунд», а
|
|
||||||
не «большие тела тормозят». Оценка допустима, когда за ней в той же фразе стоит
|
|
||||||
факт. Без факта оценка — не сведение, а настроение.
|
|
||||||
|
|
||||||
**Стоп-слова.** Убирается то, что можно убрать без потери смысла:
|
|
||||||
|
|
||||||
| Что | Примеры |
|
|
||||||
| --- | --- |
|
|
||||||
| канцелярит | является, осуществляется, в целях, в рамках, данный, вышеуказанный, необходимо отметить |
|
|
||||||
| вводные-паразиты | в общем, как известно, стоит отметить, не секрет, что |
|
|
||||||
| усилители | очень, крайне, достаточно, абсолютно, максимально, полностью |
|
|
||||||
| синонимы одного качества | «понятный и простой», «быстрый и производительный» |
|
|
||||||
| неопределённое | какой-то, некоторый, соответствующий, определённый |
|
|
||||||
|
|
||||||
Проверка одна: **вычеркни слово. Смысл изменился — оставляй.**
|
|
||||||
|
|
||||||
**Одна мысль — одно предложение.** Предложение, в котором два независимых
|
|
||||||
утверждения, делится. Придаточное, которое можно вынести в отдельную фразу,
|
|
||||||
выносится.
|
|
||||||
|
|
||||||
Исключение — **поля, которым формат отвёл одно предложение**. «Зачем» в мете
|
|
||||||
задачи именно такое: оно повторяется строкой индекса, и второе предложение там
|
|
||||||
просто не поместится. Такое поле либо укладывается в одну фразу, либо
|
|
||||||
сокращается, но не делится.
|
|
||||||
|
|
||||||
**Параллельность.** Однородное пишется одинаково: пункты списка — одной
|
|
||||||
грамматической формой, разделы одного вида — одним порядком, заголовки одного
|
|
||||||
уровня — одним типом фразы. Разнобой читатель принимает за разницу по существу и
|
|
||||||
ищет её.
|
|
||||||
|
|
||||||
**Заголовок работает.** Заголовок называет содержание раздела, а не тему
|
|
||||||
вообще: «Что проверяет `check`», а не «О проверках». Заголовков ставится
|
|
||||||
столько, чтобы длинный текст можно было просматривать, а не только читать
|
|
||||||
подряд.
|
|
||||||
|
|
||||||
## Что отброшено намеренно
|
|
||||||
|
|
||||||
Инфостиль написан для текстов, где читателя надо удержать. Проектный текст
|
|
||||||
читают потому, что надо, и держать его нечем. Отсюда три расхождения:
|
|
||||||
|
|
||||||
- **Парцелляция и рубленые фразы — нет.** Приём «Коротко. Ещё короче. Вот так»
|
|
||||||
ломает причинную связь, а в решении и в задаче ценность именно в ней:
|
|
||||||
«поэтому», «иначе», «раз так» несут смысл и остаются.
|
|
||||||
- **Не всякое вводное — мусор.** «Если», «иначе», «при таком-то условии»,
|
|
||||||
«в отличие от» — это условия и противопоставления, то есть сведения. Режутся
|
|
||||||
вводные, которые не меняют смысл предложения.
|
|
||||||
- **Скобки и точка с запятой остаются.** В технической записи скобки несут
|
|
||||||
уточнение — имя команды, единицы, слаг, — и запрет на них удлинил бы текст, а
|
|
||||||
не сократил. Запрет на многоточие принимаем: в проектном тексте оно значит
|
|
||||||
«дописать позже», и такой текст лучше не публиковать.
|
|
||||||
|
|
||||||
И общее: инфостиль призывает «снять корону с себя и надеть на читателя». Здесь
|
|
||||||
читатель — **ты сам через квартал** и тот, кто возьмёт задачу. Писать для них
|
|
||||||
значит называть состояние и остаток, а не пересказывать, как было интересно
|
|
||||||
разбираться.
|
|
||||||
|
|
||||||
## Англицизмы
|
|
||||||
|
|
||||||
Англицизм-калька заменяется, когда у него есть естественный русский аналог.
|
|
||||||
|
|
||||||
<!-- дом: язык-англицизмы -->
|
|
||||||
|
|
||||||
| Калька | Русский аналог |
|
|
||||||
| --- | --- |
|
|
||||||
| флоу | поток, процесс, сценарий |
|
|
||||||
| фикс, зафиксить | исправление, исправить, починить |
|
|
||||||
| чекать | проверять |
|
|
||||||
| апрув, заапрувить | согласование, согласовать |
|
|
||||||
| best-effort | по возможности |
|
|
||||||
| кейс | случай, сценарий |
|
|
||||||
| перформанс | производительность |
|
|
||||||
| матчинг, смэтчить | сопоставление, сопоставить |
|
|
||||||
| зарелизить | выпустить, выложить |
|
|
||||||
| отрефакторить | переписать, разделить, убрать второй путь |
|
|
||||||
|
|
||||||
Насильно не переводится то, что является **именем вещи**: термины технологий и
|
|
||||||
протоколов (`SQL`, `API`, `CSV`, `N+1`, `IDOR`), имена классов, методов, полей,
|
|
||||||
таблиц и команд, слаг, а также термин, у которого нет точного русского
|
|
||||||
эквивалента и который в команде уже прижился.
|
|
||||||
|
|
||||||
Цель — простой и точный текст, а не пуризм. Русский аналог звучит коряво или
|
|
||||||
искажает смысл — остаётся термин.
|
|
||||||
|
|
||||||
<!-- /дом: язык-англицизмы -->
|
|
||||||
|
|
||||||
## Свой словарь — закрытый список
|
|
||||||
|
|
||||||
Слово, не переводимое потому, что оно **имя вещи этого процесса**, а не украшение.
|
|
||||||
Оговорка «термин прижился» без списка проверяема на глаз и потому не проверяема:
|
|
||||||
прижившимся выглядит любое слово, встреченное трижды.
|
|
||||||
|
|
||||||
<!-- дом: язык-словарь -->
|
|
||||||
| Термин | Что называет |
|
|
||||||
| --- | --- |
|
|
||||||
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
|
|
||||||
| триаж | стадия конвейера, сводящая находки в решение |
|
|
||||||
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
|
|
||||||
| дедуп, дедупликация | сверка нового против уже лежащего |
|
|
||||||
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
|
|
||||||
| дифф, `--base` | разница между состояниями в git |
|
|
||||||
| промпт | текст, которым зовут модель |
|
|
||||||
| change, capability, spec | сущности OpenSpec, имена вещей чужого инструмента |
|
|
||||||
| generative, applicative | роды проходов ревью, вводятся определением по месту |
|
|
||||||
|
|
||||||
**Список закрыт.** Слово не отсюда и не из таблицы имён вещей выше — находка, а
|
|
||||||
не «принятый стиль»: у него либо есть живой русский аналог, либо оно требует
|
|
||||||
ввода одной строкой при первом употреблении.
|
|
||||||
|
|
||||||
Отсюда же читается снятое. Эти слова из текстов убраны, и возвращать их не надо:
|
|
||||||
**конфляция** (смешение), **декорреляция** (разведённость, разведён с кем-то),
|
|
||||||
**непоймание** (почему не поймали), **эвал-сет** (проверочный набор), **гайд**
|
|
||||||
(руководство). Каждое было латинизмом или калькой при живом русском слове, и
|
|
||||||
каждое к моменту снятия жило в трёх-шести файлах разом — то есть выглядело
|
|
||||||
словарём, не будучи им.
|
|
||||||
|
|
||||||
<!-- /дом: язык-словарь -->
|
|
||||||
|
|
||||||
## Жаргон и метафоры
|
|
||||||
|
|
||||||
Система не описывается внутренними метафорами и образными ярлыками: автору они
|
|
||||||
понятны, читателю — нет. Вещь называется прямо.
|
|
||||||
|
|
||||||
<!-- дом: язык-жаргон -->
|
|
||||||
|
|
||||||
| Метафора-жаргон | Прямо |
|
|
||||||
| --- | --- |
|
|
||||||
| рычаг (кэша, отбора) | условие отбора, параметр |
|
|
||||||
| навешен не на тот счётчик | завязан не на тот счётчик |
|
|
||||||
| переширокий матчинг по имени | слишком грубое сопоставление по имени, слишком много слабых совпадений |
|
|
||||||
| костыль | временное решение, обходной путь — и в чём именно |
|
|
||||||
| просело, отвалилось | стало медленнее на столько-то, перестало отвечать |
|
|
||||||
|
|
||||||
Проверка: **фраза требует, чтобы читатель додумал образ, — заменяется буквальным
|
|
||||||
описанием того, что происходит.**
|
|
||||||
|
|
||||||
<!-- /дом: язык-жаргон -->
|
|
||||||
|
|
||||||
## Термин, которого нет в проекте
|
|
||||||
|
|
||||||
Термин, не встречающийся ни в паспорте, ни в архитектуре, ни в конвенциях,
|
|
||||||
**вводится одной строкой или не употребляется**. Свой словарь у отдельной записи
|
|
||||||
— самый дешёвый способ сделать беклог нечитаемым для того, кто вернётся к нему
|
|
||||||
через квартал.
|
|
||||||
|
|
||||||
Заменять незнакомый термин догадкой нельзя: догадка о предметной области дороже
|
|
||||||
непонятного слова, потому что выглядит понятной.
|
|
||||||
|
|
||||||
## Порог правки
|
|
||||||
|
|
||||||
<!-- дом: порог-правки -->
|
|
||||||
|
|
||||||
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
|
||||||
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
|
||||||
перестают читать весь список, и вместе с ним пропадают настоящие находки.
|
|
||||||
Сомневаешься — не правь. Формулировка, которая просто **не твоя**, — не находка.
|
|
||||||
|
|
||||||
**Систематичность нарушения — не довод в его пользу.** Одна и та же ошибка в
|
|
||||||
пяти файлах не становится «принятым стилем»: чаще это значит, что правило не
|
|
||||||
применялось вовсе, — и находка тем важнее. «Так сделано везде» годится как
|
|
||||||
основание для **одной находки на весь набор** («правило N нарушено в пяти
|
|
||||||
записях, перечень: …»), но не как основание промолчать. Принятым считается
|
|
||||||
только то, что назвал зовущий или что записано в конвенциях проекта.
|
|
||||||
|
|
||||||
<!-- /дом: порог-правки -->
|
|
||||||
|
|
||||||
И обратное: язык правится **по ходу той операции, которая записи касается**.
|
|
||||||
Беклог не переписывают ради языка.
|
|
||||||
@@ -1,303 +0,0 @@
|
|||||||
---
|
|
||||||
name: session
|
|
||||||
description: "Ритуал между спринтами и ведение самого спринта: разбор накопившихся вопросов, разбор прошедшего спринта про процесс, переоценка задач порциями, выбор цели (или решение, что спринт без цели) и набор нового спринта с заморозкой. Плюс правила по ходу спринта — что врывается в замороженный набор, чем вопрос отличается от блокера, когда задача выходит из спринта, что считается сделанным и что идёт в доклад. Использовать, когда просят закрыть или начать спринт, собрать набор, разобрать вопросы, провести груминг/переоценку/ретроспективу, решить «что делать дальше» или доложить итоги, а также когда вернулись к проекту после перерыва и надо понять, где остановились. Формат и содержимое задач — скилл tasks."
|
|
||||||
---
|
|
||||||
|
|
||||||
# Сессия между спринтами
|
|
||||||
|
|
||||||
Работа идёт спринтами: **набор задач, замороженный до конца спринта** — обычно
|
|
||||||
под одну цель, но бывает и без неё. Между спринтами — одна сессия из четырёх шагов. Этот скилл владеет
|
|
||||||
**ритуалом**: как сессия проводится и как спринт ведётся. Форматом и содержимым
|
|
||||||
задач владеет скилл `tasks`, выполнением задачи — пайплайн проекта.
|
|
||||||
|
|
||||||
## Почему не Scrum
|
|
||||||
|
|
||||||
Терминология близка — спринт, груминг, определение готовности, ретроспектива, —
|
|
||||||
и это удобно: не нужно изобретать слова. Но добрая половина Scrum существует
|
|
||||||
ради синхронизации людей, которых здесь нет.
|
|
||||||
|
|
||||||
**Не берём:** тайм-бокс (спринт ограничен объёмом, а не временем), velocity и
|
|
||||||
оценки в очках, ежедневный стендап (стендап — это и есть диалог), планирование
|
|
||||||
отдельно от груминга (владелец беклога один), роль скрам-мастера.
|
|
||||||
|
|
||||||
**Берём:** цель спринта, заморозку набора, определение готовности, груминг —
|
|
||||||
каждое потому, что снимает решение, которое иначе принимается заново каждый раз.
|
|
||||||
**Ретроспективу берём содержанием, но не отдельным ритуалом:** она шаг той же
|
|
||||||
сессии. Процесс личный, синхронизировать некого, а отдельная встреча ради трёх
|
|
||||||
вопросов — та самая плата ритуалом без выгоды.
|
|
||||||
|
|
||||||
## Роли
|
|
||||||
|
|
||||||
**Человек** выбирает цель спринта — **или решает, что этот спринт без цели**, —
|
|
||||||
разбирает вопросы, держит право на необратимое и на истину в самих данных.
|
|
||||||
|
|
||||||
**Агент — оркестрация.** Он собирает набор под названную цель, ставит задачи,
|
|
||||||
принимает отчёты и докладывает. Кто именно делает задачу — исполнитель, сабагент,
|
|
||||||
пайплайн — дело проекта; сессия про это не знает и знать не должна.
|
|
||||||
|
|
||||||
## Единицы
|
|
||||||
|
|
||||||
- **Цель** — то, ради чего набирается спринт. Файл типа `goal` (🎯),
|
|
||||||
перечисленный в `ROADMAP.md`. Цель постоянна: живёт, пока живёт направление, —
|
|
||||||
и уходит вместе с ним, если замысел оказался неверен (порядок отмены — в
|
|
||||||
[tasks](../tasks/references/task-goal.md#отменённая-цель--сперва-задачи-потом-цель)).
|
|
||||||
- **Задача** — то, что мерджится целиком и даёт видимую пользу.
|
|
||||||
- **Вопрос** — решение человека. Не останавливает начатую работу, но **блокирует
|
|
||||||
взятие** задачи в спринт. Живёт внутри файла задачи разделом «Вопросы» и тегом
|
|
||||||
`question`.
|
|
||||||
- **Блокер** — состояние, когда спринт не может продолжаться **ни одной**
|
|
||||||
задачей.
|
|
||||||
- **Спринт** — набор задач, замороженный до его конца. Под одной целью — или
|
|
||||||
**без цели вовсе**, законно: багфикс, техдолг, спринт здоровья. Такой набор
|
|
||||||
собран по работоспособности, а не по направлению, и заводится явно
|
|
||||||
(`sprint start --no-goal`).
|
|
||||||
|
|
||||||
## Вопрос, блокер, необратимое
|
|
||||||
|
|
||||||
| | Что это | Когда спрашиваем | Что останавливает |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| **Вопрос** | решение человека | на сессии, пачкой | взятие задачи в спринт |
|
|
||||||
| **Блокер** | спринт не может продолжаться ни одной задачей | немедленно | всё |
|
|
||||||
|
|
||||||
Право на **необратимое** — третье и отдельное: что именно необратимо, называет
|
|
||||||
`CLAUDE.md` проекта, и спрашивается оно всегда, независимо от спринта.
|
|
||||||
|
|
||||||
**Блокер определяется исходом, а не одновременностью.** Встали разом или
|
|
||||||
высыпались из спринта по одной — если продолжать нечем, это блокер: спринт
|
|
||||||
распускается (`sprint close --dissolve --reason …`), человек спрашивается
|
|
||||||
немедленно. Иначе спринт, из которого задачи вышли поштучно, выглядел бы штатно
|
|
||||||
завершённым, а вопросы тихо ждали бы сессии.
|
|
||||||
|
|
||||||
**Отличать вопрос от застревания.** Правило про остаток принадлежит управлению
|
|
||||||
задачами: оно решает, **сделана задача или вышла**, а это исход планирования, не
|
|
||||||
исполнения. **Ниже канонический текст; пайплайн проекта на него ссылается, а не
|
|
||||||
пересказывает** — два экземпляра одного правила разъезжаются, и разъезжаются
|
|
||||||
незаметно, потому что расхождение видно только на редком входе.
|
|
||||||
|
|
||||||
> Есть остаток, который доводится без ответа, — задача продолжается, вопрос
|
|
||||||
> записывается в файл. Остатка нет — задача выходит из спринта.
|
|
||||||
|
|
||||||
С двумя оговорками, без которых тест ошибается:
|
|
||||||
|
|
||||||
> **Остаток, который материализует нерешённое** — записывает в хранилище,
|
|
||||||
> журнал, витрину **или наружу** состояние, зависящее от неотвеченного вопроса,
|
|
||||||
> — **не остаток**. Решение поднимается до начала записи: откатить запись
|
|
||||||
> дороже, чем подождать ответ, а иногда невозможно. «Наружу» — часть правила, а
|
|
||||||
> не пример: выкладка, публикация и отправка данных третьей стороне не
|
|
||||||
> откатываются тем более.
|
|
||||||
|
|
||||||
> **Пол для остатка:** остаток, из которого пропала польза, названная в «зачем», —
|
|
||||||
> это не сделанная задача, а вышедшая из спринта.
|
|
||||||
|
|
||||||
## Заморозка набора
|
|
||||||
|
|
||||||
**Целей не больше одной.** Названа цель — набор служит ей: задача под чужой
|
|
||||||
целью в спринт не попадает, даже если взять удобно (`sprint take` это и
|
|
||||||
запрещает). **Задача с открытым вопросом в набор не берётся** — это верно всегда.
|
|
||||||
|
|
||||||
**Спринт без цели — законный случай, а не недосмотр.** Багфикс, техдолг,
|
|
||||||
здоровье: работа на работоспособность, а не на направление. Цель не названа —
|
|
||||||
сверять нечего, и в такой набор идёт что угодно готовое к взятию, в том числе
|
|
||||||
задачи под разными целями. Заводится он **явно**, `sprint start --no-goal`:
|
|
||||||
забытый флаг и решение человека иначе неотличимы, а это решение продуктовое.
|
|
||||||
Взамен проверки цели остаётся доклад — спринт без цели **называется таковым и
|
|
||||||
объясняется** одной строкой.
|
|
||||||
|
|
||||||
**Новая работа падает в беклог, а не в идущий спринт.** Решение «врываться или
|
|
||||||
отложить» принимается один раз правилом, а не заново каждый раз. Врывается
|
|
||||||
только два класса:
|
|
||||||
|
|
||||||
1. **Необратимый ущерб** — потеря, порча или утечка данных: то, что не чинится
|
|
||||||
доделкой потом.
|
|
||||||
2. **Сломан общий станок** — красная проверка, на которой стоит определение
|
|
||||||
готовности **всех** задач набора. Это не новая работа, а починка того, на чём
|
|
||||||
делается вся остальная.
|
|
||||||
|
|
||||||
Что в проекте считается необратимым ущербом и что — общим станком, называет
|
|
||||||
`CLAUDE.md` проекта. Не названо — спрашиваем человека, а не решаем сами.
|
|
||||||
|
|
||||||
**Конец спринта** — когда каждая задача набора либо сделана, либо вышла с
|
|
||||||
записанной причиной. Не «все сделаны»: иначе одна застрявшая задача держит
|
|
||||||
спринт бесконечно. Пустой набор закрывается `sprint close` — скрипт не даст
|
|
||||||
закрыть непустой.
|
|
||||||
|
|
||||||
Ведение спринта целиком — исходы задачи, определение готовности, приёмка,
|
|
||||||
доклад — [references/sprint.md](references/sprint.md).
|
|
||||||
|
|
||||||
## Сессия: четыре шага в этом порядке
|
|
||||||
|
|
||||||
Это зависимость, а не список.
|
|
||||||
|
|
||||||
1. **Разбор вопросов.**
|
|
||||||
2. **Разбор прошедшего спринта — про процесс, а не про задачи.** Здесь же оба
|
|
||||||
судьи документов канона на весь канон разом, раз в спринт: `doc-consistency`
|
|
||||||
(документы между собой) и `doc-code-drift` (документы против кода).
|
|
||||||
3. **Переоценка задач** порциями.
|
|
||||||
4. **Выбор цели и набор спринта.** Цель называет человек — либо называет, что
|
|
||||||
этот спринт без цели; набор собирает агент и показывает **до старта работ**.
|
|
||||||
|
|
||||||
Рёбра подписаны тем, что ломается при их нарушении:
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
check["tasks.py check (+ --fix)<br/>результат — строкой в доклад"]
|
|
||||||
s1["1. Разбор вопросов<br/>пачкой, не больше трёх за раз"]
|
|
||||||
s2["2. Разбор прошедшего спринта<br/>про процесс → docs/review.md"]
|
|
||||||
s3["3. Переоценка задач порциями"]
|
|
||||||
s4["4. Цель называет человек,<br/>набор собирает агент"]
|
|
||||||
sprint["спринт: набор заморожен"]
|
|
||||||
|
|
||||||
check --> s1
|
|
||||||
s1 --> s2
|
|
||||||
s2 --> s3
|
|
||||||
s1 -->|"неотвеченный вопрос → переоценка вслепую"| s3
|
|
||||||
s3 -->|"без переоценки набор берётся из протухшего"| s4
|
|
||||||
s4 --> sprint
|
|
||||||
```
|
|
||||||
|
|
||||||
Схема — **сводка**: процедура каждого шага в
|
|
||||||
[references/cadence.md](references/cadence.md), и при расхождении прав текст.
|
|
||||||
|
|
||||||
## Вернулся, а спринт открыт
|
|
||||||
|
|
||||||
Сессия — ритуал **между** спринтами, и шаг 1 предполагает только что закрытый.
|
|
||||||
Вход после перерыва другой, и начинается он не с шага, а с вопроса, свой ли ещё
|
|
||||||
набор:
|
|
||||||
|
|
||||||
1. `tasks.py check` — блок здоровья скажет состояние спринта, число готовых к
|
|
||||||
взятию и залежавшихся; при расхождении раскладки `--fix`.
|
|
||||||
2. Прочитать `SPRINT.md`: цель (или запись, что её нет), состав, дата начала.
|
|
||||||
3. **Развилка, и решает её человек.** Набор всё ещё твой — продолжай спринт, ни
|
|
||||||
сессии, ни переоценки не нужно, они между спринтами. Взялся перечитывать,
|
|
||||||
зачем эти задачи собраны вместе, — набор протух:
|
|
||||||
`sprint close --dissolve --reason …`, недоделанное возвращается в беклог,
|
|
||||||
дальше обычная сессия с шага 1.
|
|
||||||
|
|
||||||
Порога в неделях нет намеренно — почему, в
|
|
||||||
[references/sprint.md](references/sprint.md), «Протухший набор».
|
|
||||||
Середины у развилки тоже нет: «доделаю пару штук и решу» — это работа по набору,
|
|
||||||
которого ты уже не понимаешь.
|
|
||||||
|
|
||||||
Процедура каждого шага, размер и отбор порции, храповик на залежавшихся, формат
|
|
||||||
интерактива и доклад — [references/cadence.md](references/cadence.md).
|
|
||||||
|
|
||||||
## Инструмент
|
|
||||||
|
|
||||||
Тот же `tasks.py`, что у скилла `tasks` — оба скилла в одном плагине, путь
|
|
||||||
общий: `tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"`. Сессии нужны
|
|
||||||
прежде всего:
|
|
||||||
|
|
||||||
```
|
|
||||||
python3 $tk check --dir D # с этого начинается любая сессия
|
|
||||||
python3 $tk list --dir D --questions # шаг 1: что накопилось
|
|
||||||
python3 $tk list --dir D --tag sprint:<слаг> # шаг 3: урожай спринта, первая порция
|
|
||||||
python3 $tk list --dir D --stale # шаг 3: дальше по залежалости
|
|
||||||
python3 $tk list --dir D --goal <слаг> # шаг 4: кандидаты под названную цель
|
|
||||||
python3 $tk sprint start --dir D --goal <слаг> # шаг 4: заводит и слаг спринта
|
|
||||||
python3 $tk sprint start --dir D --no-goal # шаг 4: набор без цели, явным флагом
|
|
||||||
python3 $tk sprint take --dir D <слаг> … # шаг 4: набор
|
|
||||||
python3 $tk sprint close --dir D # конец спринта; --dissolve при блокере
|
|
||||||
python3 $tk reopen <слаг> --dir D --reason … # приёмка не сошлась после закрытия
|
|
||||||
```
|
|
||||||
|
|
||||||
`D` — каталог задач проекта, по канону всегда `docs/tasks`; `--dir` передаётся
|
|
||||||
явно каждой командой. Вызов из чужого контекста описан в скилле `tasks`
|
|
||||||
(«Переносимость»). **Коды выхода** — там же: 1 это дрейф в беклоге, 3 это
|
|
||||||
«каталога нет», и ветвиться на них надо по-разному.
|
|
||||||
|
|
||||||
**Слаг спринта заводит `sprint start`** (по умолчанию — дата) и пишет его в
|
|
||||||
`SPRINT.md`; всё заведённое **при открытом спринте** помечается `sprint:<слаг>`
|
|
||||||
автоматически. Поэтому «первая порция — урожай прошедшего спринта» работает без
|
|
||||||
чьей-либо памяти — но ровно до команды `sprint close`, которая `SPRINT.md`
|
|
||||||
очищает. Отсюда порядок: **урожай заводится до закрытия, слаг для сессии берётся
|
|
||||||
из отчёта `sprint close`** ([references/sprint.md](references/sprint.md)).
|
|
||||||
|
|
||||||
Правки задач делаются мутациями (`edit`, `move`, `close`), а не редактором:
|
|
||||||
руками правится только тело файла. Это правило скилла `tasks`, здесь оно не
|
|
||||||
пересказывается.
|
|
||||||
|
|
||||||
## Стимулы, которые процесс создаёт
|
|
||||||
|
|
||||||
Правило, которое можно обойти в свою пользу, будет обойдено.
|
|
||||||
|
|
||||||
**Приёмщик и исполнитель здесь совпадают, и это надо назвать вслух.** Задачу
|
|
||||||
закрывает и двигает по индексам агент-оркестратор — тот же, кто её и сделал.
|
|
||||||
Прежде границу держала механика: моста между плагинами не было, и закрыть задачу
|
|
||||||
пайплайн физически не мог. Теперь мост есть, и защита у трёх обходов ниже —
|
|
||||||
**только текстовая**. Опоры, которые остались настоящими:
|
|
||||||
|
|
||||||
- **независимый отчёт ревью** — артефакт, написанный не исполнителем; по нему
|
|
||||||
сверяют состав прогона и урожай. Где он лежит, знает пайплайн проекта; при
|
|
||||||
конвейере `av-dev-pipeline` это отчёт триажа в
|
|
||||||
`openspec/changes/archive/<id>/review/` (до архивации — `changes/<id>/review/`);
|
|
||||||
- **`SPRINT.md` под git** — `git log -p` показывает, что и когда было закрыто.
|
|
||||||
Работает, только если закрытие **закоммичено**: удаление файла задачи и правка
|
|
||||||
индекса, оставшиеся в рабочем дереве, никакой истории не образуют;
|
|
||||||
- **`reopen <слаг> --reason`** — закрытие не окончательно. Приёмка человеком на
|
|
||||||
сессии его отменяет, и это штатная операция, а не скандал.
|
|
||||||
|
|
||||||
Известные обходы:
|
|
||||||
|
|
||||||
- **Скрыть блокер** — он останавливает всё и выглядит как провал исполнителя.
|
|
||||||
Защита: тест про остаток плюс прямая запись, что **объявление блокера
|
|
||||||
неудачей не считается**.
|
|
||||||
- **Не записать вопрос** на задаче-кандидате, чтобы не вычеркнуть её из
|
|
||||||
ближайшего набора. Защита: вопросы кандидатов разбираются на той же сессии
|
|
||||||
**вне очереди порции**.
|
|
||||||
- **Занизить критерии приёмки**, раз они пол. Защита ослаблена: правит их тот же,
|
|
||||||
кто по ним отчитывается. Остаётся требование, что расхождение критериев с
|
|
||||||
сутью — **дефект критериев, о котором сообщают, а не молча дорабатывают**, и
|
|
||||||
переоценка на сессии, где критерии видит человек.
|
|
||||||
- **Сжать задачу до остатка** и отчитаться «сделана». Защита ослаблена там же.
|
|
||||||
Пол для остатка — польза, названная в «зачем»; проверяет его человек при приёмке,
|
|
||||||
и `reopen` — его инструмент.
|
|
||||||
- **Объявить спринт без цели**, чтобы не задавать человеку продуктовый вопрос:
|
|
||||||
набор без цели берёт что угодно, и собрать его можно молча. Защита: цели нет
|
|
||||||
— это **ответ человека, а не умолчание** (`--no-goal` спрашивается так же, как
|
|
||||||
цель), плюс строка доклада, называющая спринт бесцельным и объясняющая почему.
|
|
||||||
Два бесцельных спринта подряд — предмет разбора процесса, а не мелочь.
|
|
||||||
- **Занизить урожай** — не заводить найденное по ходу. Защита: поимённая сверка
|
|
||||||
с **сохранённым независимым отчётом**, а не с прозой исполнителя. Каждая
|
|
||||||
отложенная находка имеет либо слаг, либо строку «не заведена: причина».
|
|
||||||
Нулевой урожай при непустом отчёте виден сразу.
|
|
||||||
|
|
||||||
**Проект без конвейера ревью — независимого отчёта нет, и это надо сказать, а не
|
|
||||||
обойти молча.** Задачи делались руками или чужим пайплайном, сверять урожай не с
|
|
||||||
чем: остаётся проза исполнителя, то есть тот же взгляд, что и у автора. Тогда
|
|
||||||
защита от занижения урожая **снята**, и доклад спринта обязан нести строку «урожай
|
|
||||||
сверялся с отчётом исполнителя — независимого отчёта в проекте нет». Дальше это
|
|
||||||
решение человека: завести конвейер, принимать выборочной перепроверкой или
|
|
||||||
согласиться с ценой. Молчание здесь хуже любого из трёх исходов.
|
|
||||||
|
|
||||||
Стимулы внутри пайплайна задачи (занизить требования к проверке, пропустить
|
|
||||||
проход) принадлежат ему и защищены там же.
|
|
||||||
|
|
||||||
## Слоты проекта
|
|
||||||
|
|
||||||
Сессия не знает ни языка, ни сборки, ни CI. На часть проектного отвечает своей
|
|
||||||
структурой [канон](../canon/references/canon.md): разбор процесса (шаг 2) живёт
|
|
||||||
в `docs/review.md`, оракулы и «чем краснеет безусловно» — в семантике гейта в
|
|
||||||
`CLAUDE.md`. Остальное проект **дописывает в `CLAUDE.md`**:
|
|
||||||
|
|
||||||
1. **Пайплайн задачи** — чем задача выполняется и что входит в его определение
|
|
||||||
готовности. Сессия требует только форму: пайплайн пройден + критерии приёмки
|
|
||||||
проверены поимённо.
|
|
||||||
2. **Общий станок** — какая проверка, покраснев, врывается в замороженный
|
|
||||||
спринт.
|
|
||||||
3. **Необратимое** — что спрашивается у человека всегда (тот же слот, что у
|
|
||||||
скилла `tasks`; дом один).
|
|
||||||
4. **Ориентир по размеру спринта**, если он замерялся. Умолчание — 5–8 задач, и
|
|
||||||
это **ориентир, а не закон**.
|
|
||||||
|
|
||||||
Слота «куда копируются критерии приёмки» здесь нет намеренно: на него отвечает
|
|
||||||
**пайплайн проекта** — он переносит критерии в описание изменения, когда его
|
|
||||||
заводит. Проект без пайплайна называет своё место сам, в слоте 1.
|
|
||||||
|
|
||||||
Числа проекта (сколько задач в спринте, сколько времени на задачу, каков прирост
|
|
||||||
беклога) — предмет шага 2, а не константы этого скилла.
|
|
||||||
|
|
||||||
## Чего этот скилл не делает
|
|
||||||
|
|
||||||
Не пишет код и не выполняет задачи. Не заводит и не переоформляет задачи сам по
|
|
||||||
себе — формат и содержимое ведёт `tasks` (сессия зовёт его операции). Не решает
|
|
||||||
за человека, какая цель следующая. Не двигает набор идущего спринта.
|
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
# Сессия: четыре шага
|
|
||||||
|
|
||||||
Одна сессия между спринтами. Порядок шагов — **зависимость, а не список**:
|
|
||||||
переоценивать задачи, не разобрав вопросы, значит переоценивать вслепую; набирать
|
|
||||||
спринт, не переоценив, значит набирать из протухшего.
|
|
||||||
|
|
||||||
Начинается сессия с `tasks.py check` (и `check --fix`, если дрейф накопился) —
|
|
||||||
результат идёт строкой в доклад.
|
|
||||||
|
|
||||||
## Шаг 1. Разбор вопросов
|
|
||||||
|
|
||||||
`tasks.py list --questions` — всё, что накопилось. Вопрос это решение человека,
|
|
||||||
и разбирается он **пачкой**, а не по одному, как только возник: по одному —
|
|
||||||
это дёрганье, пачкой — это сессия.
|
|
||||||
|
|
||||||
Порядок по каждому вопросу:
|
|
||||||
|
|
||||||
1. **Проверь, не отвечен ли он уже** — решением, документом, соседним
|
|
||||||
изменением, самим ходом прошедшего спринта. Отвеченный вопрос не выносится
|
|
||||||
человеку: это самая частая находка и она не требует ничьего решения.
|
|
||||||
2. **Сформулируй развилку** с вариантами и последствием каждого, рекомендация —
|
|
||||||
первым вариантом.
|
|
||||||
3. **Вынеси пачкой** через `AskUserQuestion`, не больше трёх за раз.
|
|
||||||
4. **Запиши ответ в тело задачи, опустоши раздел «Вопросы»**, сними тег
|
|
||||||
(`edit <slug> --rm-tag question`), **перепиши «зачем»**: «Решено:
|
|
||||||
…» на вопрос «почему это лежит в беклоге» уже не отвечает. Опустошение
|
|
||||||
раздела — не уборка, а условие взятия: правило и причина в скилле `tasks`,
|
|
||||||
[references/task-format.md](../../tasks/references/task-format.md).
|
|
||||||
|
|
||||||
**Вопросы на задачах-кандидатах разбираются вне очереди порции** — здесь же, на
|
|
||||||
этой сессии, даже если сама задача в порцию переоценки не попала. Иначе правило
|
|
||||||
«задача с открытым вопросом в набор не берётся» создаёт стимул вопрос не
|
|
||||||
записывать, лишь бы не вычеркнуть задачу из ближайшего спринта.
|
|
||||||
|
|
||||||
## Шаг 2. Разбор прошедшего спринта — про процесс, а не про задачи
|
|
||||||
|
|
||||||
Не «что мы сделали» (это доклад спринта, он уже был), а:
|
|
||||||
|
|
||||||
- **что сломалось в процессе и почему не поймали** — промах, доехавший до конца;
|
|
||||||
- **что оказалось дороже, чем выглядело при заведении** — не число, а сам факт и
|
|
||||||
причина: чего не было видно в постановке;
|
|
||||||
- **какие правила не сработали или сработали не так** — в том числе правила
|
|
||||||
этого плагина.
|
|
||||||
|
|
||||||
Замеров процесс не ведёт намеренно: оценки в очках и velocity не взяты
|
|
||||||
(«[Почему не Scrum](../SKILL.md#почему-не-scrum)»), а спринт ограничен объёмом, а
|
|
||||||
не временем — сравнивать «сколько заняло» не с чем. Разбор здесь качественный, и
|
|
||||||
это не упущение.
|
|
||||||
|
|
||||||
**Артефакт обязателен.** Вывод, оставшийся в контексте сессии, не существует:
|
|
||||||
следующая сессия его не увидит. Дом у него один и известен из канона —
|
|
||||||
**`docs/review.md`**: вывод про конвейер и про то, что перестали проверять, идёт
|
|
||||||
в раздел настройки, вывод про воспроизведённый дефект — в журнал. Решение с
|
|
||||||
долгим следом — в `docs/adr/`.
|
|
||||||
|
|
||||||
Отдельным ритуалом ретроспектива не выделяется: процесс личный,
|
|
||||||
синхронизировать некого.
|
|
||||||
|
|
||||||
**Здесь же зовутся оба судьи документов** — на весь канон разом, а не на пачку,
|
|
||||||
отобранную работой:
|
|
||||||
|
|
||||||
- **`doc-consistency`** — согласованность документов между собой и с openspec:
|
|
||||||
факт в двух домах, прямое противоречие, поведение в `architecture.md` вместо
|
|
||||||
спек, ADR без парного статуса при замене, число без провенанса;
|
|
||||||
- **`doc-code-drift`** — сверка с кодом по закрытому перечню фактов: имя основной
|
|
||||||
ветки, команды, пути, внешние зависимости поимённо, настройки с числовым
|
|
||||||
значением, единые точки проекта, capability.
|
|
||||||
|
|
||||||
Раз в спринт, а не чаще. Дорог из них по-настоящему первый — `doc-consistency`
|
|
||||||
на `opus`: он сличает утверждения двух документов, и это суждение. Второй с
|
|
||||||
недавних пор на `sonnet` — у него закрытый перечень фактов и команда на каждый, —
|
|
||||||
но он читает репозиторий целиком, и дешёвым от смены модели не стал. Но и не
|
|
||||||
реже — **спринт это ровно то, что двигает код и документы**:
|
|
||||||
переименованная цель сборки, ушедшая зависимость, второй способ делать то, что
|
|
||||||
обзор объявил единственным; факт, дописанный в `architecture.md`, уже живущий в
|
|
||||||
`CLAUDE.md`. Протухшее и раздвоившееся неотличимо от свежего, и по нему принимают
|
|
||||||
решения, пока кто-нибудь не наткнётся.
|
|
||||||
|
|
||||||
**Пачка — весь канон, и это не расточительство, а охват.** Когда пачку отбирала
|
|
||||||
работа, без присмотра оставалось ровно то, чего работа не касалась: правка,
|
|
||||||
отменившая решение, живёт в одном документе, а парный статус нужен в другом.
|
|
||||||
Канон мал, раз в спринт он читается целиком.
|
|
||||||
|
|
||||||
Находки обоих — обычный материал переоценки: строка на замену идёт в документ
|
|
||||||
сразу, работа больше чем на абзац становится задачей типа `chore`. **Позвал —
|
|
||||||
скажи в докладе, кого именно позвал, и приложи границы покрытия**; не позвал —
|
|
||||||
скажи и это, иначе доклад читается как «сверено».
|
|
||||||
|
|
||||||
## Шаг 3. Переоценка задач
|
|
||||||
|
|
||||||
Цель — выкинуть то, что перестало быть задачей, и вернуть остальному честное
|
|
||||||
состояние. Не «пересмотреть всё», а «пересмотреть порцию до конца».
|
|
||||||
|
|
||||||
### Порция и правило остановки
|
|
||||||
|
|
||||||
Тридцать задач за один заход — это усталость и штамповка: последние десять
|
|
||||||
получат «оставить» не потому, что живы, а потому, что сессия затянулась.
|
|
||||||
|
|
||||||
- **5–8 задач за порцию.** Размер обоснован усталостью, а не пропускной
|
|
||||||
способностью, и менять его не надо — **надо брать несколько порций за
|
|
||||||
сессию**.
|
|
||||||
- **Сколько порций:** не меньше `⌈урожай прошедшего спринта / 8⌉`. Урожай — это
|
|
||||||
задачи, заведённые за спринт; при урожае в 15 это две-три порции.
|
|
||||||
- **Отбор порций по порядку:**
|
|
||||||
1. **урожай спринта** — `list --tag sprint:<слаг>`: свежезаведённое ещё не
|
|
||||||
проходило ни одной проверки на нужность. Тег на задачах проставлен
|
|
||||||
автоматически при заведении — руками не метят и не вспоминают. **Слаг
|
|
||||||
берётся из отчёта `sprint close`, а не из `SPRINT.md`:** сессия идёт после
|
|
||||||
закрытия, а закрытие этот файл очищает;
|
|
||||||
2. дальше **по залежалости** — `list --stale`;
|
|
||||||
3. по потребности — одна секция целиком, один тег (партия ревью), одна цель
|
|
||||||
(`--goal`), список от пользователя.
|
|
||||||
- **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось».
|
|
||||||
Между порциями — промежуточный доклад.
|
|
||||||
|
|
||||||
### Что делать с каждой задачей
|
|
||||||
|
|
||||||
Сперва то, что не требует ничьего решения:
|
|
||||||
|
|
||||||
1. **Проверь, не сделано ли уже.** Задача, реализованная попутно в соседнем
|
|
||||||
изменении, — самая частая находка. Смотри код, документацию, историю коммитов
|
|
||||||
по ключевым словам. Удаление «как реализованной» деструктивно и без следа
|
|
||||||
(в `REJECTED.md` реализованные не пишутся), поэтому порог улики жёсткий:
|
|
||||||
`close <slug> --implemented` только имея **конкретный коммит или строку
|
|
||||||
документа**, закрывающие задачу, и ссылка идёт в доклад. Есть лишь косвенные
|
|
||||||
признаки — не удаляй сам, вынеси в пачку вопросов. Сделана частично → задача
|
|
||||||
сжимается до остатка: тело правишь редактором, заголовок и «зачем» — через
|
|
||||||
`edit`.
|
|
||||||
2. **Проверь, не отменена ли решением.** Документ, ADR или архивное изменение
|
|
||||||
мог закрыть вопрос иначе — тогда `close <slug> --reason "<ссылка на
|
|
||||||
решение>"`. Задача закрывается не только коммитом.
|
|
||||||
3. **Проверь пересечения.** Две задачи об одном — содержимое в одну, вторую
|
|
||||||
`close <slug> --reason "слита с <другой-слаг>"`. Смотри **шире порции**:
|
|
||||||
интейк дедуплицирует новое против существующего, но никогда не
|
|
||||||
пересматривает уже лежащее, и две задачи с одной причиной могут лежать рядом
|
|
||||||
месяцами.
|
|
||||||
4. **Пере-кластеризуй по общей причине.** Несколько задач, оказавшихся симптомами
|
|
||||||
одного дефекта, сливаются в одну — это находка, которую интейк дать не мог.
|
|
||||||
5. **Гигиена полей** — протухшее «зачем», вопрос в прозе, снятый ответ, свойство
|
|
||||||
репозитория в рамках, предписание процесса в теле, тип, разошедшийся с
|
|
||||||
задачей, границы вместо реализации в разделе «Затрагивает». Список и правила —
|
|
||||||
в скилле `tasks`. **Переоценка — то самое место, где беклог добирает тип и
|
|
||||||
разделы его схемы:** требовать их на входе значило бы выгонять в заметки то,
|
|
||||||
что должно лежать задачей, а к взятию в спринт они уже обязательны. Блок
|
|
||||||
здоровья `check` печатает, сколько записей готово к взятию, — по этому числу
|
|
||||||
и видно, добрала переоценка или нет.
|
|
||||||
|
|
||||||
Затем — то, что решает пользователь:
|
|
||||||
|
|
||||||
6. **Жива ли она вообще.** Контекст мог измениться: ушла зависимость, отпал
|
|
||||||
сценарий, обошли иначе. Здесь и звучит вопрос, выкидывать ли.
|
|
||||||
7. **Та ли цель — и нужна ли она вообще.** Приоритетов нет, и «повысить» нечего
|
|
||||||
— вместо повышения задача **меняет цель** (`edit <slug> --goal <другой>`) или
|
|
||||||
входит в ближайший набор. `feature`, которой не находится цель, — кандидат
|
|
||||||
на выход: новая возможность вне цели это возможность, которой никто не
|
|
||||||
заказывал. Операционной задаче (`fix`, `chore`, `research`) цель не нужна, и
|
|
||||||
выдумывать её здесь не надо.
|
|
||||||
|
|
||||||
**Отменяется и сама цель** — когда замысел оказался неверен, а не когда
|
|
||||||
задача выбрала не ту. Тогда порция расширяется до всех задач этой цели: каждую
|
|
||||||
либо закрыть своей причиной, либо перевесить на другую цель, и только потом
|
|
||||||
закрыть цель. Порядок и почему он такой —
|
|
||||||
[task-goal.md](../../tasks/references/task-goal.md#отменённая-цель--сперва-задачи-потом-цель).
|
|
||||||
Здесь этому и место: отмена цели это разбор её задач, а разбор задач — этот
|
|
||||||
шаг.
|
|
||||||
8. **Задача ли это по-прежнему.** Не проходит тест «готова к взятию» → `edit
|
|
||||||
<slug> --type research` и опустошённый раздел «Вопрос», то есть сырьё; дальше
|
|
||||||
штурм. Разрослась → это несколько задач под той же целью, дальше
|
|
||||||
декомпозиция.
|
|
||||||
9. **Переоценка по пройденному.** Прошедший спринт показывает, чего на самом
|
|
||||||
деле стоит такая работа. Это меняет цену **других** задач, и именно здесь
|
|
||||||
применяется: задача, оказавшаяся заметно дороже, чем думалось, при прежней
|
|
||||||
пользе — кандидат на выход. Судит человек по тому, что помнит о прошедшем
|
|
||||||
спринте; замеров процесс не ведёт и оценок не хранит.
|
|
||||||
|
|
||||||
### Храповик на залежавшихся
|
|
||||||
|
|
||||||
Сильно залежавшаяся задача — сигнал сама по себе: её либо ни разу не собирались
|
|
||||||
делать, либо нечем взять. Измеряй наблюдаемым — датой последней правки из git
|
|
||||||
(`list --stale` ставит такие первыми); счётчик «сколько сессий пережила» нигде
|
|
||||||
не хранится.
|
|
||||||
|
|
||||||
Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений,
|
|
||||||
**либо двигается (меняет цель, идёт в набор, уходит с причиной), либо остаётся с
|
|
||||||
явно записанной причиной**, почему её держим (`move <slug> --section <та же>
|
|
||||||
--reason …`). Молчаливое «оставить как есть» на давно неподвижной задаче — это
|
|
||||||
решение не принимать решение; запись причины превращает его в осознанное и не
|
|
||||||
даёт тому же вопросу всплыть на следующей сессии.
|
|
||||||
|
|
||||||
### Интерактив
|
|
||||||
|
|
||||||
- Вопросы — через `AskUserQuestion`, **не больше трёх за раз**. Порция в 5–8
|
|
||||||
задач обычно даёт больше трёх суждений — тогда веди несколько итераций по ≤3,
|
|
||||||
а не по одному на задачу и не одним перегруженным запросом.
|
|
||||||
- К каждому варианту — **предварительное суждение, рекомендация первым
|
|
||||||
вариантом**: «предлагаю выкинуть, потому что …». Пользователю дешевле
|
|
||||||
возразить, чем судить с нуля.
|
|
||||||
- Всё, что решается фактом (сделано / отменено / дублируется), решай сам и
|
|
||||||
показывай списком в докладе, а не выноси в вопросы.
|
|
||||||
|
|
||||||
Пример одной итерации — три залежавшихся задачи, механику по ним уже разобрали:
|
|
||||||
|
|
||||||
> **Переоценка: 3 залежавшихся (порция по `--stale`)**
|
|
||||||
>
|
|
||||||
> 1. `versii-kachestvo-repaki` — версии и качество одного тайтла
|
|
||||||
> - Выкинуть *(рекомендую)* — помечена «не боль», за полгода ни разу не возникла
|
|
||||||
> - Оставить под целью `nadyozhnost-razdach`
|
|
||||||
> - Перевести под цель `kachestvo-mediateki` — там она первая в очереди
|
|
||||||
> 2. `backup-sqlite` — бэкап базы
|
|
||||||
> - Оставить под текущей целью *(рекомендую)* — не сработала, но риск реальный
|
|
||||||
> - Взять в ближайший набор — без бэкапа ретеншн опасен
|
|
||||||
> - Выкинуть
|
|
||||||
> 3. `guessit-sputnik` — вынести распознавание в сервис-спутник
|
|
||||||
> - Понизить до сырья (`--type research`) *(рекомендую)* — не проходит тест «готова к взятию»
|
|
||||||
> - Оставить задачей
|
|
||||||
|
|
||||||
Каждый вариант несёт причину — ту самую, что уедет в `--reason`. Ответы применяй
|
|
||||||
сразу и, если в порции осталось ещё, следующей итерацией показывай следующие ≤3.
|
|
||||||
|
|
||||||
## Шаг 4. Выбор цели и набор спринта
|
|
||||||
|
|
||||||
1. **Покажи состояние проекта**: секцию `Готово` (что приложение уже умеет —
|
|
||||||
это половина ответа на «где мы»), затем `Запланировано` с обоснованием
|
|
||||||
очереди, `Направления`, и
|
|
||||||
по каждой цели-кандидату — сколько под ней задач без открытых вопросов
|
|
||||||
(`list --goal <слаг>`). Цель без готовых задач набором не станет: её сперва
|
|
||||||
надо декомпозировать.
|
|
||||||
2. **Цель называет человек** — либо называет, что цели не будет. Это
|
|
||||||
продуктовое решение, а не механика: агент предлагает и объясняет, но не
|
|
||||||
выбирает. **Оба ответа законны**, и «без цели» — такой же ответ, как слаг:
|
|
||||||
спринт бывает под багфикс, под техдолг, под здоровье проекта. Спрашивается он
|
|
||||||
так же, как цель, и в отдельный вопрос не выносится: это один и тот же вопрос
|
|
||||||
«подо что набираем».
|
|
||||||
3. **Набор собирает агент** — `sprint start --goal <слаг>` (или `sprint start
|
|
||||||
--no-goal`), затем `sprint take …`. Скрипт не даст взять цель, задачу с чужой
|
|
||||||
целью, с открытым вопросом, без типа и **без разделов, которых требует её
|
|
||||||
тип** (у `fix` это в том числе `Воспроизведение`, у `research` — `Вопрос` и
|
|
||||||
`Куда ляжет ответ`, и сырьё поэтому не берётся вовсе). Задача без цели (`fix`,
|
|
||||||
`chore`, `research`) берётся свободно — операционная работа входит в набор
|
|
||||||
помимо его цели. **В спринте без цели чужой цели нет вовсе**: сверять не с
|
|
||||||
чем, берётся что угодно готовое, и единственной защитой остаётся показ набора
|
|
||||||
человеку.
|
|
||||||
4. **Набор показывается человеку до старта работ.** Показ — это и есть момент
|
|
||||||
заморозки: после него набор не двигается. **В показе называется состав по
|
|
||||||
типам** — три `fix` и ни одной `feature` под целью развития это разговор про
|
|
||||||
цель, а не про набор, и увидеть его надо до заморозки, а не в докладе по
|
|
||||||
итогам. **У набора без цели показ — единственная проверка состава**: скрипту
|
|
||||||
там отказывать не по чему, и «что угодно готовое» превращается в осмысленный
|
|
||||||
набор только глазами человека.
|
|
||||||
|
|
||||||
Здесь же последний дешёвый момент заметить **разнородную задачу**: раздел
|
|
||||||
«Затрагивает» показывает границы до того, как заведено предложение об
|
|
||||||
изменении. Строка, которая одна тянет задачу на метку выше остального
|
|
||||||
перечня, — кандидат на разрез (шов — в `tasks`, `references/split.md`).
|
|
||||||
Замеченная здесь, она стоит одного `edit`; замеченная на ревью — выброшенного
|
|
||||||
предложения.
|
|
||||||
5. Задача, которой для взятия не хватает только разделов её типа, дописывается
|
|
||||||
здесь же — критерии с оракулами, перечень границ, шаги воспроизведения. Но
|
|
||||||
если для этого нужен ответ человека, это вопрос, и задача в набор не идёт.
|
|
||||||
|
|
||||||
**Размер — ориентир, а не закон:** 5–8 задач. Можно взять больше, можно меньше —
|
|
||||||
набор под цель важнее круглого числа; одна крупная задача спринтом тоже бывает.
|
|
||||||
|
|
||||||
## Доклад сессии
|
|
||||||
|
|
||||||
- Что просмотрено: N из M, сколько порций, по какому признаку отобраны.
|
|
||||||
- Вопросы: разобрано N, из них отвечено без человека N, снято тегов N.
|
|
||||||
- Разбор процесса: что записано и куда.
|
|
||||||
- Сверка документов с кодом: звался ли `doc-code-drift`, что проверено из
|
|
||||||
названного, что разошлось.
|
|
||||||
- Изменения списком: удалено как реализованное (со ссылками), ушло без
|
|
||||||
реализации (с причинами), понижено до сырья, слито, сменило тип или цель.
|
|
||||||
- Новый спринт: цель — **или строка «без цели» с объяснением, почему** (багфикс,
|
|
||||||
техдолг, здоровье), — набор со слагами, дата, состав по типам.
|
|
||||||
- **Границы покрытия**: сколько задач не трогали и какие именно секции, теги или
|
|
||||||
цели остались — иначе доклад читается как «беклог разобран».
|
|
||||||
- `tasks.py check` после правок — результат строкой.
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
# Ведение спринта
|
|
||||||
|
|
||||||
Спринт — набор задач, замороженный до его конца: под одну цель или **без цели**
|
|
||||||
(багфикс, техдолг, здоровье — это законно, `sprint start --no-goal`). Здесь то,
|
|
||||||
что происходит **внутри** спринта: как задача заканчивается, что считается сделанным,
|
|
||||||
кто принимает и что идёт в доклад. Как спринт набирается — шаг 4 в
|
|
||||||
[cadence.md](cadence.md).
|
|
||||||
|
|
||||||
## Наблюдаемые исходы задачи
|
|
||||||
|
|
||||||
Как они достигаются — дело пайплайна проекта. Сессия знает только исход и его
|
|
||||||
след.
|
|
||||||
|
|
||||||
- **Сделана** — по определению готовности ниже. `close <slug> --implemented`:
|
|
||||||
файл и строка удаляются, следом остаётся коммит. **Закрывает агент-оркестратор
|
|
||||||
последним шагом пайплайна, после коммита; приёмка человеком идёт позже и
|
|
||||||
отменяется `reopen`** — см. «Кто и когда закрывает».
|
|
||||||
- **Вышла из спринта** — `sprint drop <slug> --reason …`: возвращается в беклог
|
|
||||||
с вопросом в файле и **без живого незакоммиченного предложения** — иначе при
|
|
||||||
следующем взятии оно столкнётся с новым. Наработки, которые жалко терять,
|
|
||||||
переезжают в тело задачи текстом.
|
|
||||||
- **Оказалась крупнее задачи** — распознаётся **до того, как под неё заведено
|
|
||||||
предложение об изменении**, иначе его придётся выбрасывать. Выходит из набора,
|
|
||||||
уходит на декомпозицию; спринт продолжается остальными, части заводятся под той
|
|
||||||
же целью (у спринта без цели — без неё) и в замороженный набор не добавляются.
|
|
||||||
- **Отменена решением по ходу** — `close <slug> --reason "<ссылка на решение>"`
|
|
||||||
прямо из спринта. Это редкий, но законный исход, и он называется в докладе.
|
|
||||||
|
|
||||||
**Конец спринта** — когда по каждой задаче набора наступил один из исходов. Не
|
|
||||||
«все сделаны»: иначе одна застрявшая задача держит спринт бесконечно. Затем
|
|
||||||
`sprint close`.
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart TD
|
|
||||||
take["sprint take — задача в наборе"]
|
|
||||||
done["сделана<br/>close --implemented"]
|
|
||||||
out["вышла<br/>sprint drop --reason"]
|
|
||||||
epic["крупнее задачи<br/>распознаётся до заведения change"]
|
|
||||||
cancel["отменена решением по ходу<br/>close --reason"]
|
|
||||||
all{"по каждой задаче набора<br/>наступил исход?"}
|
|
||||||
harvest["урожай заводится интейком tasks"]
|
|
||||||
close["sprint close"]
|
|
||||||
dissolve["sprint close --dissolve --reason<br/>недоделанное — в беклог"]
|
|
||||||
|
|
||||||
take --> done
|
|
||||||
take --> out
|
|
||||||
take --> epic
|
|
||||||
take --> cancel
|
|
||||||
done --> all
|
|
||||||
out --> all
|
|
||||||
epic --> all
|
|
||||||
cancel --> all
|
|
||||||
all -->|да| harvest
|
|
||||||
harvest -->|"тег sprint: ставится, пока SPRINT.md не очищен"| close
|
|
||||||
take -->|"продолжать нечем ни одной задачей — блокер"| dissolve
|
|
||||||
done -->|"приёмка не сошлась: reopen --reason"| take
|
|
||||||
```
|
|
||||||
|
|
||||||
Два ребра на схеме — те, где порядок обязателен и нарушается молча: **урожай до
|
|
||||||
`sprint close`** (после команды автотег уже не поставится) и **блокер в обход
|
|
||||||
исходов** (спринт распускается, а не ждёт).
|
|
||||||
|
|
||||||
Схема — **сводка**: определение готовности и правила приёмки ниже, и при
|
|
||||||
расхождении прав текст.
|
|
||||||
|
|
||||||
**Урожай заводится при закрытии спринта, а не при закрытии задачи.** Это
|
|
||||||
обязанность закрывающего: пройти по спискам находок от исполнителей и завести
|
|
||||||
недостающее интейком скилла `tasks` — с дедупликацией и картой человеку. Заводимое
|
|
||||||
метится тегом спринта само (`sprint:<слаг>`), поэтому первая порция следующей
|
|
||||||
сессии поднимается одной командой `list --tag sprint:<слаг>`. Спринт, закрытый
|
|
||||||
без этого шага, оставляет находки жить в отчётах — то есть нигде.
|
|
||||||
|
|
||||||
**Порядок здесь обязателен: урожай заводится ДО команды `sprint close`.**
|
|
||||||
Автотег ставится по слагу из `SPRINT.md`, а `sprint close` этот файл очищает;
|
|
||||||
заведённое после команды остаётся без тега и в первую порцию следующей сессии
|
|
||||||
не попадёт — молча, потому что пустой `list --tag` выглядит как «урожая не
|
|
||||||
было». Если так уже вышло, тег ставится руками: `add … --tag sprint:<слаг>`,
|
|
||||||
слаг берётся из отчёта `sprint close`.
|
|
||||||
|
|
||||||
**Провал спринта.** Сработал блокер — спринт распускается (`sprint close
|
|
||||||
--dissolve --reason …`), недоделанное возвращается в беклог, новый набор
|
|
||||||
делается после ответа человека. Спринт не «ждёт»: ждать может человек, а
|
|
||||||
замороженный набор, который нельзя двигать, только мешает.
|
|
||||||
|
|
||||||
**Протухший набор — второй законный повод роспуска.** Работа стояла, и человек
|
|
||||||
вернулся к спринту, состав которого уже не держит в голове. Тем же роспуском:
|
|
||||||
`sprint close --dissolve --reason "работа стояла с <когда>"`, недоделанное в
|
|
||||||
беклог, новый набор — после переоценки, а не поверх старого.
|
|
||||||
|
|
||||||
Порога в неделях нет и не будет: счётчик простоя пришлось бы вести руками, а
|
|
||||||
решает всё равно человек. Признак — не срок, а **что набор перестал быть твоим**:
|
|
||||||
взялся перечитывать, зачем эти задачи вместе, — он протух. Заморозка тут не
|
|
||||||
мешает, она запрещает *двигать* набор, а не распустить его целиком.
|
|
||||||
|
|
||||||
## Определение готовности
|
|
||||||
|
|
||||||
Задача засчитывается сделанной, когда верно **всё**:
|
|
||||||
|
|
||||||
1. **Пайплайн задачи пройден до конца** — со своим определением готовности, за
|
|
||||||
которое отвечает проект: проверки, состав ревью, документация, коммит. Здесь
|
|
||||||
оно не пересказывается и не подменяется — **форма фиксирована, содержание
|
|
||||||
даёт `CLAUDE.md` проекта**. Пайплайна нет, задача сделана руками — условие
|
|
||||||
читается как «проверки проекта зелёные и изменение влито».
|
|
||||||
2. **Критерии приёмки проверены поимённо** — каждый со своим оракулом, исход по
|
|
||||||
каждому назван. Это единственное, что добавляет управление задачами: пайплайн
|
|
||||||
отвечает «сделано по правилам», критерии — «сделано то, что заказывали».
|
|
||||||
3. **Находки по ходу отданы списком** — исполнитель обязан их **назвать**
|
|
||||||
(каждую, с пометкой «заведена / не заведена: причина»), но **не обязан
|
|
||||||
заводить**: заведение интерактивно — оно требует дедупликации против беклога
|
|
||||||
и кладбища, а ещё решений человека. Обязанность **завести урожай** — на
|
|
||||||
закрытии спринта, ниже. Иначе автономный исполнитель оказался бы разом и
|
|
||||||
обязан завести задачи, и не вправе сделать это в одиночку.
|
|
||||||
|
|
||||||
### Кто и когда закрывает
|
|
||||||
|
|
||||||
**Задачу закрывает агент-оркестратор — тот же, кто её и сделал**, последним шагом
|
|
||||||
пайплайна, после коммита. Порядок:
|
|
||||||
|
|
||||||
1. пайплайн доводит задачу до коммита;
|
|
||||||
2. **после коммита** зовёт `Skill av-dev-pm:tasks` и закрывает задачу
|
|
||||||
(`close <slug> --implemented`); строка уходит из `SPRINT.md`;
|
|
||||||
3. **докладывает исход и по каждому критерию — оракул и наблюдаемый исход.**
|
|
||||||
Это доклад приёмщику, а не отметка «принято».
|
|
||||||
|
|
||||||
**Приёмщик и исполнитель здесь совпадают, и это принято сознательно** — цена
|
|
||||||
названа в `SKILL.md`, раздел «Стимулы». Поэтому закрытие **не окончательно**, а
|
|
||||||
доклад по критериям — не формальность: он единственное, по чему приёмка вообще
|
|
||||||
возможна.
|
|
||||||
|
|
||||||
**Порядок «коммит, потом закрытие» обязателен.** Закрытие удаляет файл задачи;
|
|
||||||
упавший коммит после закрытия оставил бы задачу закрытой без единого следа
|
|
||||||
работы.
|
|
||||||
|
|
||||||
**Само закрытие тоже коммитится, отдельным коммитом.** Удаление файла задачи и
|
|
||||||
правка индекса — правки в рабочем дереве; пока они не в истории, `SPRINT.md`
|
|
||||||
ничего не показывает, а `reopen` восстанавливает текст из `HEAD` в узком окне:
|
|
||||||
его закроет первый посторонний коммит. Сообщение про учёт, а не про
|
|
||||||
работу: `закрыта задача <slug>`.
|
|
||||||
|
|
||||||
**Дорога назад существует и обязана быть названа.** Человек на сессии сверил
|
|
||||||
критерии, и приёмка не сошлась — `tasks.py reopen <slug> --reason "приёмка не
|
|
||||||
сошлась: …"`:
|
|
||||||
файл восстанавливается из истории git, строка возвращается в набор идущего
|
|
||||||
спринта (или в беклог, если спринта нет), строка кладбища снимается. Тело
|
|
||||||
восстанавливается **на момент удаления** — всё, что было дописано позже, живёт
|
|
||||||
только в коммите задачи, и это называется в докладе.
|
|
||||||
|
|
||||||
### Кто и по чему принимает
|
|
||||||
|
|
||||||
Три условия, без которых пункт про критерии не исполняется никем:
|
|
||||||
|
|
||||||
1. **Критерии переживают файл задачи.** Файл удаляется при закрытии, поэтому
|
|
||||||
критерии копируются туда, где их увидит приёмщик. Куда именно — **отвечает
|
|
||||||
пайплайн проекта, а не слот в `CLAUDE.md`**: он переносит их в `tasks.md`
|
|
||||||
изменения, когда заводит change. Проект без пайплайна называет своё место
|
|
||||||
сам.
|
|
||||||
2. **Принимает человек на сессии, а не отдельный агент.** Исполнитель и приёмщик
|
|
||||||
в момент закрытия **не разведены** (решение о снятии и его
|
|
||||||
цена — в `SKILL.md`, «Стимулы»). Опоры остались три: **сохранённый независимый
|
|
||||||
отчёт ревью** (при конвейере `av-dev-pipeline` — отчёт триажа в
|
|
||||||
`openspec/changes/archive/<id>/review/`, до архивации — `changes/<id>/review/`),
|
|
||||||
`SPRINT.md` под git и `reopen`. Переоценка на сессии и есть момент, когда
|
|
||||||
критерии видит не исполнитель. **Конвейера ревью в проекте нет — первой опоры
|
|
||||||
нет тоже**, и это называется строкой доклада, а не обходится молча
|
|
||||||
(`SKILL.md`, «Стимулы»).
|
|
||||||
3. **Расхождение — дефект критериев.** Приёмщик правит критерии и возвращает
|
|
||||||
задачу исполнителю **в этом же спринте**: ответ есть, остаток есть, по тесту
|
|
||||||
про остаток это не выход из спринта.
|
|
||||||
|
|
||||||
## Что врывается в замороженный набор
|
|
||||||
|
|
||||||
Только два класса — правило и его обоснование в SKILL.md. Здесь механика:
|
|
||||||
|
|
||||||
- вторжение **не добавляет** задачу в набор: `SPRINT.md` остаётся тем набором,
|
|
||||||
который заморозили и показали. Внеплановая работа делается и называется в
|
|
||||||
докладе отдельной строкой «внеплановое: что и почему»;
|
|
||||||
- если внеплановое требует больше пары часов, честнее распустить спринт, чем
|
|
||||||
делать вид, что набор соблюдается;
|
|
||||||
- всё остальное падает в беклог через обычный интейк и ждёт сессии.
|
|
||||||
|
|
||||||
## Доклад в конце спринта
|
|
||||||
|
|
||||||
Проверяемые якоря, а не пересказ:
|
|
||||||
|
|
||||||
- **Цель спринта** — или строка «спринт без цели» с тем, чем он был (багфикс,
|
|
||||||
техдолг, здоровье): у бесцельного набора это единственное место, где состав
|
|
||||||
вообще объясняется. И по каждой задаче набора: **хеш коммита**, дословный
|
|
||||||
исход проверок проекта, **исход по каждому критерию приёмки**.
|
|
||||||
- **Какие развилки решались** и чем обоснованы.
|
|
||||||
- **Урожай:** сколько задач заведено, какие вопросы накопились, что вышло из
|
|
||||||
спринта и почему, что было внеплановым.
|
|
||||||
- **Поимённая сверка урожая** с независимыми отчётами ревью: каждая отложенная
|
|
||||||
находка имеет либо слаг, либо строку «не заведена: причина». Нулевой урожай при
|
|
||||||
непустом отчёте — сигнал, а не благополучие. **Отчётов нет** (проект без
|
|
||||||
конвейера ревью) — сверять не с чем, и строка доклада говорит именно это, а не
|
|
||||||
«сверено».
|
|
||||||
- **Созрела ли порция для сессии.** Решение звать — человека, напоминание —
|
|
||||||
обязанность агента: `⌈урожай / 8⌉` порций.
|
|
||||||
- **Границы покрытия** сжатой строкой: что в этом спринте не проверялось вовсе.
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"name": "av-dev-tasks",
|
||||||
|
"description": "Задачи и цели каталогом markdown-файлов: одна запись — файл в items/ плюс строка ровно в одном индексе, у записи тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение из диалога с фильтром и дедупом, разбор находок ревью в задачи, декомпозиция на независимо полезные части, гигиена полей, согласованность индексов скриптом tasks.py. Приоритет — явный порядок строк в беклоге, и расставляет его скилл groom: интерактивный разбор на два вопроса, что сейчас самое важное и что перестало быть важным. Готовность записи к работе проверяет команда ready. Записи вычитывают два прохода: task-form (форма записи) и task-wording (язык). Канон документов ведёт плагин av-dev-docs, он опционален. Задач не выполняет — этим занимается конвейер проекта.",
|
||||||
|
"author": {
|
||||||
|
"name": "Anton Vakhrushev",
|
||||||
|
"email": "anwinged@gmail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: task-form
|
name: task-form
|
||||||
description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (цель — что приложение будет уметь, задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле, и связь задачи со строкой «Завершения» её цели. Читает файл цели, на которую ссылается задача. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент doc-wording. Использовать после заведения или разбора пачки записей, до взятия в спринт и на переоценке. Только чтение."
|
description: "Проверка формы записи каталога задач по существу: тип, разошедшийся с содержанием записи, форма заголовка по типу (цель — что приложение будет уметь, задача — что нужно сделать, разведка — о чём она), «зачем», пересказывающее заголовок вместо состояния и боли, раздел «Затрагивает» с замыслом вместо границ, критерий приёмки с оракулом только на словах, предписание процесса в теле, и связь задачи со строкой «Завершения» её цели. Читает файл цели, на которую ссылается задача. Отдаёт готовые формулировки на замену и ничего не правит сам. Язык текста (залог, оценки, стоп-слова, англицизмы) смотрит отдельный агент task-wording. Использовать после заведения или разбора пачки записей, при проверке готовности перед взятием в работу (tasks.py ready) и на груминге. Только чтение."
|
||||||
tools: Read, Grep, Glob
|
tools: Read, Grep, Glob
|
||||||
model: sonnet
|
model: sonnet
|
||||||
color: green
|
color: green
|
||||||
@@ -15,7 +15,7 @@ color: green
|
|||||||
человек со скиллом `tasks`.
|
человек со скиллом `tasks`.
|
||||||
|
|
||||||
Границу с языком держи твёрдо. **Залог, оценки, стоп-слова, англицизмы, жаргон**
|
Границу с языком держи твёрдо. **Залог, оценки, стоп-слова, англицизмы, жаргон**
|
||||||
— у агента `doc-wording`, и тебе они не поручены даже там, где бросаются в
|
— у агента `task-wording`, и тебе они не поручены даже там, где бросаются в
|
||||||
глаза: две проверки одного места расходятся и начинают спорить. Увидел — скажи
|
глаза: две проверки одного места расходятся и начинают спорить. Увидел — скажи
|
||||||
одной строкой в конце доклада, не находкой. Исключение ровно одно: если
|
одной строкой в конце доклада, не находкой. Исключение ровно одно: если
|
||||||
неудачное слово стоит **в заголовке** и мешает ему ответить на вопрос своего
|
неудачное слово стоит **в заголовке** и мешает ему ответить на вопрос своего
|
||||||
@@ -27,7 +27,7 @@ color: green
|
|||||||
|
|
||||||
## Что тебе дают
|
## Что тебе дают
|
||||||
|
|
||||||
Список файлов записей (`docs/tasks/items/<slug>.md`) или каталог задач целиком.
|
Список файлов записей (`tasks/items/<slug>.md`) или каталог задач целиком.
|
||||||
Каталог тебе нужен и сам по себе: задача несёт тег `goal:<слаг>`, и **файл цели
|
Каталог тебе нужен и сам по себе: задача несёт тег `goal:<слаг>`, и **файл цели
|
||||||
ты открываешь**, иначе седьмое правило не проверить.
|
ты открываешь**, иначе седьмое правило не проверить.
|
||||||
|
|
||||||
@@ -124,18 +124,23 @@ color: green
|
|||||||
|
|
||||||
Не своё бывает двух разных родов, и поступают с ними по-разному.
|
Не своё бывает двух разных родов, и поступают с ними по-разному.
|
||||||
|
|
||||||
**Чужому подрядчику — строкой в границах покрытия.** Язык у `doc-wording`;
|
**Чужому подрядчику — строкой в границах покрытия.** Язык у `task-wording`;
|
||||||
согласованность документов канона между собой у `doc-consistency`, их
|
согласованность документов канона между собой у `doc-consistency`, их
|
||||||
соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе, но
|
соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе, но
|
||||||
если ты открыл цель и увидел расхождение в самом документе, оно их. Увидел —
|
если ты открыл цель и увидел расхождение в самом документе, оно их. Увидел —
|
||||||
назови в конце одной строкой, чтобы находка не пропала, но находкой не оформляй.
|
назови в конце одной строкой, чтобы находка не пропала, но находкой не оформляй.
|
||||||
|
|
||||||
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (наличие
|
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и
|
||||||
разделов, число критериев, состав и написание секций, теги, тег `question` при
|
написание секций, теги, тег `question` при непустом разделе «Вопросы»,
|
||||||
непустом разделе «Вопросы», согласованность индексов, битые ссылки, форма
|
согласованность индексов, битые ссылки, форма заголовка как строки), **не пиши
|
||||||
заголовка как строки), **не пиши даже строкой**: это не потерянная находка, а
|
даже строкой**: это не потерянная находка, а уже проверенное. Повторять машинную
|
||||||
уже проверенное. Повторять машинную проверку словами — заводить второй дом для
|
проверку словами — заводить второй дом для одного правила.
|
||||||
одного правила.
|
|
||||||
|
**Наличие разделов и число критериев `check` поимённо не называет** — он считает
|
||||||
|
их строкой здоровья, а поимённо судит `tasks.py ready` на входе в работу.
|
||||||
|
Отсутствующий раздел сам по себе всё равно не твоя находка (её увидит `ready`);
|
||||||
|
твоя — раздел, который **есть и лжёт**: границы вместо замысла, критерий с
|
||||||
|
оракулом только на словах.
|
||||||
|
|
||||||
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она,
|
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она,
|
||||||
достаточна ли декомпозиция. Седьмое правило подходит к этому близко и
|
достаточна ли декомпозиция. Седьмое правило подходит к этому близко и
|
||||||
@@ -143,7 +148,7 @@ color: green
|
|||||||
|
|
||||||
## Порог вмешательства
|
## Порог вмешательства
|
||||||
|
|
||||||
<!-- копия: порог-правки из av-dev-pm/skills/canon/references/language.md -->
|
<!-- копия: порог-правки из shared/language.md -->
|
||||||
|
|
||||||
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
||||||
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
---
|
||||||
|
name: task-wording
|
||||||
|
description: "Вычитка языка записей каталога задач по информационному стилю — задачи, цели, строки индексов и причины отказа. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в слаге. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form, документы проекта вычитывает doc-wording. Использовать после заведения или разбора пачки записей, до взятия в работу и на переоценке беклога. Только чтение."
|
||||||
|
tools: Read, Grep, Glob
|
||||||
|
model: sonnet
|
||||||
|
color: green
|
||||||
|
---
|
||||||
|
|
||||||
|
Ты — **вычитка языка записей каталога задач**: задач, целей, строк индексов и
|
||||||
|
причин отказа. Оптика — слова и фразы, а не то, что запись описывает: ты не
|
||||||
|
судишь, нужна ли задача, верно ли выбрана цель и правильно ли запись оформлена.
|
||||||
|
|
||||||
|
Границу держи твёрдо, и она у тебя одна. **Форму записи** — тип, заголовок по
|
||||||
|
типу, «зачем» вместо пересказа, раздел «Затрагивает», годность оракулов, связь
|
||||||
|
со строкой «Завершения» цели — смотрит `task-form`, и тебе она не поручена даже
|
||||||
|
там, где бросается в глаза. Отсюда же исключение, и оно **против** тебя:
|
||||||
|
неудачное слово **в заголовке** судит `task-form`, потому что заголовок целиком
|
||||||
|
его. Увидел не по своей части — скажи одной строкой в конце доклада, не
|
||||||
|
находкой: две проверки одного места расходятся и начинают спорить.
|
||||||
|
|
||||||
|
**Документы проекта — не твои**: их язык вычитывает `doc-wording`. Ты их
|
||||||
|
читаешь, но только как словарь — по ним проверяется, известен ли термин.
|
||||||
|
|
||||||
|
Ты **ничего не правишь**. Каждая находка — готовая формулировка на замену,
|
||||||
|
которую зовущий подставит командой (`edit <слаг> --title …`, `edit <слаг>
|
||||||
|
--why …`) или впишет редактором. Файлы ты только читаешь.
|
||||||
|
|
||||||
|
## Что тебе дают
|
||||||
|
|
||||||
|
Список записей или каталог задач: файлы `items/<slug>.md`, а с ними — индексы
|
||||||
|
(`BACKLOG.md`, `ROADMAP.md`, `REJECTED.md`), где та же запись представлена
|
||||||
|
строкой. **Строка индекса вычитывается наравне с файлом**: по ней запись
|
||||||
|
выбирают, не открывая тела, и «зачем» в ней повторяется дословно.
|
||||||
|
|
||||||
|
Плюс, если зовущий их назвал, документы проекта — паспорт, архитектура,
|
||||||
|
конвенции: по ним проверяется, известен ли термин. **Не назвали — считай
|
||||||
|
известными только те слова, что встречаются в других поданных записях**, и
|
||||||
|
говори об этом в границах покрытия.
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
Дом — `shared/language.md` в репозитории плагинов, и там же объяснено, зачем
|
||||||
|
стиль вообще нужен. Здесь только то, что нужно тебе для работы.
|
||||||
|
|
||||||
|
<!-- копия: язык-правила из shared/language.md -->
|
||||||
|
|
||||||
|
У каждого правила названа причина: она же говорит, где правило **не**
|
||||||
|
применяется.
|
||||||
|
|
||||||
|
1. **Глагол вместо отглагольного существительного, активный залог.** «Обработчик
|
||||||
|
не проверяет владельца», а не «проверка владельца не осуществляется»;
|
||||||
|
«скрипт переписывает индекс», а не «индекс переписывается скриптом».
|
||||||
|
Отглагольное существительное прячет того, кто действует, — а в техническом
|
||||||
|
тексте важен именно он. Страдательный залог **остаётся**, когда деятель
|
||||||
|
неизвестен или неважен: «файл удаляется» верно, если удаляет любая из трёх
|
||||||
|
команд.
|
||||||
|
|
||||||
|
2. **Факт вместо оценки.** «Время ответа доходит до 800 мс», а не «работает
|
||||||
|
медленно»; «тело 40 МиБ держит блокировку 5 секунд», а не «большие тела
|
||||||
|
тормозят». Оценка допустима, когда факт стоит рядом, в той же фразе. Без
|
||||||
|
факта это настроение, а не сведение, — и находка тем ценнее, что оценку
|
||||||
|
потом не проверить.
|
||||||
|
|
||||||
|
3. **Стоп-слова.** Канцелярит (является, осуществляется, в целях, в рамках,
|
||||||
|
данный, вышеуказанный), вводные-паразиты (в общем, как известно, стоит
|
||||||
|
отметить), усилители (очень, крайне, достаточно, абсолютно, максимально),
|
||||||
|
синонимы одного качества («понятный и простой»), неопределённое
|
||||||
|
(соответствующий, определённый, некоторый).
|
||||||
|
|
||||||
|
Проверка одна: **вычеркни слово — смысл изменился, оставляй.** И осторожно с
|
||||||
|
вводными: «если», «иначе», «при таком-то условии», «в отличие от» несут
|
||||||
|
условие и противопоставление, то есть сведения, — их не трогают.
|
||||||
|
|
||||||
|
4. **Одна мысль — одно предложение.** Предложение с двумя независимыми
|
||||||
|
утверждениями делится. **Причинную связь не режут**: «поэтому», «иначе», «раз
|
||||||
|
так» — смысл, а не длина; рубленые фразы ради краткости тут вредят.
|
||||||
|
|
||||||
|
**Поля меты не делятся.** «Зачем» в мете задачи по формату — одно
|
||||||
|
предложение: оно повторяется строкой индекса, и второму там не поместиться.
|
||||||
|
Тесно — сокращают, но не делят. То же с любым полем вида `- **Имя:** …`.
|
||||||
|
|
||||||
|
5. **Англицизм, у которого есть живое русское слово, заменяется.**
|
||||||
|
|
||||||
|
| Калька | Русский аналог |
|
||||||
|
| --- | --- |
|
||||||
|
| флоу | поток, процесс, сценарий |
|
||||||
|
| фикс, зафиксить | исправление, исправить, починить |
|
||||||
|
| чекать | проверять |
|
||||||
|
| апрув, заапрувить | согласование, согласовать |
|
||||||
|
| best-effort | по возможности |
|
||||||
|
| кейс | случай, сценарий |
|
||||||
|
| перформанс | производительность |
|
||||||
|
| матчинг, смэтчить | сопоставление, сопоставить |
|
||||||
|
| зарелизить | выпустить, выложить |
|
||||||
|
| отрефакторить | переписать, разделить, убрать второй путь |
|
||||||
|
|
||||||
|
Насильно не переводится то, что является **именем вещи**: термины технологий
|
||||||
|
и протоколов (`SQL`, `API`, `CSV`, `N+1`, `IDOR`), имена классов, методов,
|
||||||
|
полей, таблиц и команд, слаг, а также термин, у которого нет точного русского
|
||||||
|
эквивалента и который в команде уже прижился.
|
||||||
|
|
||||||
|
Цель — простой и точный текст, а не пуризм. Русский аналог звучит коряво или
|
||||||
|
искажает смысл — остаётся термин.
|
||||||
|
|
||||||
|
6. **Слово из своего словаря не трогается — список закрыт.** Оговорка «термин
|
||||||
|
прижился» без списка проверяема на глаз и потому не проверяема: прижившимся
|
||||||
|
выглядит любое слово, встреченное трижды.
|
||||||
|
|
||||||
|
| Термин | Что называет |
|
||||||
|
| --- | --- |
|
||||||
|
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
|
||||||
|
| триаж | стадия конвейера, сводящая находки в решение |
|
||||||
|
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
|
||||||
|
| дедуп, дедупликация | сверка нового против уже лежащего |
|
||||||
|
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
|
||||||
|
| дифф, `--base` | разница между состояниями в git |
|
||||||
|
| промпт | текст, которым зовут модель |
|
||||||
|
| change, capability, spec | сущности OpenSpec, имена вещей чужого инструмента |
|
||||||
|
| generative, applicative | роды проходов ревью, вводятся определением по месту |
|
||||||
|
|
||||||
|
**Список закрыт.** Слово не отсюда и не из таблицы имён вещей выше — находка,
|
||||||
|
а не «принятый стиль»: у него либо есть живой русский аналог, либо оно
|
||||||
|
требует ввода одной строкой при первом употреблении.
|
||||||
|
|
||||||
|
Отсюда же читается снятое. Эти слова из текстов убраны, и возвращать их не
|
||||||
|
надо: **конфляция** (смешение), **декорреляция** (разведённость, разведён с
|
||||||
|
кем-то), **непоймание** (почему не поймали), **эвал-сет** (проверочный
|
||||||
|
набор), **гайд** (руководство). Каждое было латинизмом или калькой при живом
|
||||||
|
русском слове, и каждое к моменту снятия жило в трёх-шести файлах разом — то
|
||||||
|
есть выглядело словарём, не будучи им.
|
||||||
|
|
||||||
|
7. **Жаргон и метафоры заменяются прямым называнием.** Автору образ понятен,
|
||||||
|
читателю — нет.
|
||||||
|
|
||||||
|
| Метафора-жаргон | Прямо |
|
||||||
|
| --- | --- |
|
||||||
|
| рычаг (кэша, отбора) | условие отбора, параметр |
|
||||||
|
| навешен не на тот счётчик | завязан не на тот счётчик |
|
||||||
|
| переширокий матчинг по имени | слишком грубое сопоставление по имени, слишком много слабых совпадений |
|
||||||
|
| костыль | временное решение, обходной путь — и в чём именно |
|
||||||
|
| просело, отвалилось | стало медленнее на столько-то, перестало отвечать |
|
||||||
|
|
||||||
|
Проверка: **фраза требует, чтобы читатель додумал образ, — заменяется
|
||||||
|
буквальным описанием того, что происходит.**
|
||||||
|
|
||||||
|
8. **Термин, которого нет в документах проекта, вводится одной строкой или не
|
||||||
|
употребляется.** Термин, не встречающийся ни в паспорте, ни в архитектуре, ни
|
||||||
|
в конвенциях, — свой словарь у отдельной записи, а это самый дешёвый способ
|
||||||
|
сделать беклог нечитаемым для того, кто вернётся к нему через квартал.
|
||||||
|
Заменять незнакомый термин догадкой нельзя: догадка о предметной области
|
||||||
|
дороже непонятного слова, потому что выглядит понятной.
|
||||||
|
|
||||||
|
**Слово, занятое в другом смысле, — то же нарушение.** Термин, который в
|
||||||
|
одном документе проекта значит одно, а здесь другое, ломает оба.
|
||||||
|
|
||||||
|
9. **Имя файла — английское слово по сути, а не транслит.** `queue-as-table`, а
|
||||||
|
не `ochered-tablicej`; `move-parse-strict`, а не `razbor-hoda`. Транслит
|
||||||
|
нечитаем тому, кто ищет по смыслу, и не сокращается, а имя стоит в ссылках,
|
||||||
|
коммитах и путях, которые набирают руками. Переименование — **перенос ссылок
|
||||||
|
одним проходом**, а не правка одного файла.
|
||||||
|
|
||||||
|
<!-- /копия: язык-правила -->
|
||||||
|
|
||||||
|
### Что из этих правил докладывается особым образом
|
||||||
|
|
||||||
|
**Правило 4, поля меты.** «Зачем» по формату — одно предложение, потому что
|
||||||
|
повторяется строкой индекса. Предложить разбить его надвое — находка **против**
|
||||||
|
формата, а не по нему; тесно — предлагай сокращение.
|
||||||
|
|
||||||
|
**Правило 8, неизвестный термин.** Своей догадки не подставляй — ты не знаешь
|
||||||
|
предметную область. Пиши «термин «X» не встречается ни в документах, ни в других
|
||||||
|
поданных записях — введи строкой или назови известным словом». Свой словарь у
|
||||||
|
отдельной задачи — самый дешёвый способ сделать беклог нечитаемым тому, кто
|
||||||
|
вернётся к нему через квартал.
|
||||||
|
|
||||||
|
**Правило 9, имя файла.** Кириллицу в слаге и не-kebab-case ловит `tasks.py` —
|
||||||
|
про них молчи. Твоё — **транслит**, потому что машина проверяет его эвристикой и
|
||||||
|
ловит не всё: `sostoyanie-partii` проходит мимо неё. Находка — готовый
|
||||||
|
английский слаг на замену плюс напоминание, что переименование это перенос
|
||||||
|
ссылок одним проходом, а не правка одного файла.
|
||||||
|
|
||||||
|
## Чего ты не проверяешь
|
||||||
|
|
||||||
|
Не своё бывает двух разных родов, и поступают с ними по-разному.
|
||||||
|
|
||||||
|
**Чужому подрядчику — строкой в границах покрытия.** Форма записи у `task-form`;
|
||||||
|
язык документов проекта у `doc-wording`; их согласованность между собой у
|
||||||
|
`doc-consistency`, соответствие коду у `doc-code-drift` — до записей эти двое не
|
||||||
|
доходят вовсе, но если ты открыл документ как словарь и увидел расхождение в нём
|
||||||
|
самом, оно их. Увидел — назови в конце одной строкой, чтобы находка не пропала,
|
||||||
|
но находкой не оформляй.
|
||||||
|
|
||||||
|
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (состав и
|
||||||
|
написание секций, теги, тег `question` при непустом разделе «Вопросы»,
|
||||||
|
согласованность файлов с индексами, битые ссылки), **не пиши даже строкой**: это
|
||||||
|
не потерянная находка, а уже проверенное. Повторять машинную проверку словами —
|
||||||
|
заводить второй дом для одного правила. Наличие разделов своего типа и число
|
||||||
|
критериев `check` только считает — поимённо их судит `tasks.py ready`, и это
|
||||||
|
тоже не твоя находка: твоя — язык того, что уже написано.
|
||||||
|
|
||||||
|
**Содержание работы**: нужна ли задача, верно ли выбрана цель, не крупна ли она,
|
||||||
|
достаточна ли декомпозиция. Это разбор, и его ведёт человек со скиллом `tasks`.
|
||||||
|
|
||||||
|
**Полезное действие, параллельность и работающий заголовок** — тоже не твои.
|
||||||
|
Они в доктрине языка, судит их человек: находка по ним требует увидеть текст
|
||||||
|
целиком, а не фразу.
|
||||||
|
|
||||||
|
## Порог вмешательства
|
||||||
|
|
||||||
|
<!-- копия: порог-правки из shared/language.md -->
|
||||||
|
|
||||||
|
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
||||||
|
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
||||||
|
перестают читать весь список, и вместе с ним пропадают настоящие находки.
|
||||||
|
Сомневаешься — не правь. Формулировка, которая просто **не твоя**, — не находка.
|
||||||
|
|
||||||
|
**Систематичность нарушения — не довод в его пользу.** Одна и та же ошибка в
|
||||||
|
пяти файлах не становится «принятым стилем»: чаще это значит, что правило не
|
||||||
|
применялось вовсе, — и находка тем важнее. «Так сделано везде» годится как
|
||||||
|
основание для **одной находки на весь набор** («правило N нарушено в пяти
|
||||||
|
записях, перечень: …»), но не как основание промолчать. Принятым считается
|
||||||
|
только то, что назвал зовущий или что записано в конвенциях проекта.
|
||||||
|
|
||||||
|
<!-- /копия: порог-правки -->
|
||||||
|
|
||||||
|
Одна запись может дать несколько находок, но каждое место правится один раз: не
|
||||||
|
предлагай два варианта на выбор, предлагай лучший.
|
||||||
|
|
||||||
|
## Доклад
|
||||||
|
|
||||||
|
<!-- копия: вычитка-доклад из shared/language.md -->
|
||||||
|
|
||||||
|
Находки по одной, в порядке важности: залог и оценки → жаргон и англицизмы →
|
||||||
|
стоп-слова. Первые меняют, **что** читатель понимает; последние — только сколько
|
||||||
|
он на это тратит.
|
||||||
|
|
||||||
|
```
|
||||||
|
<файл>
|
||||||
|
правило: <номер и короткое имя>
|
||||||
|
сейчас: <как написано>
|
||||||
|
предложение: <готовая формулировка, подставляемая как есть>
|
||||||
|
почему: <одна фраза>
|
||||||
|
```
|
||||||
|
|
||||||
|
В конце — **границы покрытия**: сколько файлов просмотрено из скольких, какие не
|
||||||
|
смотрел и почему, и по чему проверялись термины (документы проекта названы или
|
||||||
|
нет). Отчёт без этой строки читается как «всё вычитано», не сообщая, какая часть
|
||||||
|
осталась нетронутой. Туда же — строка «замечено не по моей части»; машинно
|
||||||
|
проверяемое в неё **не идёт**.
|
||||||
|
|
||||||
|
Ничего не нашёл — так и скажи одной строкой. Пустой доклад с границами покрытия
|
||||||
|
полезнее выдуманной находки.
|
||||||
|
|
||||||
|
<!-- /копия: вычитка-доклад -->
|
||||||
|
|
||||||
|
**Находку в заголовке или в «зачем» отмечай особо.** По ним запись выбирают, и
|
||||||
|
подставляются они командой, а не редактором: зовущий обязан показать
|
||||||
|
предложенное человеку вместе с тем, что было. Прочие правки в теле применяются
|
||||||
|
сразу.
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
---
|
||||||
|
name: groom
|
||||||
|
description: "Груминг беклога — интерактивный разбор, отвечающий на два вопроса: что сейчас самое важное и что перестало быть важным. Ответ записывается порядком строк в беклоге: первая строка — то, что делают следующим. Разбирает накопившиеся вопросы, переоценивает задачи порциями по 5–8 (сделано попутно, отменено решением, слилось с соседней, подешевело, разрослось, стало сырьём), закрывает отжившее с причиной и расставляет очередь. Использовать, когда просят разобрать беклог, расставить приоритеты, решить «что делать дальше», провести груминг или переоценку, а также когда вернулись к проекту после перерыва и надо понять, где остановились. Формат и содержимое записей — скилл tasks; выполнение задачи — конвейер проекта."
|
||||||
|
---
|
||||||
|
|
||||||
|
# Груминг: что важно, что перестало
|
||||||
|
|
||||||
|
Скилл отвечает на **два вопроса**, и всё, что не служит им, — не его работа:
|
||||||
|
|
||||||
|
1. **Что сейчас самое важное?**
|
||||||
|
2. **Что перестало быть важным?**
|
||||||
|
|
||||||
|
Ответ на оба **записывается порядком строк в беклоге**: первая строка секции —
|
||||||
|
то, что делают следующим; то, что перестало быть важным, из беклога уходит с
|
||||||
|
причиной. Приоритет — свойство очереди, а не задачи, и живёт он в индексе
|
||||||
|
(правило 4 скилла `tasks`). Груминг — единственное место, где очередь
|
||||||
|
назначается человеком.
|
||||||
|
|
||||||
|
**Скилл интерактивный.** Он не «приводит беклог в порядок» сам: суждение о
|
||||||
|
важности принадлежит человеку, и весь ход — это подготовленные развилки с
|
||||||
|
рекомендацией. Что решается фактом (сделано, отменено, дублируется), решается
|
||||||
|
без вопросов и показывается списком.
|
||||||
|
|
||||||
|
Форматом и содержимым записей владеет скилл `tasks` — груминг зовёт его
|
||||||
|
операции, а не правит файлы руками. Выполнением задачи — конвейер проекта.
|
||||||
|
|
||||||
|
## Три правила, из которых всё следует
|
||||||
|
|
||||||
|
1. **Порядок назначает человек, машина его не выводит.** Ни давность, ни тип, ни
|
||||||
|
число задач под целью приоритетом не являются. Единственное место в очереди,
|
||||||
|
назначенное не человеком, — конец секции у сырья, и оно из очереди изъято
|
||||||
|
(`tasks`, правило 4).
|
||||||
|
2. **Порция важнее охвата.** Тридцать задач за заход — это усталость и
|
||||||
|
штамповка: последние десять получат «оставить» не потому, что живы, а потому,
|
||||||
|
что разбор затянулся. Лучше две честные порции, чем один полный проход.
|
||||||
|
3. **Причина уезжает в запись.** Всё, что решено здесь, оставляет след:
|
||||||
|
`--reason` у закрытия и переноса, ответ в теле задачи, строка в докладе.
|
||||||
|
Решение, оставшееся в переписке, будет принято заново через месяц.
|
||||||
|
|
||||||
|
## Когда груминг созрел
|
||||||
|
|
||||||
|
**Зовёт человек.** Скилл сам себя не назначает, но обязан **напоминать**, и
|
||||||
|
признак наблюдаемый, а не календарный:
|
||||||
|
|
||||||
|
- в беклоге появились записи, которых человек ещё не видел (заведены интейком по
|
||||||
|
ходу работы, урожаем ревью, разбором находок);
|
||||||
|
- на верхних строках очереди есть задача с открытым вопросом — очередь
|
||||||
|
показывает то, что взять нельзя;
|
||||||
|
- `tasks.py check` печатает «готово к взятию: 0 из N» — брать сегодня нечего.
|
||||||
|
|
||||||
|
Порога в неделях нет намеренно: счётчик простоя пришлось бы вести руками, а
|
||||||
|
решает всё равно человек. Признак — **очередь перестала быть твоей**: взялся
|
||||||
|
перечитывать, почему эти задачи стоят в таком порядке, — пора.
|
||||||
|
|
||||||
|
## Вопрос, блокер, необратимое
|
||||||
|
|
||||||
|
| | Что это | Когда спрашиваем | Что останавливает |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| **Вопрос** | решение человека | на груминге, пачкой | взятие задачи в работу |
|
||||||
|
| **Блокер** | работа не может продолжаться ни одной задачей | немедленно | всё |
|
||||||
|
|
||||||
|
Право на **необратимое** — третье и отдельное: что именно необратимо, называет
|
||||||
|
`CLAUDE.md` проекта, и спрашивается оно всегда, независимо от того, когда был
|
||||||
|
последний груминг.
|
||||||
|
|
||||||
|
**Блокер определяется исходом, а не одновременностью.** Встали разом или
|
||||||
|
задачи выпадали по одной — если продолжать нечем, это блокер, и человек
|
||||||
|
спрашивается немедленно, а не ждёт ближайшего груминга.
|
||||||
|
|
||||||
|
**Отличать вопрос от застревания.** Правило про остаток принадлежит управлению
|
||||||
|
задачами: оно решает, **сделана задача или вышла**, а это исход планирования, не
|
||||||
|
исполнения. **Ниже канонический текст; конвейер проекта на него ссылается, а не
|
||||||
|
пересказывает** — два экземпляра одного правила разъезжаются, и разъезжаются
|
||||||
|
незаметно, потому что расхождение видно только на редком входе.
|
||||||
|
|
||||||
|
> Есть остаток, который доводится без ответа, — задача продолжается, вопрос
|
||||||
|
> записывается в файл. Остатка нет — задача возвращается в беклог.
|
||||||
|
|
||||||
|
С двумя оговорками, без которых тест ошибается:
|
||||||
|
|
||||||
|
> **Остаток, который материализует нерешённое** — записывает в хранилище,
|
||||||
|
> журнал, витрину **или наружу** состояние, зависящее от неотвеченного вопроса,
|
||||||
|
> — **не остаток**. Решение поднимается до начала записи: откатить запись
|
||||||
|
> дороже, чем подождать ответ, а иногда невозможно. «Наружу» — часть правила, а
|
||||||
|
> не пример: выкладка, публикация и отправка данных третьей стороне не
|
||||||
|
> откатываются тем более.
|
||||||
|
|
||||||
|
> **Пол для остатка:** остаток, из которого пропала польза, названная в «зачем», —
|
||||||
|
> это не сделанная задача, а вернувшаяся в беклог.
|
||||||
|
|
||||||
|
## Ход груминга
|
||||||
|
|
||||||
|
Четыре шага, и порядок — зависимость, а не список.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
check["tasks.py check (+ --fix)<br/>результат — строкой в доклад"]
|
||||||
|
s1["1. Осмотреться<br/>что накопилось, чего человек ещё не видел"]
|
||||||
|
s2["2. Разобрать вопросы<br/>пачкой, не больше трёх за раз"]
|
||||||
|
s3["3. Что перестало быть важным<br/>порциями по 5–8"]
|
||||||
|
s4["4. Что важно сейчас<br/>расставить порядок строк"]
|
||||||
|
|
||||||
|
check --> s1 --> s2 --> s3 --> s4
|
||||||
|
s2 -->|"неотвеченный вопрос → судим о важности вслепую"| s4
|
||||||
|
s3 -->|"без переоценки очередь строится из протухшего"| s4
|
||||||
|
```
|
||||||
|
|
||||||
|
Схема — **сводка**: процедура каждого шага в
|
||||||
|
[references/portions.md](references/portions.md), и при расхождении прав текст.
|
||||||
|
|
||||||
|
**1. Осмотреться.** `tasks.py check` (при дрейфе — `--fix`), затем показать
|
||||||
|
человеку текущую очередь: верхние строки каждой секции и что появилось с
|
||||||
|
прошлого раза. Это половина ответа на «что важно»: очередь, которую не видели,
|
||||||
|
обсуждать бессмысленно.
|
||||||
|
|
||||||
|
**2. Разобрать вопросы.** Вопрос — решение человека, и разбирается он **пачкой**,
|
||||||
|
а не по одному, как только возник: по одному это дёрганье, пачкой это груминг.
|
||||||
|
Вопрос на верхних строках очереди разбирается **вне очереди порции**: иначе
|
||||||
|
правило «задача с открытым вопросом в работу не берётся» создаёт стимул вопрос
|
||||||
|
не записывать, лишь бы не вычеркнуть задачу из ближайшей работы.
|
||||||
|
|
||||||
|
**3. Что перестало быть важным.** Порциями по 5–8. Сперва то, что решается
|
||||||
|
фактом и не требует ничьего суждения (сделано попутно, отменено решением,
|
||||||
|
дублируется, симптомы одного дефекта), затем то, что решает человек (жива ли,
|
||||||
|
та ли цель, задача ли это ещё).
|
||||||
|
|
||||||
|
**4. Что важно сейчас.** Расстановка порядка — `move --after <слаг>` и
|
||||||
|
`move --first`. Разбирается **не весь беклог, а верх очереди**: первые три-пять
|
||||||
|
строк каждой секции. Ниже пятой строки порядок всё равно перестаёт что-либо
|
||||||
|
значить — до них дойдут после следующего груминга, и очередь к тому времени
|
||||||
|
будет другой.
|
||||||
|
|
||||||
|
## Приоритет: как его расставляют
|
||||||
|
|
||||||
|
**Вопрос ставится сравнением, а не оценкой.** «Насколько важна эта задача» не
|
||||||
|
имеет проверяемого ответа; «что из этих двух делают раньше» — имеет. Поэтому
|
||||||
|
очередь строится попарно и сверху: что первое, что после него.
|
||||||
|
|
||||||
|
Доводы, которые принимаются:
|
||||||
|
|
||||||
|
- **что сломано сейчас** — работоспособность обгоняет развитие, и это не правило
|
||||||
|
вкуса: сломанное дорожает само;
|
||||||
|
- **что разблокирует остальное** — задача, после которой можно взять три другие,
|
||||||
|
стоит раньше любой из трёх;
|
||||||
|
- **что дешевеет от того, что сделано** — работа рядом с только что тронутым
|
||||||
|
кодом стоит меньше, чем та же работа через квартал;
|
||||||
|
- **что дорожает от ожидания** — данные копятся, миграция усложняется, внешний
|
||||||
|
срок приближается;
|
||||||
|
- **цель, которую человек назвал следующей.**
|
||||||
|
|
||||||
|
Довод **записывается причиной** (`move --after <слаг> --reason …`). Порядок без
|
||||||
|
причины — это порядок, который на следующем груминге назначат заново с нуля.
|
||||||
|
|
||||||
|
**Цель и приоритет — независимые оси.** Очередь может идти поперёк целей, и это
|
||||||
|
законно: задачи одной цели не обязаны стоять подряд. Но если под целью годами
|
||||||
|
ничего не поднимается наверх — это разговор про цель, а не про очередь, и он
|
||||||
|
идёт на шаге 3.
|
||||||
|
|
||||||
|
## Документы устаревают тем же ходом работы
|
||||||
|
|
||||||
|
Груминг судит **задачи**, а не документы, и агентов канона не зовёт: они
|
||||||
|
принадлежат плагину `av-dev-docs`, и когда их звать — решает он.
|
||||||
|
|
||||||
|
Но повод назвать это здесь есть: беклог и документы протухают от одного и того
|
||||||
|
же — от сделанной работы. Пришёл на груминг и видишь, что с прошлого раза сделан
|
||||||
|
десяток задач, — скажи строкой, что документы стоит сверить
|
||||||
|
(`av-dev-docs:healthcheck`), и иди дальше. Плагина в проекте нет — сверять нечем,
|
||||||
|
и это тоже строка.
|
||||||
|
|
||||||
|
## Интерактив
|
||||||
|
|
||||||
|
- Вопросы — через `AskUserQuestion`, **не больше трёх за раз**. Порция в 5–8
|
||||||
|
задач обычно даёт больше трёх суждений: веди несколько итераций по ≤3, а не по
|
||||||
|
одному вопросу на задачу и не одним перегруженным запросом.
|
||||||
|
- К каждому варианту — **предварительное суждение, рекомендация первым
|
||||||
|
вариантом**: «предлагаю выкинуть, потому что …». Возразить дешевле, чем судить
|
||||||
|
с нуля.
|
||||||
|
- Всё, что решается фактом, решай сам и показывай списком в докладе.
|
||||||
|
- **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось».
|
||||||
|
Между порциями — промежуточный доклад.
|
||||||
|
|
||||||
|
Примеры итераций, отбор порции, храповик на залежавшихся —
|
||||||
|
[references/portions.md](references/portions.md).
|
||||||
|
|
||||||
|
## Стимулы, которые процесс создаёт
|
||||||
|
|
||||||
|
Правило, которое можно обойти в свою пользу, будет обойдено.
|
||||||
|
|
||||||
|
**Приёмщик и исполнитель совпадают, и это надо назвать вслух.** Задачу закрывает
|
||||||
|
тот же агент, который её и сделал. Груминг приёмкой не занимается — отдельного
|
||||||
|
ритуала у неё нет, — и настоящих опор остаётся две:
|
||||||
|
|
||||||
|
- **независимый отчёт ревью** — артефакт, написанный не исполнителем; при
|
||||||
|
конвейере `av-dev-code` это отчёт триажа в
|
||||||
|
`openspec/changes/archive/<id>/review/` (до архивации — `changes/<id>/review/`);
|
||||||
|
- **`reopen <слаг> --reason`** — закрытие не окончательно. Заметил на груминге,
|
||||||
|
что закрытая задача сделана не тем, чем обещала, — возвращай, это штатная
|
||||||
|
операция, а не скандал. Индексы под git: `git log -p` по беклогу показывает,
|
||||||
|
что и когда закрыто, потому что закрытие коммитится отдельным коммитом учёта.
|
||||||
|
|
||||||
|
Известные обходы:
|
||||||
|
|
||||||
|
- **Не записать вопрос** на задаче, которую хочется поднять наверх очереди.
|
||||||
|
Защита: вопросы верхних строк разбираются вне очереди порции, шагом 2.
|
||||||
|
- **Оставить всё как есть.** Груминг, на котором ничего не сдвинулось и ничего
|
||||||
|
не закрылось, — это не «беклог в порядке», а не проведённый груминг. Защита:
|
||||||
|
задача из верхних строк, которую и этот заход оставляет без изменений, **либо
|
||||||
|
двигается, либо получает записанную причину**, почему её держат.
|
||||||
|
- **Расставить порядок молча**, без доводов: тогда через месяц он неотличим от
|
||||||
|
случайного. Защита: причина у каждого движения и строка доклада.
|
||||||
|
- **Разобрать много и мелко** вместо немногого и важного: тридцать полей гигиены
|
||||||
|
вместо трёх решений о важности. Защита: гигиена — работа скилла `tasks` и
|
||||||
|
побочный продукт здесь; доклад называет **решения**, а не правки.
|
||||||
|
|
||||||
|
## Слоты проекта
|
||||||
|
|
||||||
|
Груминг не знает ни языка, ни сборки, ни CI. Проект дописывает в `CLAUDE.md`:
|
||||||
|
|
||||||
|
1. **Что считается сломанным** — какая красная проверка обгоняет развитие.
|
||||||
|
Не названо — спрашиваем человека, а не решаем сами.
|
||||||
|
2. **Необратимое** — что спрашивается всегда (тот же слот, что у скилла `tasks`;
|
||||||
|
дом один).
|
||||||
|
3. **Ориентир по размеру порции**, если он замерялся. Умолчание — 5–8 задач, и
|
||||||
|
это **ориентир, а не закон**.
|
||||||
|
|
||||||
|
Числа проекта (сколько задач приходит за месяц, каков прирост беклога) — предмет
|
||||||
|
наблюдения человека, а не константы этого скилла.
|
||||||
|
|
||||||
|
## Доклад
|
||||||
|
|
||||||
|
- Что просмотрено: N из M, сколько порций, по какому признаку отобраны.
|
||||||
|
- Вопросы: разобрано N, из них отвечено без человека N, снято тегов N.
|
||||||
|
- **Что перестало быть важным**: удалено как реализованное (со ссылками), ушло
|
||||||
|
без реализации (с причинами), понижено до сырья, слито, сменило тип или цель.
|
||||||
|
- **Что важно сейчас**: верх очереди по каждой секции — слаги в порядке, и по
|
||||||
|
каждому движению довод одной строкой.
|
||||||
|
- **Границы покрытия**: сколько задач не трогали и какие именно секции, теги или
|
||||||
|
цели остались — иначе доклад читается как «беклог разобран».
|
||||||
|
- `tasks.py check` после правок — результат строкой.
|
||||||
|
|
||||||
|
## Чего этот скилл не делает
|
||||||
|
|
||||||
|
Не пишет код и не выполняет задачи. Не заводит и не переоформляет записи сам по
|
||||||
|
себе — формат и содержимое ведёт `tasks` (груминг зовёт его операции). Не решает
|
||||||
|
за человека, что важно: он готовит развилки и рекомендует. Не принимает
|
||||||
|
закрытые задачи отдельным ритуалом — `reopen` есть, момента у него нет. Не судит
|
||||||
|
документы проекта — это плагин `av-dev-docs`.
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
# Порции, разбор и расстановка
|
||||||
|
|
||||||
|
Процедура шагов 2–4 груминга. Рамка и правила — [SKILL.md](../SKILL.md).
|
||||||
|
|
||||||
|
Начинается всё с `tasks.py check` (и `check --fix`, если дрейф накопился) —
|
||||||
|
результат идёт строкой в доклад.
|
||||||
|
|
||||||
|
## Шаг 2. Разбор вопросов
|
||||||
|
|
||||||
|
`tasks.py list --questions` — всё, что накопилось. Порядок по каждому вопросу:
|
||||||
|
|
||||||
|
1. **Проверь, не отвечен ли он уже** — решением, документом, соседним
|
||||||
|
изменением, самим ходом сделанной с тех пор работы. Отвеченный вопрос не
|
||||||
|
выносится человеку: это самая частая находка и она не требует ничьего
|
||||||
|
решения.
|
||||||
|
2. **Сформулируй развилку** с вариантами и последствием каждого, рекомендация —
|
||||||
|
первым вариантом.
|
||||||
|
3. **Вынеси пачкой** через `AskUserQuestion`, не больше трёх за раз.
|
||||||
|
4. **Запиши ответ в тело задачи, опустоши раздел «Вопросы»**, сними тег
|
||||||
|
(`edit <slug> --rm-tag question`), **перепиши «зачем»**: «Решено: …» на вопрос
|
||||||
|
«почему это лежит в беклоге» уже не отвечает. Опустошение раздела — не
|
||||||
|
уборка, а условие взятия: правило и причина в скилле `tasks`,
|
||||||
|
[references/task-format.md](../../tasks/references/task-format.md).
|
||||||
|
|
||||||
|
**Вопросы на верхних строках очереди разбираются вне очереди порции** — здесь
|
||||||
|
же, даже если сама задача в порцию переоценки не попала. Иначе правило «задача с
|
||||||
|
открытым вопросом в работу не берётся» создаёт стимул вопрос не записывать, лишь
|
||||||
|
бы не вычеркнуть задачу из ближайшей работы.
|
||||||
|
|
||||||
|
## Шаг 3. Что перестало быть важным
|
||||||
|
|
||||||
|
Цель — выкинуть то, что перестало быть задачей, и вернуть остальному честное
|
||||||
|
состояние. Не «пересмотреть всё», а «пересмотреть порцию до конца».
|
||||||
|
|
||||||
|
### Порция и правило остановки
|
||||||
|
|
||||||
|
- **5–8 задач за порцию.** Размер обоснован усталостью, а не пропускной
|
||||||
|
способностью, и менять его не надо — **надо брать несколько порций**.
|
||||||
|
- **Отбор порций по порядку:**
|
||||||
|
1. **свежее** — заведённое с прошлого груминга: оно ещё не проходило ни одной
|
||||||
|
проверки на нужность. Свежесть меряется git'ом, как и залежалость, — датой
|
||||||
|
появления файла в истории;
|
||||||
|
2. дальше **по залежалости** — `list --stale`;
|
||||||
|
3. по потребности — одна секция целиком, один тег (партия ревью), одна цель
|
||||||
|
(`--goal`), список от человека.
|
||||||
|
- **Останавливайся на границе порции**, даже если «ещё чуть-чуть осталось».
|
||||||
|
Между порциями — промежуточный доклад.
|
||||||
|
|
||||||
|
### Что делать с каждой задачей
|
||||||
|
|
||||||
|
Сперва то, что не требует ничьего решения:
|
||||||
|
|
||||||
|
1. **Проверь, не сделано ли уже.** Задача, реализованная попутно в соседнем
|
||||||
|
изменении, — самая частая находка. Смотри код, документацию, историю коммитов
|
||||||
|
по ключевым словам. Удаление «как реализованной» деструктивно и без следа
|
||||||
|
(в `REJECTED.md` реализованные не пишутся), поэтому порог улики жёсткий:
|
||||||
|
`close <slug> --implemented` только имея **конкретный коммит или строку
|
||||||
|
документа**, закрывающие задачу, и ссылка идёт в доклад. Есть лишь косвенные
|
||||||
|
признаки — не удаляй сам, вынеси в пачку вопросов. Сделана частично → задача
|
||||||
|
сжимается до остатка: тело правишь редактором, заголовок и «зачем» — через
|
||||||
|
`edit`.
|
||||||
|
2. **Проверь, не отменена ли решением.** Документ, ADR или архивное изменение
|
||||||
|
мог закрыть вопрос иначе — тогда `close <slug> --reason "<ссылка на
|
||||||
|
решение>"`. Задача закрывается не только коммитом.
|
||||||
|
3. **Проверь пересечения.** Две задачи об одном — содержимое в одну, вторую
|
||||||
|
`close <slug> --reason "слита с <другой-слаг>"`. Смотри **шире порции**:
|
||||||
|
интейк дедуплицирует новое против существующего, но никогда не пересматривает
|
||||||
|
уже лежащее, и две задачи с одной причиной могут лежать рядом месяцами.
|
||||||
|
4. **Пере-кластеризуй по общей причине.** Несколько задач, оказавшихся симптомами
|
||||||
|
одного дефекта, сливаются в одну — это находка, которую интейк дать не мог.
|
||||||
|
5. **Гигиена полей** — протухшее «зачем», вопрос в прозе, снятый ответ, свойство
|
||||||
|
репозитория в рамках, предписание процесса в теле, тип, разошедшийся с
|
||||||
|
задачей, границы вместо реализации в разделе «Затрагивает». Список и правила —
|
||||||
|
в скилле `tasks`. **Груминг — то самое место, где беклог добирает тип и
|
||||||
|
разделы его схемы:** требовать их на входе значило бы выгонять в заметки то,
|
||||||
|
что должно лежать задачей, а к взятию в работу они уже обязательны (`ready`).
|
||||||
|
Блок здоровья `check` печатает, сколько записей готово к взятию, — по этому
|
||||||
|
числу и видно, добрал ли груминг.
|
||||||
|
|
||||||
|
Гигиена — **побочный продукт, а не предмет**. Тридцать полей вместо трёх
|
||||||
|
решений о важности означают, что груминг не состоялся.
|
||||||
|
|
||||||
|
Затем — то, что решает человек:
|
||||||
|
|
||||||
|
6. **Жива ли она вообще.** Контекст мог измениться: ушла зависимость, отпал
|
||||||
|
сценарий, обошли иначе. Здесь и звучит вопрос, выкидывать ли.
|
||||||
|
7. **Та ли цель — и нужна ли она вообще.** `feature`, которой не находится цель,
|
||||||
|
— кандидат на выход: новая возможность вне цели это возможность, которой никто
|
||||||
|
не заказывал. Операционной задаче (`fix`, `chore`, `research`) цель не нужна,
|
||||||
|
и выдумывать её здесь не надо.
|
||||||
|
|
||||||
|
**Отменяется и сама цель** — когда замысел оказался неверен, а не когда
|
||||||
|
задача выбрала не ту. Тогда порция расширяется до всех задач этой цели: каждую
|
||||||
|
либо закрыть своей причиной, либо перевесить на другую цель, и только потом
|
||||||
|
закрыть цель. Порядок и почему он такой —
|
||||||
|
[task-goal.md](../../tasks/references/task-goal.md#отменённая-цель--сперва-задачи-потом-цель).
|
||||||
|
8. **Задача ли это по-прежнему.** Не проходит `ready` по существу, а не по
|
||||||
|
недописанным разделам → `edit <slug> --type research` и опустошённый раздел
|
||||||
|
«Вопрос», то есть сырьё; дальше штурм. Разрослась → это несколько задач под
|
||||||
|
той же целью, дальше декомпозиция.
|
||||||
|
9. **Не подешевела ли она.** Сделанная с прошлого раза работа меняет цену
|
||||||
|
**других** задач: рядом с только что тронутым кодом та же работа стоит меньше.
|
||||||
|
Это довод и на шаге 4 — задача, внезапно подешевевшая, поднимается в очереди
|
||||||
|
не потому, что стала важнее, а потому, что окно открыто.
|
||||||
|
|
||||||
|
### Храповик на залежавшихся
|
||||||
|
|
||||||
|
Сильно залежавшаяся задача — сигнал сама по себе: её либо ни разу не собирались
|
||||||
|
делать, либо нечем взять. Измеряй наблюдаемым — датой последней правки из git
|
||||||
|
(`list --stale` ставит такие первыми); счётчик «сколько грумингов пережила»
|
||||||
|
нигде не хранится.
|
||||||
|
|
||||||
|
Задача из верхних строк `--stale`, которую и этот заход оставляет без изменений,
|
||||||
|
**либо двигается (меняет цель, поднимается в очереди, уходит с причиной), либо
|
||||||
|
остаётся с явно записанной причиной**, почему её держим (`move <slug> --reason
|
||||||
|
…` — без `--section` секция берётся текущая). Молчаливое «оставить как есть» на
|
||||||
|
давно неподвижной задаче — это решение не принимать решение; запись причины
|
||||||
|
превращает его в осознанное и не даёт тому же вопросу всплыть на следующем
|
||||||
|
груминге.
|
||||||
|
|
||||||
|
## Шаг 4. Что важно сейчас — расстановка
|
||||||
|
|
||||||
|
Разбирается **верх очереди**, а не весь беклог: первые три-пять строк каждой
|
||||||
|
секции. Ниже пятой строки порядок всё равно перестаёт что-либо значить.
|
||||||
|
|
||||||
|
1. **Покажи текущий верх** — `list --index backlog`, по секциям, в том порядке,
|
||||||
|
в каком строки лежат. Плюс состояние проекта из роадмапа: секция `Готово`
|
||||||
|
отвечает на «где мы», `Запланировано` — на «куда шли».
|
||||||
|
2. **Спрашивай сравнением, а не оценкой.** «Что из этих двух делают раньше»
|
||||||
|
имеет проверяемый ответ, «насколько важна эта задача» — нет. Веди попарно и
|
||||||
|
сверху: что первое, что после него.
|
||||||
|
3. **Двигай командой, с причиной** — `move <slug> --after <другой> --reason …`
|
||||||
|
или `move <slug> --first --reason …`. Довод берётся из перечня в
|
||||||
|
[SKILL.md](../SKILL.md#приоритет-как-его-расставляют): сломано сейчас,
|
||||||
|
разблокирует остальное, дешевеет от сделанного, дорожает от ожидания,
|
||||||
|
названная цель.
|
||||||
|
4. **Проверь верх на готовность** — `tasks.py ready <слаг> …` по первым строкам.
|
||||||
|
Задача, стоящая первой и не проходящая `ready`, — это очередь, которая врёт:
|
||||||
|
взять её нельзя. Либо дописывается здесь же, либо уступает место.
|
||||||
|
|
||||||
|
Пример одной итерации:
|
||||||
|
|
||||||
|
> **Верх секции «Игра», сейчас в таком порядке:**
|
||||||
|
> `board-render-once` · `draw-before-full-board` · `move-parse-strict`
|
||||||
|
>
|
||||||
|
> 1. Что делаем первым?
|
||||||
|
> - `draw-before-full-board` *(рекомендую)* — ничья объявляется на неполном
|
||||||
|
> поле: игра врёт о результате, это сломано сейчас
|
||||||
|
> - `board-render-once` — печать поля дублируется; мешает всякой правке
|
||||||
|
> отрисовки, то есть разблокирует остальное
|
||||||
|
> - оставить как есть
|
||||||
|
> 2. `move-parse-strict` — третьей или выше?
|
||||||
|
> - Оставить третьей *(рекомендую)* — ошибка ввода видна игроку сразу
|
||||||
|
> - Поднять второй: тот же разбор трогает `board-render-once`, окно открыто
|
||||||
|
|
||||||
|
Каждый вариант несёт причину — ту самую, что уедет в `--reason`.
|
||||||
|
|
||||||
|
## Что делать, если разбирать нечего
|
||||||
|
|
||||||
|
Беклог пуст или в нём три задачи и все живые — груминг кончается за минуту, и
|
||||||
|
это законный исход. Скажи строкой: очередь такая-то, сдвигать нечего. Придумывать
|
||||||
|
работу, чтобы груминг «состоялся», — ровно тот ритуал без выгоды, от которого
|
||||||
|
процесс избавлялся.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: tasks
|
name: tasks
|
||||||
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Ритуал между спринтами — скилл session. Не реализует задачи — этим занимается пайплайн проекта.
|
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Расстановка приоритетов и разбор накопившегося — скилл groom. Не реализует задачи — этим занимается скилл решения задачи.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Задачи
|
# Задачи
|
||||||
@@ -9,9 +9,9 @@ description: Ведение задач и целей как каталога mar
|
|||||||
строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**:
|
строка **ровно в одном** индексе. Скилл владеет **форматом и содержимым**:
|
||||||
заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё.
|
заводит, редактирует, закрывает, разбирает находки ревью, дробит, штурмует сырьё.
|
||||||
|
|
||||||
Чем он **не** владеет: ритуалом между спринтами (разбор вопросов → разбор
|
Чем он **не** владеет: **очередью** — что делать следующим и что перестало быть
|
||||||
прошедшего спринта → переоценка → выбор цели и набор) — это скилл `session`; и
|
важным, решает скилл `groom`, а этот скилл лишь даёт ему операции; и выполнением
|
||||||
выполнением задачи — это пайплайн проекта.
|
задачи — это конвейер проекта.
|
||||||
|
|
||||||
## Шесть правил, из которых всё следует
|
## Шесть правил, из которых всё следует
|
||||||
|
|
||||||
@@ -35,47 +35,60 @@ description: Ведение задач и целей как каталога mar
|
|||||||
Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь
|
Поэтому **«зачем» живёт в мете файла**, а строка индекса его лишь
|
||||||
повторяет: пока поле лежало только в индексе, восстановление пропавшей
|
повторяет: пока поле лежало только в индексе, восстановление пропавшей
|
||||||
строки теряло его молча и навсегда. Единственное исключение намеренное: **в каком
|
строки теряло его молча и навсегда. Единственное исключение намеренное: **в каком
|
||||||
индексе лежит задача, знают индексы** — «в спринте» это свойство спринта, а
|
индексе лежит запись, знают индексы** — поля-состояния в файле нет. И
|
||||||
не файла, поля-состояния нет.
|
**порядок строк в беклоге**: приоритет это свойство очереди, а не задачи, и в
|
||||||
|
файле ему места нет (правило 4).
|
||||||
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
|
3. **Причина переживает запись.** Выкинутая без причины задача вернётся через
|
||||||
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
|
квартал тем же текстом. Реализованная оставляет след в коммите — выкинутая не
|
||||||
оставляет ничего, поэтому у неё есть `REJECTED.md`.
|
оставляет ничего, поэтому у неё есть `REJECTED.md`.
|
||||||
4. **Порядка нет, есть цель — но цель есть не у всякой задачи.** Приоритетов,
|
4. **Приоритет — это порядок строк, а цель есть не у всякой задачи.** Очередь
|
||||||
«повысить» и «встать раньше» нет: «что делать дальше» отвечает набор спринта,
|
внутри секции беклога значима: **первая строка — то, что делают следующим**.
|
||||||
а между спринтами порядок не нужен никому. Цель обязательна там, где она и
|
Приоритет назначает человек на груминге, машина его не выводит и не угадывает.
|
||||||
есть содержание работы, — у **новой возможности** (`feature`). Починка,
|
|
||||||
техдолг и разведка служат работоспособности, а не направлению, и живут без
|
|
||||||
цели законно; в набор спринта они входят помимо его цели. Придуманная им цель
|
|
||||||
— то же враньё, от которого спасает тип.
|
|
||||||
|
|
||||||
Единственный порядок, который в беклоге всё-таки есть, **производен от типа**,
|
Прежде здесь стояло «порядка нет, есть цель», и обосновано это было тем, что
|
||||||
а не назначен человеком: **сырьё** (`research` без раздела «Вопрос») стоит в
|
на «что делать дальше» отвечает **набор спринта**. Набора больше нет, а
|
||||||
конце своей категории. Его не берут, и между берущимся оно каждый раз требует
|
вопрос остался — и без порядка отвечать на него стало нечем.
|
||||||
открыть файл, чтобы это понять. Раз порядок выводится, его проверяет машина —
|
|
||||||
и приоритетом он не становится.
|
**Дом приоритета — индекс, а не файл.** Это то же исключение из правила 2,
|
||||||
|
что и «в каком индексе лежит запись»: приоритет — свойство очереди. Положи он
|
||||||
|
в файл числом, и два соседних файла смогли бы утверждать одно и то же место,
|
||||||
|
а строка индекса — противоречить обоим.
|
||||||
|
|
||||||
|
Цель обязательна там, где она и есть содержание работы, — у **новой
|
||||||
|
возможности** (`feature`). Починка, техдолг и разведка служат
|
||||||
|
работоспособности, а не направлению, и живут без цели законно. Придуманная им
|
||||||
|
цель — то же враньё, от которого спасает тип. **Цель и приоритет —
|
||||||
|
независимые оси:** очередь может идти поперёк целей, и это законно.
|
||||||
|
|
||||||
|
Одно место в очереди назначено **не человеком, а типом**: **сырьё**
|
||||||
|
(`research` без раздела «Вопрос») стоит в конце своей категории. Его не берут,
|
||||||
|
и между берущимся оно каждый раз требует открыть файл, чтобы это понять. Раз
|
||||||
|
это выводится, проверяет и чинит это машина.
|
||||||
5. **Тип решает, что с записью можно делать.** Тип — единственная ось и первое
|
5. **Тип решает, что с записью можно делать.** Тип — единственная ось и первое
|
||||||
поле меты: от него зависит, какие разделы обязательны в теле, нужна ли цель,
|
поле меты: от него зависит, какие разделы обязательны в теле, нужна ли цель,
|
||||||
берётся ли запись в спринт и в каком индексе живёт её строка. Словарь закрыт;
|
берётся ли запись в работу и в каком индексе живёт её строка. Словарь закрыт;
|
||||||
ни один тип не подошёл — значит, в записи их два, и её надо разделить.
|
ни один тип не подошёл — значит, в записи их два, и её надо разделить.
|
||||||
|
|
||||||
## Раскладка
|
## Раскладка
|
||||||
|
|
||||||
Каталог задач — **`docs/tasks`, жёстко**: это часть
|
Каталог задач — **`tasks/` в корне репозитория, жёстко.** Он принадлежит этому
|
||||||
[канона документов](../canon/references/canon.md), и подгоняется под него
|
плагину, а не канону документов: `docs/` ведёт другой плагин (`av-dev-docs`), и
|
||||||
проект, а не наоборот.
|
проект, поставивший учёт работ без него, каталога `docs/` не имеет вовсе. Внутри
|
||||||
|
`docs/` задачи лежали до версии канона 11; непереехавший проект скрипт
|
||||||
|
по-прежнему находит, но новый заводит только в корне.
|
||||||
|
|
||||||
```
|
```
|
||||||
docs/tasks/
|
tasks/
|
||||||
items/ задачи и цели файлами, <slug>.md, слаги английские
|
items/ задачи и цели файлами, <slug>.md, слаги английские
|
||||||
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет
|
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет
|
||||||
BACKLOG.md что можно взять — только задачи, целей здесь нет
|
BACKLOG.md что можно взять — только задачи, целей здесь нет.
|
||||||
SPRINT.md текущий спринт: цель (или её отсутствие), набор, дата
|
Порядок строк в секции значим: это очередь
|
||||||
REJECTED.md ушедшее БЕЗ реализации, с причиной и датой
|
REJECTED.md ушедшее БЕЗ реализации, с причиной и датой
|
||||||
```
|
```
|
||||||
|
|
||||||
Правило, снимающее путаницу: **`BACKLOG.md` — то, что берут; `ROADMAP.md` — то,
|
Правило, снимающее путаницу: **`BACKLOG.md` — то, что берут; `ROADMAP.md` — то,
|
||||||
подо что берут.** Цель в спринт взять нельзя, поэтому в списке берущихся ей не
|
подо что берут.** Цель в работу взять нельзя — берут её задачи, — поэтому в
|
||||||
место.
|
списке берущихся ей не место.
|
||||||
|
|
||||||
**Четыре секции роадмапа, и последняя отвечает на половину вопроса:**
|
**Четыре секции роадмапа, и последняя отвечает на половину вопроса:**
|
||||||
|
|
||||||
@@ -97,7 +110,7 @@ docs/tasks/
|
|||||||
роадмап, названный по-своему, читался бы только своим автором. Категории беклога
|
роадмап, названный по-своему, читался бы только своим автором. Категории беклога
|
||||||
(`Ядро`, `Инфра`) смысла не несут — это полки домена, и остаются делом проекта.
|
(`Ядро`, `Инфра`) смысла не несут — это полки домена, и остаются делом проекта.
|
||||||
Отсюда и разные имена поля меты: у цели **Секция** (часть роадмапа — состояние
|
Отсюда и разные имена поля меты: у цели **Секция** (часть роадмапа — состояние
|
||||||
очереди), у задачи **Категория** (полка, в которую она вернётся из спринта).
|
очереди), у задачи **Категория** (полка домена, на которой она лежит).
|
||||||
|
|
||||||
Отсюда четыре правила, которые проверяет `tasks.py check`: **состав закреплён**
|
Отсюда четыре правила, которые проверяет `tasks.py check`: **состав закреплён**
|
||||||
(чужая секция — ошибка, а не вольность), **все четыре обязаны быть** (нет
|
(чужая секция — ошибка, а не вольность), **все четыре обязаны быть** (нет
|
||||||
@@ -116,24 +129,31 @@ docs/tasks/
|
|||||||
называла слишком много: роадмап **весь** про разработку, и секция с таким именем
|
называла слишком много: роадмап **весь** про разработку, и секция с таким именем
|
||||||
не отличалась от остальных ничем.
|
не отличалась от остальных ничем.
|
||||||
|
|
||||||
**Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (спринт не может
|
**Секции «блокеры» в беклоге нет.** Блокер — это *состояние* (работа не может
|
||||||
продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и
|
продолжаться ни одной задачей), а не полка: он живёт ровно до ответа человека, и
|
||||||
записи в такой секции не успевают жить. Следы блокера остаются вопросами в
|
записи в такой секции не успевают жить. Следы блокера остаются вопросами в
|
||||||
файлах задач распущенного спринта. Постоянно пустая секция со старой семантикой
|
файлах задач. Постоянно пустая секция со старой семантикой
|
||||||
«разбираются пачками» противоречила бы правилу «блокер эскалируется немедленно»,
|
«разбираются пачками» противоречила бы правилу «блокер эскалируется немедленно»,
|
||||||
поэтому `init` её заводить отказывается, а `check` о ней говорит. **Проекту,
|
поэтому `init` её заводить отказывается, а `check` о ней говорит. **Проекту,
|
||||||
который переезжает с такой секцией, её надо удалить** — это единственное место,
|
который переезжает с такой секцией, её надо удалить** — это единственное место,
|
||||||
где это сказано.
|
где это сказано.
|
||||||
|
|
||||||
**Задача живёт в одном индексе за раз.** Взята в спринт — строка переезжает из
|
**Запись живёт в одном индексе за раз.** Индексов два, и выбирает между ними
|
||||||
`BACKLOG.md` в `SPRINT.md`; вышла — обратно. Файл в `items/` при этом **не
|
тип: цель в роадмапе, задача в беклоге. Сменился тип — строка переезжает
|
||||||
двигается**: он и есть запись, индексы лишь показывают, где она числится.
|
(`edit --type`). Файл в `items/` при этом **не двигается**: он и есть запись,
|
||||||
|
индексы лишь показывают, где она числится и в каком порядке стоит.
|
||||||
|
|
||||||
|
**Порядок строк в беклоге — приоритет**, и он единственное, чего в файле нет
|
||||||
|
(правило 4). Отсюда следствие для всякой машинной правки индекса:
|
||||||
|
восстановленная или перенесённая строка встаёт **в конец своей секции**, и
|
||||||
|
скрипт об этом говорит. Молчаливая вставка выдала бы машинную позицию за
|
||||||
|
решение человека — а решение это его.
|
||||||
|
|
||||||
**У сделанной задачи записи не остаётся** — файл и строка удаляются (`close
|
**У сделанной задачи записи не остаётся** — файл и строка удаляются (`close
|
||||||
--implemented`). Ей хватает коммита и документации проекта; вторая запись была
|
--implemented`). Ей хватает коммита и документации проекта; вторая запись была
|
||||||
бы вторым домом для того же факта. Вопрос «что было в спринте N» отвечается
|
бы вторым домом для того же факта. Вопрос «что было сделано и когда» отвечается
|
||||||
даром: `SPRINT.md` лежит под git, `git log -p docs/tasks/SPRINT.md` отдаёт историю
|
даром: индексы лежат под git, а закрытие коммитится отдельным коммитом учёта —
|
||||||
всех наборов без отдельного журнала.
|
`git log -p tasks/BACKLOG.md` отдаёт историю без отдельного журнала.
|
||||||
|
|
||||||
**У достигнутой цели запись остаётся, и это единственное исключение.** Файл
|
**У достигнутой цели запись остаётся, и это единственное исключение.** Файл
|
||||||
удаляется так же, а строка переезжает в секцию `Готово` с датой. Причина в том,
|
удаляется так же, а строка переезжает в секцию `Готово` с датой. Причина в том,
|
||||||
@@ -150,7 +170,6 @@ docs/tasks/
|
|||||||
stateDiagram-v2
|
stateDiagram-v2
|
||||||
state "BACKLOG.md — что берут" as B
|
state "BACKLOG.md — что берут" as B
|
||||||
state "ROADMAP.md — подо что берут" as P
|
state "ROADMAP.md — подо что берут" as P
|
||||||
state "SPRINT.md — набор спринта" as S
|
|
||||||
state "REJECTED.md — ушла без реализации" as R
|
state "REJECTED.md — ушла без реализации" as R
|
||||||
state "записи нет — реализована" as D
|
state "записи нет — реализована" as D
|
||||||
state "ROADMAP.md, «умеет» — цель достигнута" as A
|
state "ROADMAP.md, «умеет» — цель достигнута" as A
|
||||||
@@ -159,12 +178,9 @@ stateDiagram-v2
|
|||||||
[*] --> P: add --type goal
|
[*] --> P: add --type goal
|
||||||
B --> P: edit --type goal --section
|
B --> P: edit --type goal --section
|
||||||
P --> B: edit --type feature|fix|chore|research --section
|
P --> B: edit --type feature|fix|chore|research --section
|
||||||
B --> S: sprint take
|
B --> D: close --implemented
|
||||||
S --> B: sprint drop --reason
|
|
||||||
S --> D: close --implemented
|
|
||||||
P --> A: close --implemented
|
P --> A: close --implemented
|
||||||
B --> R: close --reason
|
B --> R: close --reason
|
||||||
S --> R: close --reason
|
|
||||||
P --> R: close --reason
|
P --> R: close --reason
|
||||||
D --> B: reopen --reason
|
D --> B: reopen --reason
|
||||||
R --> B: reopen --reason
|
R --> B: reopen --reason
|
||||||
@@ -194,7 +210,7 @@ stateDiagram-v2
|
|||||||
часть кода мы трогаем».
|
часть кода мы трогаем».
|
||||||
|
|
||||||
**Целью не становится работа, которой держат проект.** Состав перечислен
|
**Целью не становится работа, которой держат проект.** Состав перечислен
|
||||||
[в каноне](../canon/references/canon.md), раздел «Сопровождение и эксплуатация»;
|
[в словаре сопровождения](references/operations.md);
|
||||||
на вопрос «что приложение будет уметь» ничто из него не отвечает. Им отведена отдельная секция роадмапа,
|
на вопрос «что приложение будет уметь» ничто из него не отвечает. Им отведена отдельная секция роадмапа,
|
||||||
чтобы они были видны в том же экране и при этом не читались как возможности
|
чтобы они были видны в том же экране и при этом не читались как возможности
|
||||||
продукта.
|
продукта.
|
||||||
@@ -206,11 +222,12 @@ stateDiagram-v2
|
|||||||
секции отвечают на разные вопросы.
|
секции отвечают на разные вопросы.
|
||||||
|
|
||||||
**Сопровождение и эксплуатация — целое и часть**, а не синонимы, и та же тема
|
**Сопровождение и эксплуатация — целое и часть**, а не синонимы, и та же тема
|
||||||
живёт ещё в двух местах канона: разделе «Эксплуатация» в `architecture.md` и
|
живёт ещё в двух местах: разделе «Эксплуатация» в `architecture.md` и теме ревью
|
||||||
теме ревью `operations`. Словарь у всех трёх общий и живёт одним домом —
|
`operations`. Словарь у всех трёх общий, дом у него один — `shared/operations.md`
|
||||||
[canon.md](../canon/references/canon.md), раздел «Сопровождение и эксплуатация».
|
в репозитории плагинов, — а здесь лежит дословная копия:
|
||||||
Пересказывать его здесь нельзя: три перечня «чем держат проект» уже разъезжались
|
[references/operations.md](references/operations.md). Пересказывать его своими
|
||||||
на «метриках и логах» против «мониторинга».
|
словами нельзя: три перечня «чем держат проект» уже разъезжались на «метриках и
|
||||||
|
логах» против «мониторинга».
|
||||||
|
|
||||||
Секция выбирается так: очередь значима и обоснована прозой — `Запланировано`;
|
Секция выбирается так: очередь значима и обоснована прозой — `Запланировано`;
|
||||||
тянется долго и очереди не имеет — `Направления`; не про приложение, а про то,
|
тянется долго и очереди не имеет — `Направления`; не про приложение, а про то,
|
||||||
@@ -244,7 +261,7 @@ stateDiagram-v2
|
|||||||
**поле меты `Тип` первой строкой**; эмодзи в заголовке H1 от него производна, её
|
**поле меты `Тип` первой строкой**; эмодзи в заголовке H1 от него производна, её
|
||||||
ставит `add` и чинит `check --fix`.
|
ставит `add` и чинит `check --fix`.
|
||||||
|
|
||||||
| Тип | Обязательные разделы | Цель | В спринт | Устав |
|
| Тип | Обязательные разделы | Цель | В работу | Устав |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| 🎯 `goal` | `Завершение` | — | нет | [task-goal.md](references/task-goal.md) |
|
| 🎯 `goal` | `Завершение` | — | нет | [task-goal.md](references/task-goal.md) |
|
||||||
| ✨ `feature` | `Затрагивает`, `Критерии приёмки` | **обязательна** | да | [task-feature.md](references/task-feature.md) |
|
| ✨ `feature` | `Затрагивает`, `Критерии приёмки` | **обязательна** | да | [task-feature.md](references/task-feature.md) |
|
||||||
@@ -267,19 +284,19 @@ stateDiagram-v2
|
|||||||
незаполненности** — «первый, второй или третий вопрос теста готовности не
|
незаполненности** — «первый, второй или третий вопрос теста готовности не
|
||||||
отвечается», — а состояние типом быть не может: оно меняется по мере того, как
|
отвечается», — а состояние типом быть не может: оно меняется по мере того, как
|
||||||
запись дописывают, а тип меняют командой. Теперь это состояние называется честно:
|
запись дописывают, а тип меняют командой. Теперь это состояние называется честно:
|
||||||
`research` без раздела «Вопрос» — **сырьё**. В спринт не берётся ровно как
|
`research` без раздела «Вопрос» — **сырьё**. В работу не берётся ровно как
|
||||||
прежняя идея, лежит в конце своей категории и отбирается `list --raw`.
|
прежняя идея, лежит в конце своей категории и отбирается `list --raw`.
|
||||||
|
|
||||||
Словарь **закрыт**. Открытый разъедется на синонимах — `bug`, `bugfix`, `fix`,
|
Словарь **закрыт**. Открытый разъедется на синонимах — `bug`, `bugfix`, `fix`,
|
||||||
`defect`, — и отбор по типу перестанет отвечать на свой единственный вопрос. Ни
|
`defect`, — и отбор по типу перестанет отвечать на свой единственный вопрос. Ни
|
||||||
один тип не подходит — это сигнал, что в задаче их два и её надо разделить.
|
один тип не подходит — это сигнал, что в задаче их два и её надо разделить.
|
||||||
|
|
||||||
**Требуется тип там, где по нему принимают решение:** `sprint take` без типа
|
**Требуется тип там, где по нему принимают решение:** `ready` без типа
|
||||||
откажет, потому что не знает, каких разделов требовать. `check` о пропаже только
|
откажет, потому что не знает, каких разделов требовать. `check` о пропаже только
|
||||||
**напоминает** — беклог, заведённый до появления типа, законен, и переоформлять
|
**напоминает** — беклог, заведённый до появления типа, законен, и переоформлять
|
||||||
его «заодно» здесь не просят.
|
его «заодно» здесь не просят.
|
||||||
|
|
||||||
**Тип не выбирает метку ревью и вообще ничего не предписывает пайплайну.**
|
**Тип не выбирает метку ревью и вообще ничего не предписывает конвейеру.**
|
||||||
Профиль выбирается по факту изменения, а не по типу задачи: `chore` бывает
|
Профиль выбирается по факту изменения, а не по типу задачи: `chore` бывает
|
||||||
миграцией схемы, `fix` — правкой публичного контракта. Правило «предписание
|
миграцией схемы, `fix` — правкой публичного контракта. Правило «предписание
|
||||||
процесса в теле задачи снимается» типом не отменяется, а подтверждается: он
|
процесса в теле задачи снимается» типом не отменяется, а подтверждается: он
|
||||||
@@ -321,7 +338,7 @@ stateDiagram-v2
|
|||||||
делать, и какие границы это трогает: эндпоинт или команду, таблицу и миграцию,
|
делать, и какие границы это трогает: эндпоинт или команду, таблицу и миграцию,
|
||||||
формат на диске, публичный тип пакета, внешний сервис. Перечень живёт разделом
|
формат на диске, публичный тип пакета, внешний сервис. Перечень живёт разделом
|
||||||
«Затрагивает» (форма — [references/task-format.md](references/task-format.md)) и
|
«Затрагивает» (форма — [references/task-format.md](references/task-format.md)) и
|
||||||
требуется к взятию в спринт. Без него задача оценивается по объёму текста, а не
|
требуется к взятию в работу. Без него задача оценивается по объёму текста, а не
|
||||||
по объёму поверхности, — и оценка систематически занижена ровно там, где текст
|
по объёму поверхности, — и оценка систематически занижена ровно там, где текст
|
||||||
короткий, а границ много. Названы **границы**, а не то, как они изменятся: план
|
короткий, а границ много. Названы **границы**, а не то, как они изменятся: план
|
||||||
реализации живёт в предложении об изменении, а не в задаче.
|
реализации живёт в предложении об изменении, а не в задаче.
|
||||||
@@ -329,11 +346,12 @@ stateDiagram-v2
|
|||||||
**Предметно, но без усложнения.** Текст задачи читает человек, который решает,
|
**Предметно, но без усложнения.** Текст задачи читает человек, который решает,
|
||||||
брать её или нет, и делает это по строке индекса и одному экрану тела.
|
брать её или нет, и делает это по строке индекса и одному экрану тела.
|
||||||
|
|
||||||
Язык — общий для всех проектных текстов, и живёт он одним файлом:
|
Язык — общий для всех проектных текстов, и дом у него один,
|
||||||
[../canon/references/language.md](../canon/references/language.md)
|
`shared/language.md` в репозитории плагинов; здесь лежит дословная копия:
|
||||||
(информационный стиль, применённый к задачам и документам канона; там же таблицы
|
[references/language.md](references/language.md) (информационный стиль,
|
||||||
англицизмов и жаргона и то, что из стиля отброшено намеренно). Задаче он даёт
|
применённый к задачам и документам канона; там же таблицы англицизмов и жаргона
|
||||||
четыре требования, которые нарушаются чаще прочих:
|
и то, что из стиля отброшено намеренно). Задаче он даёт четыре требования,
|
||||||
|
которые нарушаются чаще прочих:
|
||||||
|
|
||||||
- **глагол вместо отглагольного существительного**: «обработчик не проверяет
|
- **глагол вместо отглагольного существительного**: «обработчик не проверяет
|
||||||
владельца», а не «проверка владельца не осуществляется»;
|
владельца», а не «проверка владельца не осуществляется»;
|
||||||
@@ -356,7 +374,7 @@ stateDiagram-v2
|
|||||||
## Инструмент (`tasks.py`)
|
## Инструмент (`tasks.py`)
|
||||||
|
|
||||||
Пусть `tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"`, а `D` —
|
Пусть `tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"`, а `D` —
|
||||||
`docs/tasks` от корня проекта. `--dir` стоит в примерах намеренно: вызов из
|
`tasks` от корня проекта. `--dir` стоит в примерах намеренно: вызов из
|
||||||
подкаталога — обычное дело.
|
подкаталога — обычное дело.
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -365,11 +383,11 @@ python3 $tk check --dir D --fix # + починить дрейф (тип
|
|||||||
python3 $tk list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--goal S] [--raw] [--index …] [--questions]
|
python3 $tk list --dir D [--stale] [--section S] [--type T] [--tag a,b] [--goal S] [--raw] [--index …] [--questions]
|
||||||
python3 $tk add --dir D --slug S --title T --type goal|feature|fix|chore|research [--section S] [--goal G] [--why «зачем»] [--tag a,b]
|
python3 $tk add --dir D --slug S --title T --type goal|feature|fix|chore|research [--section S] [--goal G] [--why «зачем»] [--tag a,b]
|
||||||
python3 $tk edit S --dir D [--title T] [--why «зачем»] [--type T] [--goal G] [--add-tag a,b] [--rm-tag c]
|
python3 $tk edit S --dir D [--title T] [--why «зачем»] [--type T] [--goal G] [--add-tag a,b] [--rm-tag c]
|
||||||
python3 $tk move S --dir D --section S [--reason R] [--after S | --first]
|
python3 $tk move S --dir D [--section S] [--reason R] [--after S | --first] # без --section — текущая секция
|
||||||
python3 $tk close S --dir D --reason R # в REJECTED.md + удалить (ушла без реализации)
|
python3 $tk close S --dir D --reason R # в REJECTED.md + удалить (ушла без реализации)
|
||||||
python3 $tk close S --dir D --implemented # просто удалить (реализована и закоммичена)
|
python3 $tk close S --dir D --implemented # просто удалить (реализована и закоммичена)
|
||||||
python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась
|
python3 $tk reopen S --dir D --reason R # вернуть закрытую: приёмка не сошлась
|
||||||
python3 $tk sprint start (--goal S | --no-goal) --dir D | take S… | drop S… --reason R | close [--dissolve --reason R]
|
python3 $tk ready S… --dir D # схема типа выполнена — можно брать в работу
|
||||||
python3 $tk init --dir D [--sections …] [--items …] [--backlog …] …
|
python3 $tk init --dir D [--sections …] [--items …] [--backlog …] …
|
||||||
python3 $tk adopt scan --from … | apply --plan … # разовая адаптация, references/adopt.md
|
python3 $tk adopt scan --from … | apply --plan … # разовая адаптация, references/adopt.md
|
||||||
```
|
```
|
||||||
@@ -393,7 +411,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
заголовке ставит скрипт.
|
заголовке ставит скрипт.
|
||||||
|
|
||||||
**Мутации правят файл и индексы заодно** — руками строку индекса или мету
|
**Мутации правят файл и индексы заодно** — руками строку индекса или мету
|
||||||
не пиши, зови `add`/`edit`/`move`/`close`/`sprint`. Смена заголовка, «зачем», типа,
|
не пиши, зови `add`/`edit`/`move`/`close`/`reopen`. Смена заголовка, «зачем», типа,
|
||||||
цели и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс
|
цели и **тегов** — это `edit`: он держит H1 (вместе с эмодзи), мету и индекс
|
||||||
согласованными. Снятие тега — `--rm-tag` (после ответа на вопрос снимается
|
согласованными. Снятие тега — `--rm-tag` (после ответа на вопрос снимается
|
||||||
`question`), смена цели — `--goal`, типа — `--type`; оба заменяют прежнее
|
`question`), смена цели — `--goal`, типа — `--type`; оба заменяют прежнее
|
||||||
@@ -406,7 +424,12 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
`move` двигает только внутри одного индекса и пишет причину. `--section` у
|
`move` двигает только внутри одного индекса и пишет причину. `--section` у
|
||||||
`edit` работает **только** при таком переезде — иначе он отсылает к `move`,
|
`edit` работает **только** при таком переезде — иначе он отсылает к `move`,
|
||||||
потому что смена секции без причины и есть тот дрейф, который потом никто не
|
потому что смена секции без причины и есть тот дрейф, который потом никто не
|
||||||
объяснит. Задача в наборе спринта тип не меняет вовсе: сперва `sprint drop`.
|
объяснит.
|
||||||
|
|
||||||
|
**`move --after <слаг>` и `move --first` — это и есть расстановка приоритета.**
|
||||||
|
Порядок строк в секции значим (правило 4), и двигают его только этой командой:
|
||||||
|
руками поправленная строка не оставляет причины, а причина здесь и есть половина
|
||||||
|
решения.
|
||||||
|
|
||||||
Тело задачи скрипт не трогает:
|
Тело задачи скрипт не трогает:
|
||||||
`add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь
|
`add` кладёт заголовок, мета-блок и шаблон с подсказками, тело дописываешь
|
||||||
@@ -435,9 +458,12 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
где по нему принимают решение. Такие записи идут в `НЕОДНОЗНАЧНО`, и тип им
|
где по нему принимают решение. Такие записи идут в `НЕОДНОЗНАЧНО`, и тип им
|
||||||
проставляет человек — `edit <слаг> --type …`.
|
проставляет человек — `edit <слаг> --type …`.
|
||||||
|
|
||||||
**Что механизировано, а что нет.** У задачи, взятой в набор (`sprint take` и
|
**Что механизировано, а что нет.** Схему типа проверяет `ready` на входе в
|
||||||
`check` по задачам спринта), проверяется схема её типа, и у каждой части своя
|
работу — там, где по ней принимают решение; `check` поимённо о ней не говорит, а
|
||||||
глубина:
|
считает: строка здоровья **«схема типа не выполнена: N из M»** называет число и
|
||||||
|
первые слаги, строка **«готово к взятию»** — сколько задач беклога пройдут
|
||||||
|
`ready` целиком (схема плюс цель плюс отсутствие открытого вопроса). Это две
|
||||||
|
разные строки, и совпадение их чисел — совпадение. У каждой части своя глубина:
|
||||||
|
|
||||||
- **тип** — жёстко: назван и из закрытого словаря;
|
- **тип** — жёстко: назван и из закрытого словаря;
|
||||||
- **критерии приёмки** (`feature`, `fix`, `chore`) — число пунктов жёстко
|
- **критерии приёмки** (`feature`, `fix`, `chore`) — число пунктов жёстко
|
||||||
@@ -514,7 +540,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
**одним проходом вместе с починкой перекрёстных ссылок**, а не по одному слагу.
|
**одним проходом вместе с починкой перекрёстных ссылок**, а не по одному слагу.
|
||||||
|
|
||||||
Если переводить надо не только задачи, а весь `docs/` — это скилл
|
Если переводить надо не только задачи, а весь `docs/` — это скилл
|
||||||
`av-dev-pm:canon`, и он зовёт этот сценарий сам на своём шаге.
|
`av-dev-docs:canon`, и он зовёт этот сценарий сам на своём шаге.
|
||||||
|
|
||||||
### Декомпозиция и штурм сырья
|
### Декомпозиция и штурм сырья
|
||||||
|
|
||||||
@@ -536,7 +562,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
| Проход | Что смотрит | Над чем работает |
|
| Проход | Что смотрит | Над чем работает |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `task-form` | заголовок по типу, «зачем» вместо пересказа, границы вместо замысла, годность оракулов, предписание процесса, связь со строкой «Завершения» цели | только `items/`, **открывает файл цели** |
|
| `task-form` | заголовок по типу, «зачем» вместо пересказа, границы вместо замысла, годность оракулов, предписание процесса, связь со строкой «Завершения» цели | только `items/`, **открывает файл цели** |
|
||||||
| `doc-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин | любой проектный текст, включая документы канона |
|
| `task-wording` | залог и отглагольные, оценка без факта, стоп-слова, англицизмы, жаргон, неизвестный термин, транслит в слаге | `items/` и строки индексов; документы проекта — только как словарь |
|
||||||
|
|
||||||
Разделены они не по охвату, а **по глубине**. Язык проверяется по словам и
|
Разделены они не по охвату, а **по глубине**. Язык проверяется по словам и
|
||||||
фразам, поштучно; форма записи требует понять, что задача делает, и открыть
|
фразам, поштучно; форма записи требует понять, что задача делает, и открыть
|
||||||
@@ -582,7 +608,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
мету файла и строку индекса заодно;
|
мету файла и строку индекса заодно;
|
||||||
- **вопрос, застрявший в прозе** — вынимается в раздел «Вопросы» плюс тег
|
- **вопрос, застрявший в прозе** — вынимается в раздел «Вопросы» плюс тег
|
||||||
`question` (`edit --add-tag question`), иначе он не виден ни `list
|
`question` (`edit --add-tag question`), иначе он не виден ни `list
|
||||||
--questions`, ни правилу «задача с открытым вопросом в набор не берётся»;
|
--questions`, ни правилу «задача с открытым вопросом в работу не берётся»;
|
||||||
- **тег, который некому снять** — `question` после ответа снимается `edit
|
- **тег, который некому снять** — `question` после ответа снимается `edit
|
||||||
--rm-tag question` вместе с записью ответа в тело **и опустошением раздела
|
--rm-tag question` вместе с записью ответа в тело **и опустошением раздела
|
||||||
«Вопросы»**: судит раздел, а не тег (`references/task-format.md`);
|
«Вопросы»**: судит раздел, а не тег (`references/task-format.md`);
|
||||||
@@ -598,7 +624,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
ровно там, где по нему отбирают, **и требует не тех разделов**: у брошенного
|
ровно там, где по нему отбирают, **и требует не тех разделов**: у брошенного
|
||||||
`fix` останется «Воспроизведение», которого нечем заполнить;
|
`fix` останется «Воспроизведение», которого нечем заполнить;
|
||||||
- **сырьё, у которого появился вопрос** — разведка обросла формулировкой, но
|
- **сырьё, у которого появился вопрос** — разведка обросла формулировкой, но
|
||||||
раздел «Вопрос» так и пуст: она числится сырьём и в спринт не берётся.
|
раздел «Вопрос» так и пуст: она числится сырьём и в работу не берётся.
|
||||||
Записывается вопрос, и `check --fix` поднимает строку из конца категории;
|
Записывается вопрос, и `check --fix` поднимает строку из конца категории;
|
||||||
- **границы, названные вместо реализации** — «переписать хранилище на новый
|
- **границы, названные вместо реализации** — «переписать хранилище на новый
|
||||||
драйвер» в разделе «Затрагивает» это не граница, а замысел. Границы —
|
драйвер» в разделе «Затрагивает» это не граница, а замысел. Границы —
|
||||||
@@ -615,28 +641,35 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
просто каталог markdown. Текст задач — русский (язык документации проекта);
|
просто каталог markdown. Текст задач — русский (язык документации проекта);
|
||||||
зашита только латиница слага. OpenSpec ему тоже не нужен.
|
зашита только латиница слага. OpenSpec ему тоже не нужен.
|
||||||
|
|
||||||
- **Каталог задач — `docs/tasks`, жёстко**, и `--dir` передаётся явно всегда:
|
- **Каталог задач — `tasks/` в корне, жёстко**, и `--dir` передаётся явно всегда:
|
||||||
раскладка канона одинакова во всех проектах, и искать больше нечего. Каталога
|
раскладка канона одинакова во всех проектах, и искать больше нечего. Каталога
|
||||||
нет — код 3 и вопрос человеку; `init` заводит его **только** когда проект
|
нет — код 3 и вопрос человеку; `init` заводит его **только** когда проект
|
||||||
действительно новый, а перевод чужой раскладки делает `av-dev-pm:canon`.
|
действительно новый, а перевод чужой раскладки делает `av-dev-docs:canon`.
|
||||||
У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и
|
У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и
|
||||||
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
|
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
|
||||||
- **Настройки живут в `docs/.pm.json`**, ключ `tasks`: **имена** файлов и
|
- **Настройки живут в `<каталог задач>/.tasks.json`** — свой файл у своего
|
||||||
заголовков, и только если они отличаются от умолчания. Один конфиг на весь
|
плагина: **имена** файлов и заголовков, и только если они отличаются от
|
||||||
канон, а не по одному на каталог. Неизвестный ключ — код 3 на любой команде,
|
умолчания. Неизвестный ключ — код 3 на любой команде, так что лишнее слово в
|
||||||
так что лишнее слово в этом объекте останавливает работу с задачами целиком.
|
этом объекте останавливает работу с задачами целиком.
|
||||||
|
|
||||||
|
Дом именно свой, а не `docs/.pm.json`, потому что `docs/` принадлежит плагину
|
||||||
|
канона: проект, поставивший учёт работ без него, каталога `docs/` не имеет
|
||||||
|
вовсе. Прежний ключ `tasks` в `docs/.pm.json` читается, **только когда своего
|
||||||
|
файла нет** — для проектов, заведённых до раскола плагинов; скрипт при этом
|
||||||
|
говорит замечанием, куда его перенести. Есть оба — побеждает свой, и об этом
|
||||||
|
тоже говорится вслух: молча выбранный из двух конфиг это дрейф.
|
||||||
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
||||||
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
|
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
|
||||||
второй список разошёлся бы с заголовками молча.
|
второй список разошёлся бы с заголовками молча.
|
||||||
|
|
||||||
### Вызов из другого плагина
|
### Вызов из другого плагина
|
||||||
|
|
||||||
`$CLAUDE_PLUGIN_ROOT` раскрывается **только внутри своего плагина**: пайплайн
|
`$CLAUDE_PLUGIN_ROOT` раскрывается **только внутри своего плагина**: конвейер
|
||||||
задачи, конвейер ревью и любой другой чужой контекст до `tasks.py` по этой
|
задачи, конвейер ревью и любой другой чужой контекст до `tasks.py` по этой
|
||||||
переменной не дотянутся. Мост — **вызов скилла через пространство имён**, а не
|
переменной не дотянутся. Мост — **вызов скилла через пространство имён**, а не
|
||||||
путь:
|
путь:
|
||||||
|
|
||||||
> Чужой контекст зовёт `Skill av-dev-pm:tasks` и называет, что нужно сделать
|
> Чужой контекст зовёт `Skill av-dev-tasks:tasks` и называет, что нужно сделать
|
||||||
> («закрой задачу `<слаг>`, реализована»). Скилл разрешает свой
|
> («закрой задачу `<слаг>`, реализована»). Скилл разрешает свой
|
||||||
> `$CLAUDE_PLUGIN_ROOT` сам. Путь наружу не выносится вовсе.
|
> `$CLAUDE_PLUGIN_ROOT` сам. Путь наружу не выносится вовсе.
|
||||||
|
|
||||||
@@ -651,8 +684,8 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
какие в проекте оракулы — семантика гейта в `CLAUDE.md`. Отдельными слотами
|
какие в проекте оракулы — семантика гейта в `CLAUDE.md`. Отдельными слотами
|
||||||
остаётся то, чего из раскладки не вывести. **Проект дописывает в `CLAUDE.md`**:
|
остаётся то, чего из раскладки не вывести. **Проект дописывает в `CLAUDE.md`**:
|
||||||
|
|
||||||
1. **Что такое «сделана»** — чем задача выполняется (пайплайн проекта) и что
|
1. **Что такое «сделана»** — чем задача выполняется (конвейер проекта) и что
|
||||||
входит в его определение готовности. Скилл требует лишь **форму**: пайплайн
|
входит в его определение сделанного. Скилл требует лишь **форму**: конвейер
|
||||||
проекта пройден + критерии приёмки проверены поимённо.
|
проекта пройден + критерии приёмки проверены поимённо.
|
||||||
2. **Что считается необратимым** и потому спрашивается у человека всегда
|
2. **Что считается необратимым** и потому спрашивается у человека всегда
|
||||||
(деплой, выкладка наружу, удаление или перезапись данных).
|
(деплой, выкладка наружу, удаление или перезапись данных).
|
||||||
@@ -680,6 +713,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
## Чего этот скилл не делает
|
## Чего этот скилл не делает
|
||||||
|
|
||||||
Не пишет код, не заводит спеки и предложения об изменении, не берёт задачу в
|
Не пишет код, не заводит спеки и предложения об изменении, не берёт задачу в
|
||||||
работу — этим занимается пайплайн проекта. Не ведёт спринт и не проводит сессию
|
работу — этим занимается конвейер проекта. **Не ведёт очередь:** что делать
|
||||||
между спринтами — это `session`. Не решает за пользователя, что важно. Не
|
следующим и что перестало быть важным — скилл `groom`, а этот даёт ему операции.
|
||||||
|
Не решает за пользователя, что важно. Не
|
||||||
переоформляет существующие задачи «заодно»: правится то, чего касается операция.
|
переоформляет существующие задачи «заодно»: правится то, чего касается операция.
|
||||||
+15
-12
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится**
|
Проект, где задачи уже как-то ведутся, и из имеющегося материала **выводится**
|
||||||
заполненный каталог задач: цели, задачи, кладбище, индексы. Операция разовая —
|
заполненный каталог задач: цели, задачи, кладбище, индексы. Операция разовая —
|
||||||
после неё проект живёт скиллами `tasks` и `session`.
|
после неё проект живёт скиллами `tasks` и `groom`.
|
||||||
|
|
||||||
**Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл
|
**Это часть приведения проекта к канону.** Раскладку `docs/` целиком ведёт скилл
|
||||||
`av-dev-pm:canon`; он же зовёт этот сценарий на шаге «каталог задач», потому что
|
`av-dev-docs:canon`; он же зовёт этот сценарий на шаге «каталог задач», потому что
|
||||||
форматом задач владеет `tasks`, а не `canon`. Отдельно сценарий вызывается,
|
форматом задач владеет `tasks`, а не `canon`. Отдельно сценарий вызывается,
|
||||||
когда переводить надо **только** задачи.
|
когда переводить надо **только** задачи.
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"
|
tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"
|
||||||
|
|
||||||
python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \
|
python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \
|
||||||
--target docs/tasks --out tasks-adopt-plan.json # только чтение
|
--target tasks --out tasks-adopt-plan.json # только чтение
|
||||||
python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
||||||
--refs docs openspec CLAUDE.md README.md # запись
|
--refs docs openspec CLAUDE.md README.md # запись
|
||||||
```
|
```
|
||||||
@@ -63,7 +63,7 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
|||||||
## Порядок
|
## Порядок
|
||||||
|
|
||||||
1. **Осмотрись.** Где лежат задачи, роадмап, заметки. Каталог задач по канону —
|
1. **Осмотрись.** Где лежат задачи, роадмап, заметки. Каталог задач по канону —
|
||||||
всегда `docs/tasks`. Секции беклога (`--sections`) — по умолчанию
|
всегда `tasks`. Секции беклога (`--sections`) — по умолчанию
|
||||||
`Ядро,Инфра`; если у проекта деление другое по существу, оно называется
|
`Ядро,Инфра`; если у проекта деление другое по существу, оно называется
|
||||||
здесь, а не подгоняется под умолчание, и становится **заголовками `##`
|
здесь, а не подгоняется под умолчание, и становится **заголовками `##`
|
||||||
индекса** — их единственным домом. В `docs/.pm.json` секции не пишутся.
|
индекса** — их единственным домом. В `docs/.pm.json` секции не пишутся.
|
||||||
@@ -94,22 +94,25 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
|||||||
быть названо, иначе следующий агент примет пустой беклог за поломку.
|
быть названо, иначе следующий агент примет пустой беклог за поломку.
|
||||||
|
|
||||||
`apply` печатает состояние по факту: сколько задач без цели (это **ошибки**
|
`apply` печатает состояние по факту: сколько задач без цели (это **ошибки**
|
||||||
`check`) и сколько без критериев (`check` их ошибкой не считает, но `sprint
|
`check`) и сколько не собрало разделы своего типа (для `check` это не ошибка, а
|
||||||
take` такую задачу не возьмёт). Закрывается это **порциями переоценки** — шаг 3
|
строка здоровья, но `ready` такую задачу не пропустит). Закрывается это
|
||||||
скилла `session`, 5–8 задач за порцию: проставить цели, превратить «готово,
|
**порциями груминга** — скилл
|
||||||
когда» в критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы».
|
`groom`, 5–8 задач за порцию: проставить цели, превратить «готово, когда» в
|
||||||
|
критерии с оракулами, вынуть вопросы из прозы в раздел «Вопросы». Там же
|
||||||
|
беклогу впервые назначается **порядок**: после адаптации его нет вовсе, а
|
||||||
|
очередь и есть то, ради чего каталог заводят.
|
||||||
|
|
||||||
Готовность к первому спринту — не «`check` зелёный», а «есть 2–5 критериев хотя
|
Готовность к первой задаче — не «`check` зелёный», а «`ready` пропускает хотя бы
|
||||||
бы у набора под одну цель».
|
верхние строки очереди».
|
||||||
|
|
||||||
## Чего адаптация не делает
|
## Чего адаптация не делает
|
||||||
|
|
||||||
- **Не удаляет источники.** Старый каталог остаётся на месте: сверить и убрать —
|
- **Не удаляет источники.** Старый каталог остаётся на месте: сверить и убрать —
|
||||||
дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда.
|
дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда.
|
||||||
- **Не переписывает подписи ссылок.** `[docs/backlog](docs/tasks/BACKLOG.md)` —
|
- **Не переписывает подписи ссылок.** `[docs/backlog](tasks/BACKLOG.md)` —
|
||||||
цель поправлена, текст остался; это правится глазами, и таких мест немного.
|
цель поправлена, текст остался; это правится глазами, и таких мест немного.
|
||||||
- **Не сочиняет критерии приёмки и не придумывает цели**, которых в материале
|
- **Не сочиняет критерии приёмки и не придумывает цели**, которых в материале
|
||||||
нет. Придуманная цель хуже отсутствующей: под неё соберут спринт.
|
нет. Придуманная цель хуже отсутствующей: под неё заведут задачи.
|
||||||
- **Не трогает историю.** В коммитах старые слаги остаются, и это нормально.
|
- **Не трогает историю.** В коммитах старые слаги остаются, и это нормально.
|
||||||
|
|
||||||
## Доклад
|
## Доклад
|
||||||
+30
-14
@@ -12,6 +12,12 @@
|
|||||||
не файл-на-находку.** Если у ревью был триаж — половина работы уже сделана, бери
|
не файл-на-находку.** Если у ревью был триаж — половина работы уже сделана, бери
|
||||||
его выход. Если нет — триажируй сам, прежде чем заводить.
|
его выход. Если нет — триажируй сам, прежде чем заводить.
|
||||||
|
|
||||||
|
**Штатный отправитель — `av-dev-code:review`** (и `av-dev-code:resolve`, который
|
||||||
|
его вызывает): задач он не заводит сам, а отдаёт отложенные находки **списком
|
||||||
|
урожая** — формулировка, оракул, провенанс — и хранит отчёт триажа вместе с
|
||||||
|
изменением. Приходит и любой другой разбор, вплоть до пересказа человеком; тогда
|
||||||
|
триажа нет и шаг 1 порядка делается руками.
|
||||||
|
|
||||||
## Находка агента — не задача
|
## Находка агента — не задача
|
||||||
|
|
||||||
Мнение агента — **гипотеза, пока у неё нет свидетельства** (падающий тест,
|
Мнение агента — **гипотеза, пока у неё нет свидетельства** (падающий тест,
|
||||||
@@ -25,7 +31,7 @@
|
|||||||
переживает запись.
|
переживает запись.
|
||||||
- **Находка без свидетельства / низкой уверенности** → **сырьё**: `research`, у
|
- **Находка без свидетельства / низкой уверенности** → **сырьё**: `research`, у
|
||||||
которого раздел «Вопрос» и есть недостающее свидетельство («при каких условиях
|
которого раздел «Вопрос» и есть недостающее свидетельство («при каких условиях
|
||||||
это воспроизводится»). Не `fix`: без `Воспроизведения` его в спринт не
|
это воспроизводится»). Не `fix`: без `Воспроизведения` его в работу не
|
||||||
возьмут, и правильно — чинить нечего, пока непонятно, что ломается. Судьба
|
возьмут, и правильно — чинить нечего, пока непонятно, что ломается. Судьба
|
||||||
сырья — штурм, где либо найдётся подтверждение, либо оно уедет в
|
сырья — штурм, где либо найдётся подтверждение, либо оно уедет в
|
||||||
`REJECTED.md`.
|
`REJECTED.md`.
|
||||||
@@ -46,7 +52,7 @@
|
|||||||
устареть, выноси пользователю, а не заводи молча заново.
|
устареть, выноси пользователю, а не заводи молча заново.
|
||||||
4. **Разложи по целям — там, где цель нужна.** Большинство находок ревью это
|
4. **Разложи по целям — там, где цель нужна.** Большинство находок ревью это
|
||||||
`fix` и `chore`, и **цель им не требуется**: они служат работоспособности, а
|
`fix` и `chore`, и **цель им не требуется**: они служат работоспособности, а
|
||||||
не направлению, и в спринт входят помимо его цели. Придуманная им цель —
|
не направлению. Придуманная им цель —
|
||||||
ровно то враньё, от которого спасает тип.
|
ровно то враньё, от которого спасает тип.
|
||||||
|
|
||||||
Цель обязательна у находки, которая оказалась **новой возможностью**
|
Цель обязательна у находки, которая оказалась **новой возможностью**
|
||||||
@@ -73,21 +79,31 @@
|
|||||||
Без него через месяц не отличить проверенную находку от догадки.
|
Без него через месяц не отличить проверенную находку от догадки.
|
||||||
7. `tasks.py check`.
|
7. `tasks.py check`.
|
||||||
|
|
||||||
## Куда девается серьёзность, если приоритетов нет
|
## Куда девается серьёзность находки
|
||||||
|
|
||||||
Приоритетов нет, и отображать серьёзность некуда — но **выкидывать её нельзя**.
|
Уровня серьёзности в записи нет — но **выкидывать её нельзя**: серьёзность
|
||||||
Правило замены:
|
отображается **в позицию в очереди**, потому что приоритет и есть порядок строк
|
||||||
|
в беклоге (правило 4 [SKILL.md](../SKILL.md)). Отображается через довод, а не
|
||||||
|
напрямую: своей шкалы у интейка нет, доводы расстановки перечислены в
|
||||||
|
[скилле груминга](../../groom/SKILL.md#приоритет-как-его-расставляют), и
|
||||||
|
серьёзность попадает ровно в один из них.
|
||||||
|
|
||||||
- **тяжёлая находка со свидетельством** → задача под ту цель, которой она
|
- **тяжёлая находка со свидетельством о сломанном сейчас** → задача под ту цель,
|
||||||
угрожает, и **кандидат в ближайший набор**: серьёзность здесь превращается в
|
которой она угрожает, и **первой строкой секции**: `move <слаг> --first
|
||||||
довод при выборе цели следующего спринта, а не в уровень в файле. Довод
|
--reason «сломано сейчас: …»`. Это довод «что сломано сейчас» из перечня
|
||||||
записывается причиной в мете (`--reason`), иначе к моменту набора его
|
груминга — единственный, который не требует сравнения с соседями по очереди,
|
||||||
никто не вспомнит;
|
потому что сломанное дорожает само. Позицию всё равно назначает человек, и
|
||||||
- **находка, ломающая уже идущий спринт**, — не интейк вовсе: см. правило
|
здесь он её уже назначил: верх очереди для такой находки предъявляется картой
|
||||||
вторжения в скилле `session`. В беклог она падает, только если врываться не
|
шага 5, а не проставляется молча;
|
||||||
положено;
|
- **тяжёлая находка о риске, а не о поломке** (дорожает от ожидания,
|
||||||
|
разблокирует остальное) → в конец секции, а довод — причиной в мете
|
||||||
|
(`--reason`). Позицию назначит человек на ближайшем груминге, сравнив её с
|
||||||
|
верхом очереди; без записанного довода сравнивать он будет с нуля;
|
||||||
|
- **находка, которая не ждёт груминга вовсе** (необратимый ущерб, покраснела
|
||||||
|
проверка, которую проект назвал сломанным), — не интейк: это работа прямо
|
||||||
|
сейчас, а в беклог она падает, только если ждать всё-таки можно;
|
||||||
- **низкая уверенность или нет свидетельства** → сырьё (`research` с пустым
|
- **низкая уверенность или нет свидетельства** → сырьё (`research` с пустым
|
||||||
разделом «Вопрос»);
|
разделом «Вопрос»): его место в очереди производно от типа — конец секции;
|
||||||
- **мелочь** → строка в пакетный файл;
|
- **мелочь** → строка в пакетный файл;
|
||||||
- **уже починено / развилка решена сейчас** → ничего.
|
- **уже починено / развилка решена сейчас** → ничего.
|
||||||
|
|
||||||
@@ -0,0 +1,211 @@
|
|||||||
|
# Язык проектных текстов
|
||||||
|
|
||||||
|
**Копия.** Дом — `shared/language.md` в репозитории плагинов; язык общий для
|
||||||
|
документов канона и для задач, и потому не принадлежит ни одному плагину.
|
||||||
|
Правится дом, а не этот файл: расхождение ловит `copies.py` на гейте коммита.
|
||||||
|
|
||||||
|
<!-- копия: язык-доктрина из shared/language.md -->
|
||||||
|
|
||||||
|
Правила — для всего, что пишется словами: задачи и цели, документы канона,
|
||||||
|
решения ADR, записки разведки, сообщения коммитов. Не для кода и не для
|
||||||
|
сообщений программы пользователю — там свои конвенции проекта.
|
||||||
|
|
||||||
|
Основа — **информационный стиль** Максима Ильяхова ([учебник
|
||||||
|
бюро](https://bureau.ru/projects/book-text/), книга «Пиши, сокращай»). Он
|
||||||
|
написан для рекламы, статей и писем, поэтому взят не целиком.
|
||||||
|
|
||||||
|
## Зачем он здесь
|
||||||
|
|
||||||
|
Проектный текст читают в двух положениях, и оба неудобные: **выбирают, брать ли
|
||||||
|
задачу**, глядя в строку индекса и один экран тела; и **возвращаются через
|
||||||
|
квартал**, не помня контекста. Оба положения наказывают одно и то же — слова, не
|
||||||
|
несущие сведений. Информационный стиль ровно про это, и его польза здесь не
|
||||||
|
эстетическая: текст, из которого нельзя достать факт, заставляет открывать код,
|
||||||
|
а это и есть цена, которой мы избегаем.
|
||||||
|
|
||||||
|
## Что взято сверх правил вычитки
|
||||||
|
|
||||||
|
Эти три требования судит человек, а не проход вычитки: находка по ним требует
|
||||||
|
увидеть текст целиком, а не фразу.
|
||||||
|
|
||||||
|
**Полезное действие.** У каждого текста есть вопрос, на который он отвечает, и
|
||||||
|
читатель, который этот вопрос задаёт. Не отвечает — не пишется. У задачи это
|
||||||
|
«зачем она нужна» и «что станет наблюдаемо иначе»; у документа канона — его
|
||||||
|
собственный вопрос («что это за система», «как сложено», «почему так решили»).
|
||||||
|
Текст, который не отвечает ни на чей вопрос, сокращается до нуля — это законный
|
||||||
|
исход правки.
|
||||||
|
|
||||||
|
**Параллельность.** Однородное пишется одинаково: пункты списка — одной
|
||||||
|
грамматической формой, разделы одного вида — одним порядком, заголовки одного
|
||||||
|
уровня — одним типом фразы. Разнобой читатель принимает за разницу по существу и
|
||||||
|
ищет её.
|
||||||
|
|
||||||
|
**Заголовок работает.** Заголовок называет содержание раздела, а не тему
|
||||||
|
вообще: «Что проверяет `check`», а не «О проверках». Заголовков ставится
|
||||||
|
столько, чтобы длинный текст можно было просматривать, а не только читать
|
||||||
|
подряд.
|
||||||
|
|
||||||
|
## Что отброшено намеренно
|
||||||
|
|
||||||
|
Инфостиль написан для текстов, где читателя надо удержать. Проектный текст
|
||||||
|
читают потому, что надо, и держать его нечем. Отсюда три расхождения:
|
||||||
|
|
||||||
|
- **Парцелляция и рубленые фразы — нет.** Приём «Коротко. Ещё короче. Вот так»
|
||||||
|
ломает причинную связь, а в решении и в задаче ценность именно в ней:
|
||||||
|
«поэтому», «иначе», «раз так» несут смысл и остаются.
|
||||||
|
- **Не всякое вводное — мусор.** «Если», «иначе», «при таком-то условии»,
|
||||||
|
«в отличие от» — это условия и противопоставления, то есть сведения. Режутся
|
||||||
|
вводные, которые не меняют смысл предложения.
|
||||||
|
- **Скобки и точка с запятой остаются.** В технической записи скобки несут
|
||||||
|
уточнение — имя команды, единицы, слаг, — и запрет на них удлинил бы текст, а
|
||||||
|
не сократил. Запрет на многоточие принимаем: в проектном тексте оно значит
|
||||||
|
«дописать позже», и такой текст лучше не публиковать.
|
||||||
|
|
||||||
|
И общее: инфостиль призывает «снять корону с себя и надеть на читателя». Здесь
|
||||||
|
читатель — **ты сам через квартал** и тот, кто возьмёт задачу. Писать для них
|
||||||
|
значит называть состояние и остаток, а не пересказывать, как было интересно
|
||||||
|
разбираться.
|
||||||
|
|
||||||
|
<!-- /копия: язык-доктрина -->
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
<!-- копия: язык-правила из shared/language.md -->
|
||||||
|
|
||||||
|
У каждого правила названа причина: она же говорит, где правило **не**
|
||||||
|
применяется.
|
||||||
|
|
||||||
|
1. **Глагол вместо отглагольного существительного, активный залог.** «Обработчик
|
||||||
|
не проверяет владельца», а не «проверка владельца не осуществляется»;
|
||||||
|
«скрипт переписывает индекс», а не «индекс переписывается скриптом».
|
||||||
|
Отглагольное существительное прячет того, кто действует, — а в техническом
|
||||||
|
тексте важен именно он. Страдательный залог **остаётся**, когда деятель
|
||||||
|
неизвестен или неважен: «файл удаляется» верно, если удаляет любая из трёх
|
||||||
|
команд.
|
||||||
|
|
||||||
|
2. **Факт вместо оценки.** «Время ответа доходит до 800 мс», а не «работает
|
||||||
|
медленно»; «тело 40 МиБ держит блокировку 5 секунд», а не «большие тела
|
||||||
|
тормозят». Оценка допустима, когда факт стоит рядом, в той же фразе. Без
|
||||||
|
факта это настроение, а не сведение, — и находка тем ценнее, что оценку
|
||||||
|
потом не проверить.
|
||||||
|
|
||||||
|
3. **Стоп-слова.** Канцелярит (является, осуществляется, в целях, в рамках,
|
||||||
|
данный, вышеуказанный), вводные-паразиты (в общем, как известно, стоит
|
||||||
|
отметить), усилители (очень, крайне, достаточно, абсолютно, максимально),
|
||||||
|
синонимы одного качества («понятный и простой»), неопределённое
|
||||||
|
(соответствующий, определённый, некоторый).
|
||||||
|
|
||||||
|
Проверка одна: **вычеркни слово — смысл изменился, оставляй.** И осторожно с
|
||||||
|
вводными: «если», «иначе», «при таком-то условии», «в отличие от» несут
|
||||||
|
условие и противопоставление, то есть сведения, — их не трогают.
|
||||||
|
|
||||||
|
4. **Одна мысль — одно предложение.** Предложение с двумя независимыми
|
||||||
|
утверждениями делится. **Причинную связь не режут**: «поэтому», «иначе», «раз
|
||||||
|
так» — смысл, а не длина; рубленые фразы ради краткости тут вредят.
|
||||||
|
|
||||||
|
**Поля меты не делятся.** «Зачем» в мете задачи по формату — одно
|
||||||
|
предложение: оно повторяется строкой индекса, и второму там не поместиться.
|
||||||
|
Тесно — сокращают, но не делят. То же с любым полем вида `- **Имя:** …`.
|
||||||
|
|
||||||
|
5. **Англицизм, у которого есть живое русское слово, заменяется.**
|
||||||
|
|
||||||
|
| Калька | Русский аналог |
|
||||||
|
| --- | --- |
|
||||||
|
| флоу | поток, процесс, сценарий |
|
||||||
|
| фикс, зафиксить | исправление, исправить, починить |
|
||||||
|
| чекать | проверять |
|
||||||
|
| апрув, заапрувить | согласование, согласовать |
|
||||||
|
| best-effort | по возможности |
|
||||||
|
| кейс | случай, сценарий |
|
||||||
|
| перформанс | производительность |
|
||||||
|
| матчинг, смэтчить | сопоставление, сопоставить |
|
||||||
|
| зарелизить | выпустить, выложить |
|
||||||
|
| отрефакторить | переписать, разделить, убрать второй путь |
|
||||||
|
|
||||||
|
Насильно не переводится то, что является **именем вещи**: термины технологий
|
||||||
|
и протоколов (`SQL`, `API`, `CSV`, `N+1`, `IDOR`), имена классов, методов,
|
||||||
|
полей, таблиц и команд, слаг, а также термин, у которого нет точного русского
|
||||||
|
эквивалента и который в команде уже прижился.
|
||||||
|
|
||||||
|
Цель — простой и точный текст, а не пуризм. Русский аналог звучит коряво или
|
||||||
|
искажает смысл — остаётся термин.
|
||||||
|
|
||||||
|
6. **Слово из своего словаря не трогается — список закрыт.** Оговорка «термин
|
||||||
|
прижился» без списка проверяема на глаз и потому не проверяема: прижившимся
|
||||||
|
выглядит любое слово, встреченное трижды.
|
||||||
|
|
||||||
|
| Термин | Что называет |
|
||||||
|
| --- | --- |
|
||||||
|
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
|
||||||
|
| триаж | стадия конвейера, сводящая находки в решение |
|
||||||
|
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
|
||||||
|
| дедуп, дедупликация | сверка нового против уже лежащего |
|
||||||
|
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
|
||||||
|
| дифф, `--base` | разница между состояниями в git |
|
||||||
|
| промпт | текст, которым зовут модель |
|
||||||
|
| change, capability, spec | сущности OpenSpec, имена вещей чужого инструмента |
|
||||||
|
| generative, applicative | роды проходов ревью, вводятся определением по месту |
|
||||||
|
|
||||||
|
**Список закрыт.** Слово не отсюда и не из таблицы имён вещей выше — находка,
|
||||||
|
а не «принятый стиль»: у него либо есть живой русский аналог, либо оно
|
||||||
|
требует ввода одной строкой при первом употреблении.
|
||||||
|
|
||||||
|
Отсюда же читается снятое. Эти слова из текстов убраны, и возвращать их не
|
||||||
|
надо: **конфляция** (смешение), **декорреляция** (разведённость, разведён с
|
||||||
|
кем-то), **непоймание** (почему не поймали), **эвал-сет** (проверочный
|
||||||
|
набор), **гайд** (руководство). Каждое было латинизмом или калькой при живом
|
||||||
|
русском слове, и каждое к моменту снятия жило в трёх-шести файлах разом — то
|
||||||
|
есть выглядело словарём, не будучи им.
|
||||||
|
|
||||||
|
7. **Жаргон и метафоры заменяются прямым называнием.** Автору образ понятен,
|
||||||
|
читателю — нет.
|
||||||
|
|
||||||
|
| Метафора-жаргон | Прямо |
|
||||||
|
| --- | --- |
|
||||||
|
| рычаг (кэша, отбора) | условие отбора, параметр |
|
||||||
|
| навешен не на тот счётчик | завязан не на тот счётчик |
|
||||||
|
| переширокий матчинг по имени | слишком грубое сопоставление по имени, слишком много слабых совпадений |
|
||||||
|
| костыль | временное решение, обходной путь — и в чём именно |
|
||||||
|
| просело, отвалилось | стало медленнее на столько-то, перестало отвечать |
|
||||||
|
|
||||||
|
Проверка: **фраза требует, чтобы читатель додумал образ, — заменяется
|
||||||
|
буквальным описанием того, что происходит.**
|
||||||
|
|
||||||
|
8. **Термин, которого нет в документах проекта, вводится одной строкой или не
|
||||||
|
употребляется.** Термин, не встречающийся ни в паспорте, ни в архитектуре, ни
|
||||||
|
в конвенциях, — свой словарь у отдельной записи, а это самый дешёвый способ
|
||||||
|
сделать беклог нечитаемым для того, кто вернётся к нему через квартал.
|
||||||
|
Заменять незнакомый термин догадкой нельзя: догадка о предметной области
|
||||||
|
дороже непонятного слова, потому что выглядит понятной.
|
||||||
|
|
||||||
|
**Слово, занятое в другом смысле, — то же нарушение.** Термин, который в
|
||||||
|
одном документе проекта значит одно, а здесь другое, ломает оба.
|
||||||
|
|
||||||
|
9. **Имя файла — английское слово по сути, а не транслит.** `queue-as-table`, а
|
||||||
|
не `ochered-tablicej`; `move-parse-strict`, а не `razbor-hoda`. Транслит
|
||||||
|
нечитаем тому, кто ищет по смыслу, и не сокращается, а имя стоит в ссылках,
|
||||||
|
коммитах и путях, которые набирают руками. Переименование — **перенос ссылок
|
||||||
|
одним проходом**, а не правка одного файла.
|
||||||
|
|
||||||
|
<!-- /копия: язык-правила -->
|
||||||
|
|
||||||
|
## Порог правки
|
||||||
|
|
||||||
|
<!-- копия: порог-правки из shared/language.md -->
|
||||||
|
|
||||||
|
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
||||||
|
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
||||||
|
перестают читать весь список, и вместе с ним пропадают настоящие находки.
|
||||||
|
Сомневаешься — не правь. Формулировка, которая просто **не твоя**, — не находка.
|
||||||
|
|
||||||
|
**Систематичность нарушения — не довод в его пользу.** Одна и та же ошибка в
|
||||||
|
пяти файлах не становится «принятым стилем»: чаще это значит, что правило не
|
||||||
|
применялось вовсе, — и находка тем важнее. «Так сделано везде» годится как
|
||||||
|
основание для **одной находки на весь набор** («правило N нарушено в пяти
|
||||||
|
записях, перечень: …»), но не как основание промолчать. Принятым считается
|
||||||
|
только то, что назвал зовущий или что записано в конвенциях проекта.
|
||||||
|
|
||||||
|
<!-- /копия: порог-правки -->
|
||||||
|
|
||||||
|
И обратное: язык правится **по ходу той операции, которая записи касается**.
|
||||||
|
Беклог не переписывают ради языка.
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# Сопровождение и эксплуатация
|
||||||
|
|
||||||
|
**Копия.** Дом — `shared/operations.md` в репозитории плагинов. Словарь общий для
|
||||||
|
роадмапа, архитектуры и темы ревью `operations`, и не принадлежит ни одному из
|
||||||
|
трёх — правится дом, а не этот файл.
|
||||||
|
|
||||||
|
Скиллу задач он нужен для секции `Сопровождение` в `ROADMAP.md`: она отвечает не
|
||||||
|
на «что приложение будет уметь», а на «чем его держат», и путать эти два вопроса
|
||||||
|
нельзя.
|
||||||
|
|
||||||
|
<!-- копия: сопровождение-словарь из shared/operations.md -->
|
||||||
|
|
||||||
|
Одна тема живёт в трёх местах, и путать их слова нельзя.
|
||||||
|
|
||||||
|
**Сопровождение** — всё, чем держат проект: инструмент и сборка, процесс,
|
||||||
|
выкладка, метрики и логи, инфраструктура, дежурство. **Эксплуатация** — его
|
||||||
|
часть: работа системы на проде. Целое и часть, и никогда наоборот.
|
||||||
|
|
||||||
|
| Место | Уровень | Что там |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ROADMAP.md`, секция `Сопровождение` | план | **работы**, которые собираемся делать: цели и их задачи |
|
||||||
|
| `architecture.md`, раздел «Эксплуатация» | состояние | **как устроено сейчас**: где работает, что рядом, кто перезапускает |
|
||||||
|
| тема ревью `operations` | оптика | **чем проверяем**: «это упало через неделю на проде» |
|
||||||
|
|
||||||
|
Слово **«поддержка» не употребляется вовсе** — в нём слышится помощь
|
||||||
|
пользователю, а это другая работа.
|
||||||
|
|
||||||
|
**Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
|
||||||
|
сообщает о своём состоянии» — возможность приложения, её место среди прочих
|
||||||
|
целей: наблюдает пользователь сервиса. «Дежурный видит состояние на одном
|
||||||
|
экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в разные
|
||||||
|
секции роадмапа, и это верно — секции отвечают на разные вопросы.
|
||||||
|
|
||||||
|
<!-- /копия: сопровождение-словарь -->
|
||||||
+11
-8
@@ -57,22 +57,25 @@
|
|||||||
`REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись:
|
`REJECTED.md` здесь — не «выкинули», а именно тот след, что переживает запись:
|
||||||
через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на
|
через квартал вопрос «куда делась задача X» отвечается строкой со ссылками на
|
||||||
наследников, а не археологией git;
|
наследников, а не археологией git;
|
||||||
- родитель осмыслен как **возможность**, а не как шаг → это цель. Тип на месте
|
- родитель осмыслен как **возможность**, а не как шаг → это цель, и **строка
|
||||||
не меняется (цель живёт в другом индексе): заводится `--type goal` в `ROADMAP.md`,
|
переезжает**: `edit <slug> --type goal --section <часть роадмапа>` снимает её с
|
||||||
части получают `--goal <новый слаг>`, родитель закрывается с причиной-ссылкой.
|
`BACKLOG.md` и вставляет в `ROADMAP.md`. Файл в `items/` при этом не двигается —
|
||||||
|
он и есть запись. Части получают `--goal <слаг родителя>`, а закрывать родителя
|
||||||
|
нечем и незачем: он не выкинут, он стал целью.
|
||||||
|
|
||||||
**Промежуточного зонтика между целью и задачей нет.** Тип `epic` упразднён:
|
**Промежуточного зонтика между целью и задачей нет.** Тип `epic` упразднён:
|
||||||
роль зонтика играет цель, а слишком крупный шаг дробится на шаги помельче под
|
роль зонтика играет цель, а слишком крупный шаг дробится на шаги помельче под
|
||||||
той же целью. Если частям нужен общий заголовок — значит у них общая
|
той же целью. Если частям нужен общий заголовок — значит у них общая
|
||||||
возможность, и её надо назвать целью, а не заводить временный тип.
|
возможность, и её надо назвать целью, а не заводить временный тип.
|
||||||
|
|
||||||
## Когда декомпозиция случается посреди спринта
|
## Когда декомпозиция случается посреди работы
|
||||||
|
|
||||||
Задача, которая **оказалась крупнее задачи**, распознаётся до того, как под неё
|
Задача, которая **оказалась крупнее задачи**, распознаётся до того, как под неё
|
||||||
заведено предложение об изменении: иначе его придётся выбрасывать. Она выходит
|
заведено предложение об изменении: иначе его придётся выбрасывать. Она выходит
|
||||||
из набора (`sprint drop … --reason "крупнее задачи"`), уходит на декомпозицию, а
|
из работы на декомпозицию, а её строка возвращается в беклог с причиной
|
||||||
спринт продолжается остальными. Части заводятся сразу под той же целью, но в
|
(`move … --reason "крупнее задачи"`). Части заводятся сразу под той же целью, и
|
||||||
текущий набор **не добавляются** — набор заморожен.
|
**место в очереди им назначает человек**: машина поставит их в конец секции, а
|
||||||
|
крупная задача редко распадается на что-то менее срочное, чем была сама.
|
||||||
|
|
||||||
## Мозговой штурм сырья
|
## Мозговой штурм сырья
|
||||||
|
|
||||||
@@ -80,7 +83,7 @@
|
|||||||
тест «готова к взятию», потому что неясно, что именно делаем. Штурм проясняет —
|
тест «готова к взятию», потому что неясно, что именно делаем. Штурм проясняет —
|
||||||
и это **generative-операция, а не applicative**.
|
и это **generative-операция, а не applicative**.
|
||||||
|
|
||||||
Исход штурма и есть заполненный «Вопрос» (тогда разведку можно брать в спринт)
|
Исход штурма и есть заполненный «Вопрос» (тогда разведку можно брать в работу)
|
||||||
или набор задач с типами, которые из ответа следуют. Третий законный исход —
|
или набор задач с типами, которые из ответа следуют. Третий законный исход —
|
||||||
`close --reason`.
|
`close --reason`.
|
||||||
|
|
||||||
+6
-6
@@ -15,8 +15,8 @@
|
|||||||
| Допустимые сверх того | `Рамки`, `Вопросы` |
|
| Допустимые сверх того | `Рамки`, `Вопросы` |
|
||||||
| Поле места | **Категория** — полка домена беклога |
|
| Поле места | **Категория** — полка домена беклога |
|
||||||
| Цель (`goal:<слаг>`) | нет: цель — это возможность, а здесь её не появляется |
|
| Цель (`goal:<слаг>`) | нет: цель — это возможность, а здесь её не появляется |
|
||||||
| Индекс | `BACKLOG.md` → `SPRINT.md` |
|
| Индекс | `BACKLOG.md` |
|
||||||
| Берётся в спринт | да |
|
| Берётся в работу | да |
|
||||||
|
|
||||||
## Адресат — разработчик, и это законно
|
## Адресат — разработчик, и это законно
|
||||||
|
|
||||||
@@ -48,13 +48,13 @@
|
|||||||
5. **Проверить, что это не «заодно».** Обслуживание любит склеиваться в пачку
|
5. **Проверить, что это не «заодно».** Обслуживание любит склеиваться в пачку
|
||||||
(«обновить зависимости и переписать сборку и убрать мёртвый код»). Не
|
(«обновить зависимости и переписать сборку и убрать мёртвый код»). Не
|
||||||
мерджится порознь — это несколько задач ([split.md](split.md)).
|
мерджится порознь — это несколько задач ([split.md](split.md)).
|
||||||
6. **Цель не проставлять.** `chore` служит работоспособности, а не направлению,
|
6. **Цель не проставлять.** `chore` служит работоспособности, а не направлению.
|
||||||
и в набор спринта входит помимо его цели. Работа по сопровождению проекта
|
Работа по сопровождению проекта при этом видна в роадмапе — секцией
|
||||||
при этом видна в роадмапе — секцией `Сопровождение`, но целью не становится.
|
`Сопровождение`, но целью не становится.
|
||||||
|
|
||||||
## Что видит машина, а что человек
|
## Что видит машина, а что человек
|
||||||
|
|
||||||
`check` и `sprint take` смотрят на **наличие непустого** `Затрагивает` и на
|
`ready` смотрит на **наличие непустого** `Затрагивает` и на
|
||||||
**число** критериев — ровно то же, что у `feature`. Разница между типами здесь не
|
**число** критериев — ровно то же, что у `feature`. Разница между типами здесь не
|
||||||
в строгости проверки, а в том, **кому адресован ответ** на «что станет
|
в строгости проверки, а в том, **кому адресован ответ** на «что станет
|
||||||
наблюдаемо иначе», — и это судит человек.
|
наблюдаемо иначе», — и это судит человек.
|
||||||
+9
-7
@@ -16,12 +16,12 @@
|
|||||||
| Допустимые сверх того | `Рамки`, `Вопросы` |
|
| Допустимые сверх того | `Рамки`, `Вопросы` |
|
||||||
| Поле места | **Категория** — полка домена беклога |
|
| Поле места | **Категория** — полка домена беклога |
|
||||||
| Цель (`goal:<слаг>`) | **обязательна** |
|
| Цель (`goal:<слаг>`) | **обязательна** |
|
||||||
| Индекс | `BACKLOG.md` → `SPRINT.md` |
|
| Индекс | `BACKLOG.md` |
|
||||||
| Берётся в спринт | да |
|
| Берётся в работу | да |
|
||||||
|
|
||||||
**Цель обязательна, и это единственный тип, у которого так.** Новая возможность
|
**Цель обязательна, и это единственный тип, у которого так.** Новая возможность
|
||||||
и есть содержание цели: подходящей нет — либо она заводится, либо перед тобой не
|
и есть содержание цели: подходящей нет — либо она заводится, либо перед тобой не
|
||||||
`feature`. `sprint take` без цели откажет.
|
`feature`. `ready` без цели откажет.
|
||||||
|
|
||||||
## Алгоритм
|
## Алгоритм
|
||||||
|
|
||||||
@@ -42,15 +42,17 @@
|
|||||||
заходом и не мерджится целиком — это несколько задач под одной целью, дроби
|
заходом и не мерджится целиком — это несколько задач под одной целью, дроби
|
||||||
сразу ([split.md](split.md)). Промежуточного зонтика между целью и задачей
|
сразу ([split.md](split.md)). Промежуточного зонтика между целью и задачей
|
||||||
нет.
|
нет.
|
||||||
6. **Реализация** — дело пайплайна проекта, не этого скилла. Закрывается
|
6. **Реализация** — дело конвейера проекта, не этого скилла. Закрывается
|
||||||
`close <слаг> --implemented`: файл и строка удаляются, суть переезжает в
|
`close <слаг> --implemented`: файл и строка удаляются, суть переезжает в
|
||||||
`openspec/specs/` и документацию.
|
`openspec/specs/` и документацию.
|
||||||
|
|
||||||
## Что видит машина, а что человек
|
## Что видит машина, а что человек
|
||||||
|
|
||||||
`check` и `sprint take` смотрят на **наличие непустого** раздела `Затрагивает`,
|
Схему типа судит `ready` на входе в работу: **наличие непустого** раздела
|
||||||
на **число** критериев (меньше двух — отказ, больше пяти — замечание) и на цель.
|
`Затрагивает`, **число** критериев (меньше двух — отказ, больше пяти —
|
||||||
Наличие оракула проверяется **эвристикой** — словом «оракул» в пункте.
|
замечание) и цель. Наличие оракула проверяется **эвристикой** — словом «оракул»
|
||||||
|
в пункте. `check` этого поимённо не говорит, а считает строкой здоровья
|
||||||
|
(`SKILL.md`, «Что механизировано, а что нет»).
|
||||||
|
|
||||||
Полнота перечня границ машине не видна: границу, которую забыли назвать, она от
|
Полнота перечня границ машине не видна: границу, которую забыли назвать, она от
|
||||||
отсутствующей не отличает. Настоящий оракул от слова «оракул» тоже не отличает.
|
отсутствующей не отличает. Настоящий оракул от слова «оракул» тоже не отличает.
|
||||||
+6
-7
@@ -17,14 +17,14 @@
|
|||||||
| Допустимые сверх того | `Рамки`, `Вопросы` |
|
| Допустимые сверх того | `Рамки`, `Вопросы` |
|
||||||
| Поле места | **Категория** — полка домена беклога |
|
| Поле места | **Категория** — полка домена беклога |
|
||||||
| Цель (`goal:<слаг>`) | необязательна |
|
| Цель (`goal:<слаг>`) | необязательна |
|
||||||
| Индекс | `BACKLOG.md` → `SPRINT.md` |
|
| Индекс | `BACKLOG.md` |
|
||||||
| Берётся в спринт | да |
|
| Берётся в работу | да |
|
||||||
|
|
||||||
## `Воспроизведение` — раздел, которого нет у других типов
|
## `Воспроизведение` — раздел, которого нет у других типов
|
||||||
|
|
||||||
**Не воспроизводится — это `research`, а не `fix`.** Правило было записано и
|
**Не воспроизводится — это `research`, а не `fix`.** Правило было записано и
|
||||||
раньше, но проверять его было нечем, и «починки» без единого шага повторения
|
раньше, но проверять его было нечем, и «починки» без единого шага повторения
|
||||||
уходили в спринт наравне с остальными. Раздел делает правило проверяемым: он
|
уходили в работу наравне с остальными. Раздел делает правило проверяемым: он
|
||||||
называет, **что сделать, чтобы расхождение проявилось, и что при этом видно
|
называет, **что сделать, чтобы расхождение проявилось, и что при этом видно
|
||||||
вместо ожидаемого**.
|
вместо ожидаемого**.
|
||||||
|
|
||||||
@@ -54,9 +54,8 @@
|
|||||||
почти всегда есть парный критерий: **прежнее поведение не сломалось**
|
почти всегда есть парный критерий: **прежнее поведение не сломалось**
|
||||||
(«ввод `а1` принимается по-прежнему»). Без него починка чинит одно и ломает
|
(«ввод `а1` принимается по-прежнему»). Без него починка чинит одно и ломает
|
||||||
соседнее.
|
соседнее.
|
||||||
6. **Цель не выдумывать.** `fix` служит работоспособности, а не направлению, и в
|
6. **Цель не выдумывать.** `fix` служит работоспособности, а не направлению.
|
||||||
набор спринта входит помимо его цели. Придуманная цель — то же враньё, от
|
Придуманная цель — то же враньё, от которого спасает тип.
|
||||||
которого спасает тип.
|
|
||||||
7. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман
|
7. **Записать дефект в журнал** `docs/review.md` с пометкой «проскочил / пойман
|
||||||
ревью». Проскочившие — проверочный набор для калибровки конвейера; пойманные с
|
ревью». Проскочившие — проверочный набор для калибровки конвейера; пойманные с
|
||||||
оракулом — лучшая опора для прохода ревью: проектные, воспроизводимые,
|
оракулом — лучшая опора для прохода ревью: проектные, воспроизводимые,
|
||||||
@@ -64,7 +63,7 @@
|
|||||||
|
|
||||||
## Что видит машина, а что человек
|
## Что видит машина, а что человек
|
||||||
|
|
||||||
`check` и `sprint take` смотрят на **наличие непустого** `Воспроизведения` и
|
`ready` смотрит на **наличие непустого** `Воспроизведения` и
|
||||||
`Затрагивает` и на **число** критериев. Годность воспроизведения — человеку:
|
`Затрагивает` и на **число** критериев. Годность воспроизведения — человеку:
|
||||||
шаги, по которым ничего не воспроизводится, машина от годных не отличает, и
|
шаги, по которым ничего не воспроизводится, машина от годных не отличает, и
|
||||||
делать вид, что проверено больше проверенного, хуже, чем не проверять вовсе.
|
делать вид, что проверено больше проверенного, хуже, чем не проверять вовсе.
|
||||||
+21
-41
@@ -23,9 +23,9 @@
|
|||||||
# 🐞 Не отбрасывать молча лишние символы в ходе
|
# 🐞 Не отбрасывать молча лишние символы в ходе
|
||||||
|
|
||||||
- **Тип:** fix
|
- **Тип:** fix
|
||||||
- **Категория:** Ядро — вышла из спринта: остаток писал нерешённое в журнал
|
- **Категория:** Ядро — вернулась из работы: остаток писал нерешённое в журнал
|
||||||
- **Зачем:** ввод «а1б2» ходит в a1 — игрок не видит, что ошибся, и винит игру
|
- **Зачем:** ввод «а1б2» ходит в a1 — игрок не видит, что ошибся, и винит игру
|
||||||
- **Теги:** goal:merge-robustness, sprint:2026-08-03
|
- **Теги:** goal:merge-robustness
|
||||||
|
|
||||||
Разбор хода читает первые два символа и молча выбрасывает остаток строки.
|
Разбор хода читает первые два символа и молча выбрасывает остаток строки.
|
||||||
|
|
||||||
@@ -63,11 +63,11 @@
|
|||||||
здоровье; годность формулировки смотрит агент `task-form`.
|
здоровье; годность формулировки смотрит агент `task-form`.
|
||||||
- **Мета-блок** — список сразу после заголовка, **поле на строку**. Обязательны
|
- **Мета-блок** — список сразу после заголовка, **поле на строку**. Обязательны
|
||||||
**тип** и **место**, причина после тире желательна (именно она объясняет,
|
**тип** и **место**, причина после тире желательна (именно она объясняет,
|
||||||
почему задача здесь оказалась — в том числе «вышла из спринта: …»), «зачем» и
|
почему задача здесь оказалась — в том числе «вернулась из работы: …»), «зачем» и
|
||||||
теги необязательны. Нераспознанные поля сохраняются: скрипт правит свои и не
|
теги необязательны. Нераспознанные поля сохраняются: скрипт правит свои и не
|
||||||
трогает чужие.
|
трогает чужие.
|
||||||
- **Тип — первым полем.** Он решает, что у записи вообще может быть: какие
|
- **Тип — первым полем.** Он решает, что у записи вообще может быть: какие
|
||||||
разделы обязательны, нужна ли цель, берётся ли она в спринт, — и читается
|
разделы обязательны, нужна ли цель, берётся ли она в работу, — и читается
|
||||||
раньше всего остального. Словарь **закрыт**: `goal` | `feature` | `fix` |
|
раньше всего остального. Словарь **закрыт**: `goal` | `feature` | `fix` |
|
||||||
`chore` | `research`. Не подходит ни один — это сигнал, что в записи их два и
|
`chore` | `research`. Не подходит ни один — это сигнал, что в записи их два и
|
||||||
её надо разделить.
|
её надо разделить.
|
||||||
@@ -93,11 +93,11 @@
|
|||||||
| Тип | Поле | Значения | Что это |
|
| Тип | Поле | Значения | Что это |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| `goal` | **Секция** | `Запланировано`, `Направления`, `Сопровождение` | часть роадмапа: состояние очереди |
|
| `goal` | **Секция** | `Запланировано`, `Направления`, `Сопровождение` | часть роадмапа: состояние очереди |
|
||||||
| прочие | **Категория** | секции беклога проекта (`Ядро`, `Инфра`, …) | полка домена, в которую задача вернётся из спринта |
|
| прочие | **Категория** | секции беклога проекта (`Ядро`, `Инфра`, …) | полка домена, на которой задача лежит |
|
||||||
|
|
||||||
Разные имена потому, что это **разные вещи**. У задачи поле переживает спринт:
|
Разные имена потому, что это **разные вещи**. У задачи это полка: куда её
|
||||||
`sprint drop` возвращает её именно туда. У цели оно называет не полку, а место в
|
положили и куда вернут, если она уйдёт в работу и вернётся. У цели оно называет
|
||||||
очереди работ. Одно имя на два смысла их и смешивало; `check` называет
|
не полку, а место в очереди работ. Одно имя на два смысла их и смешивало; `check` называет
|
||||||
несовпадение дрейфом, `check --fix` переименовывает.
|
несовпадение дрейфом, `check --fix` переименовывает.
|
||||||
|
|
||||||
Имя самого места принадлежит **заголовку индекса** — файл на него лишь
|
Имя самого места принадлежит **заголовку индекса** — файл на него лишь
|
||||||
@@ -142,7 +142,7 @@
|
|||||||
имя таблицы стабильно, номер последней миграции протухает молча. Пишется
|
имя таблицы стабильно, номер последней миграции протухает молча. Пишется
|
||||||
`таблица points и её миграция`, а не `миграция 0042`.
|
`таблица points и её миграция`, а не `миграция 0042`.
|
||||||
|
|
||||||
**Что из этого механизировано.** `check` и `sprint take` смотрят только на
|
**Что из этого механизировано.** `ready` смотрит только на
|
||||||
**наличие непустого раздела**. Полнота перечня машине не видна: границу, которую
|
**наличие непустого раздела**. Полнота перечня машине не видна: границу, которую
|
||||||
забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии:
|
забыли назвать, она от отсутствующей не отличает. Раздела нет — отказ во взятии:
|
||||||
оценивать нечем.
|
оценивать нечем.
|
||||||
@@ -154,11 +154,11 @@
|
|||||||
|
|
||||||
2–5 проверяемых утверждений **списком** `- …`, **у каждого назван оракул**. Не
|
2–5 проверяемых утверждений **списком** `- …`, **у каждого назван оракул**. Не
|
||||||
«работает корректно», а «повторный прогон даёт тот же отпечаток — оракул:
|
«работает корректно», а «повторный прогон даёт тот же отпечаток — оракул:
|
||||||
команда сверки». Это не второе определение готовности, а проектная
|
команда сверки». Это не второе определение сделанного, а проектная
|
||||||
конкретизация вопроса «по чему видно, что закончено» из теста готовности ниже:
|
конкретизация вопроса «по чему видно, что закончено» из теста готовности ниже:
|
||||||
там сказано «признак завершённости», здесь — «признак плюс чем проверяется».
|
там сказано «признак завершённости», здесь — «признак плюс чем проверяется».
|
||||||
|
|
||||||
**Что из этого механизировано.** `check` и `sprint take` считают пункты: меньше
|
**Что из этого механизировано.** `ready` считает пункты: меньше
|
||||||
двух — отказ («— работает» одной строкой больше не проходит), больше пяти —
|
двух — отказ («— работает» одной строкой больше не проходит), больше пяти —
|
||||||
замечание, обычно это признак, что задача крупнее задачи. Наличие оракула
|
замечание, обычно это признак, что задача крупнее задачи. Наличие оракула
|
||||||
проверяется **эвристикой** — словом «оракул» в пункте, — и потому даёт только
|
проверяется **эвристикой** — словом «оракул» в пункте, — и потому даёт только
|
||||||
@@ -205,8 +205,8 @@
|
|||||||
ответом. Затем снимается тег (`edit <slug> --rm-tag question`) и переписывается
|
ответом. Затем снимается тег (`edit <slug> --rm-tag question`) и переписывается
|
||||||
«зачем»: «Решено: …» на вопрос «зачем нужна эта задача» уже не отвечает.
|
«зачем»: «Решено: …» на вопрос «зачем нужна эта задача» уже не отвечает.
|
||||||
|
|
||||||
**Порядок именно такой, потому что судит раздел, а не тег.** `sprint take`
|
**Порядок именно такой, потому что судит раздел, а не тег.** `ready`
|
||||||
смотрит в непустой раздел и откажет взять задачу даже со снятым тегом, а `check`
|
смотрит в непустой раздел и откажет даже при снятом теге, а `check`
|
||||||
на снятый тег при непустом разделе посоветует тег вернуть. Снять тег, не
|
на снятый тег при непустом разделе посоветует тег вернуть. Снять тег, не
|
||||||
опустошив раздел, — значит закольцевать себя между двумя советами.
|
опустошив раздел, — значит закольцевать себя между двумя советами.
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@
|
|||||||
`check` напоминает о нём у цели без задач замечанием — неразобранная цель
|
`check` напоминает о нём у цели без задач замечанием — неразобранная цель
|
||||||
законна и зелёного прогона не ломает; `check --fix` сам ставит его цели, у
|
законна и зелёного прогона не ломает; `check --fix` сам ставит его цели, у
|
||||||
которой задачи есть, а цель с тегом и без задач — прямое приглашение закрыть.
|
которой задачи есть, а цель с тегом и без задач — прямое приглашение закрыть.
|
||||||
- Цель живёт в `ROADMAP.md` и **никогда** — в `BACKLOG.md` или `SPRINT.md`.
|
- Цель живёт в `ROADMAP.md` и **никогда** — в `BACKLOG.md`.
|
||||||
- **Достигнутая цель не исчезает.** `close <слаг> --implemented` удаляет файл и
|
- **Достигнутая цель не исчезает.** `close <слаг> --implemented` удаляет файл и
|
||||||
переносит строку в секцию `Готово` с датой:
|
переносит строку в секцию `Готово` с датой:
|
||||||
`- 2026-08-04 \`merge-order\` — Исход слияния не зависит от порядка доставки. …`
|
`- 2026-08-04 \`merge-order\` — Исход слияния не зависит от порядка доставки. …`
|
||||||
@@ -277,33 +277,23 @@
|
|||||||
| Файл | Что отвечает | Секции |
|
| Файл | Что отвечает | Секции |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `ROADMAP.md` | что приложение уже умеет и чего ещё не умеет | канонические и в этом порядке: `Запланировано`, `Направления`, `Сопровождение`, `Готово` (англ. `Planned`, `Directions`, `Operations`, `Done`) |
|
| `ROADMAP.md` | что приложение уже умеет и чего ещё не умеет | канонические и в этом порядке: `Запланировано`, `Направления`, `Сопровождение`, `Готово` (англ. `Planned`, `Directions`, `Operations`, `Done`) |
|
||||||
| `BACKLOG.md` | что **можно взять** — только задачи | категории проекта (по умолчанию Ядро/Инфра) |
|
| `BACKLOG.md` | что **можно взять** — только задачи, **в порядке очереди** | категории проекта (по умолчанию Ядро/Инфра) |
|
||||||
| `SPRINT.md` | какая цель (или что её нет) и какой набор заморожен | одна: «Набор» |
|
|
||||||
| `REJECTED.md` | что ушло без реализации и почему | — |
|
| `REJECTED.md` | что ушло без реализации и почему | — |
|
||||||
|
|
||||||
Шапку `SPRINT.md` пишет `sprint start` — **тем же мета-блоком, что у задачи**:
|
|
||||||
поле на строку, `- **Цель:** [Заголовок](items/slug.md)`, `- **Начат:**` датой,
|
|
||||||
`- **Спринт:**` слагом, которым метится урожай. У спринта без цели
|
|
||||||
(`sprint start --no-goal`) поле «Цель» остаётся на месте и пишется прозой без
|
|
||||||
ссылки — «не названа»: **«цели нет» и «цель потерялась» обязаны различаться**.
|
|
||||||
Поэтому и признак «спринт идёт» — слаг, а не цель: слаг есть у любого спринта,
|
|
||||||
без него нечем метить урожай. Прежняя форма (три поля одной
|
|
||||||
строкой через `·`) читается по-прежнему и уходит сама: файл переписывается на
|
|
||||||
следующем `sprint start` и очищается на `sprint close`.
|
|
||||||
|
|
||||||
Секции — **единственные заголовки `##` в индексе**: любой другой `##` в
|
Секции — **единственные заголовки `##` в индексе**: любой другой `##` в
|
||||||
преамбуле проверка сочтёт секцией.
|
преамбуле проверка сочтёт секцией.
|
||||||
|
|
||||||
**Порядка «по важности» внутри секции беклога нет** — «что делать дальше»
|
**Порядок строк внутри секции беклога значим: это очередь.** Первая строка — то,
|
||||||
отвечает набор спринта. Единственный порядок, который есть, **производен от типа
|
что делают следующим; назначает порядок человек на груминге, и двигают его
|
||||||
и заполненности**: **сырьё** (`research` без раздела «Вопрос») стоит в конце
|
`move --after` и `move --first`. Одно место из очереди изъято и **производно от
|
||||||
|
типа и заполненности**: **сырьё** (`research` без раздела «Вопрос») стоит в конце
|
||||||
своей секции, потому что его не берут, и между берущимся оно каждый раз требует
|
своей секции, потому что его не берут, и между берущимся оно каждый раз требует
|
||||||
открыть файл, чтобы это понять. Проверяет `check`, переставляет `check --fix`,
|
открыть файл, чтобы это понять. Проверяет `check`, переставляет `check --fix`,
|
||||||
и человек этот порядок не назначает — иначе он был бы приоритетом, которого
|
и человек этот порядок не назначает — иначе он был бы приоритетом, которого
|
||||||
здесь нет.
|
здесь нет.
|
||||||
|
|
||||||
**Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до
|
**Секции «блокеры» среди них нет.** Блокер — состояние, а не полка: он живёт до
|
||||||
ответа человека, а следы остаются вопросами в файлах задач распущенного спринта.
|
ответа человека, а следы остаются вопросами в файлах задач.
|
||||||
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
|
Постоянно пустая секция со старой семантикой «разбираются пачками» противоречила
|
||||||
бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check`
|
бы правилу «эскалируем немедленно», поэтому `init` её не заводит, а `check`
|
||||||
говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. В секции
|
говорит о ней в чужом беклоге. Переезжаешь с такой секцией — удали её. В секции
|
||||||
@@ -333,9 +323,6 @@ SKILL.md. Порядок закреплён потому, что `Готово`
|
|||||||
Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при
|
Поэтому отказ на второй задаче из пяти не оставляет первую переписанной при
|
||||||
нетронутых индексах.
|
нетронутых индексах.
|
||||||
|
|
||||||
`SPRINT.md` и есть артефакт заморозки: без него набор существует только в
|
|
||||||
контексте сессии, и нарушение заморозки ненаблюдаемо.
|
|
||||||
|
|
||||||
## `REJECTED.md`
|
## `REJECTED.md`
|
||||||
|
|
||||||
Туда уходит задача, покинувшая беклог **без реализации**. Строку пишет
|
Туда уходит задача, покинувшая беклог **без реализации**. Строку пишет
|
||||||
@@ -361,15 +348,8 @@ SKILL.md. Порядок закреплён потому, что `Готово`
|
|||||||
|
|
||||||
- `goal:<слаг>` — цель, которой служит задача. Обязателен **у `feature`**:
|
- `goal:<слаг>` — цель, которой служит задача. Обязателен **у `feature`**:
|
||||||
новая возможность и есть содержание цели. У `fix`, `chore` и `research` его
|
новая возможность и есть содержание цели. У `fix`, `chore` и `research` его
|
||||||
может не быть — они служат работоспособности, а не направлению, и в набор
|
может не быть — они служат работоспособности, а не направлению.
|
||||||
спринта входят помимо его цели.
|
|
||||||
- `question` — в файле есть неразобранный раздел «Вопросы».
|
- `question` — в файле есть неразобранный раздел «Вопросы».
|
||||||
- `sprint:<слаг>` — задача заведена в этом спринте; по нему отбирается первая
|
|
||||||
порция разбора («урожай спринта»). **Ставится сам**: слаг спринта заводит
|
|
||||||
`sprint start` (по умолчанию — дата начала, он же пишется в `SPRINT.md`), и
|
|
||||||
`add` при открытом спринте помечает заводимое. Тег, который надо помнить
|
|
||||||
ставить руками, не ставится никогда — а на нём висит правило «первая порция
|
|
||||||
разбора — урожай прошедшего спринта».
|
|
||||||
- `decomposed` — на цели: разложена на задачи (см. «Файл цели»).
|
- `decomposed` — на цели: разложена на задачи (см. «Файл цели»).
|
||||||
|
|
||||||
Тега `kind:<род>` больше нет: род работы стал типом. Оставшийся в файле `check`
|
Тега `kind:<род>` больше нет: род работы стал типом. Оставшийся в файле `check`
|
||||||
+8
-8
@@ -16,11 +16,11 @@
|
|||||||
| Допустимые сверх того | — |
|
| Допустимые сверх того | — |
|
||||||
| Поле места | **Секция** — часть роадмапа |
|
| Поле места | **Секция** — часть роадмапа |
|
||||||
| Цель (`goal:<слаг>`) | запрещена: цель и есть цель |
|
| Цель (`goal:<слаг>`) | запрещена: цель и есть цель |
|
||||||
| Индекс | `ROADMAP.md`, и никогда `BACKLOG.md` или `SPRINT.md` |
|
| Индекс | `ROADMAP.md`, и никогда `BACKLOG.md` |
|
||||||
| Берётся в спринт | нет — берутся её задачи |
|
| Берётся в работу | нет — берутся её задачи |
|
||||||
|
|
||||||
Поле места у цели называется **«Секция»**, а не «Категория», и это не разнобой:
|
Поле места у цели называется **«Секция»**, а не «Категория», и это не разнобой:
|
||||||
у задачи оно называет полку домена, в которую она вернётся из спринта, а у цели
|
у задачи оно называет полку домена, на которой она лежит, а у цели
|
||||||
— часть роадмапа, то есть состояние очереди. Одно имя на два смысла их и
|
— часть роадмапа, то есть состояние очереди. Одно имя на два смысла их и
|
||||||
смешивало.
|
смешивало.
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@
|
|||||||
|
|
||||||
1. **Проверить, что это возможность, а не работа.** Работа, которой держат
|
1. **Проверить, что это возможность, а не работа.** Работа, которой держат
|
||||||
проект, на вопрос «что приложение будет уметь» не отвечает; состав перечислен
|
проект, на вопрос «что приложение будет уметь» не отвечает; состав перечислен
|
||||||
[в каноне](../../canon/references/canon.md), раздел «Сопровождение и
|
[в словаре сопровождения](operations.md). Ей отведена секция
|
||||||
эксплуатация». Ей отведена секция `Сопровождение` — там она видна в том же
|
`Сопровождение` — там она видна в том же
|
||||||
экране и не читается как обещание продукта. Граница проходит по тому,
|
экране и не читается как обещание продукта. Граница проходит по тому,
|
||||||
**кто наблюдает**:
|
**кто наблюдает**:
|
||||||
«приложение сообщает о своём состоянии» — возможность, «дежурный видит
|
«приложение сообщает о своём состоянии» — возможность, «дежурный видит
|
||||||
@@ -75,9 +75,9 @@
|
|||||||
не попадает: `Готово` отвечает «что приложение умеет», а отменённая цель не
|
не попадает: `Готово` отвечает «что приложение умеет», а отменённая цель не
|
||||||
умеет ничего.
|
умеет ничего.
|
||||||
|
|
||||||
**Место этому — переоценка на сессии, а не отдельный заход.** Отмена цели значит
|
**Место этому — груминг, а не отдельный заход.** Отмена цели значит
|
||||||
разбор всех её задач, а разбор задач и есть шаг 3 сессии
|
разбор всех её задач, а разбор задач и есть шаг 3 груминга
|
||||||
([cadence.md](../../session/references/cadence.md), пункт 7). Отменять на ходу,
|
(скилл `groom`, «что перестало быть важным»). Отменять на ходу,
|
||||||
между делом, — верный способ закрыть скопом то, что стоило перевесить.
|
между делом, — верный способ закрыть скопом то, что стоило перевесить.
|
||||||
|
|
||||||
## Что видит машина, а что человек
|
## Что видит машина, а что человек
|
||||||
+11
-10
@@ -15,8 +15,8 @@
|
|||||||
| Допустимые сверх того | `Рамки`, `Вопросы` |
|
| Допустимые сверх того | `Рамки`, `Вопросы` |
|
||||||
| Поле места | **Категория** — полка домена беклога |
|
| Поле места | **Категория** — полка домена беклога |
|
||||||
| Цель (`goal:<слаг>`) | нет |
|
| Цель (`goal:<слаг>`) | нет |
|
||||||
| Индекс | `BACKLOG.md` → `SPRINT.md` |
|
| Индекс | `BACKLOG.md` |
|
||||||
| Берётся в спринт | да — **но только с заполненным «Вопросом»** |
|
| Берётся в работу | да — **но только с заполненным «Вопросом»** |
|
||||||
|
|
||||||
**Критериев приёмки у `research` нет, и это не поблажка.** Критерии в форме
|
**Критериев приёмки у `research` нет, и это не поблажка.** Критерии в форме
|
||||||
«оракул: тест» разведке натянуты: проверять нечего, пока ответа нет. Её приёмка
|
«оракул: тест» разведке натянуты: проверять нечего, пока ответа нет. Её приёмка
|
||||||
@@ -39,13 +39,14 @@
|
|||||||
| | сырьё | разведка |
|
| | сырьё | разведка |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Раздел `Вопрос` | пуст или отсутствует | заполнен |
|
| Раздел `Вопрос` | пуст или отсутствует | заполнен |
|
||||||
| `sprint take` | отказ | берёт |
|
| `ready` | отказ | берёт |
|
||||||
| Место в секции беклога | **конец**, `check --fix` сносит туда сам | среди прочих |
|
| Место в секции беклога | **конец**, `check --fix` сносит туда сам | среди прочих |
|
||||||
| `tasks.py list --raw` | показывает | нет |
|
| `tasks.py list --raw` | показывает | нет |
|
||||||
|
|
||||||
Порядка «по важности» в беклоге по-прежнему нет. Этот порядок **производен от
|
Порядок строк в беклоге назначает человек — это приоритет (правило 4 скилла).
|
||||||
типа и заполненности**, а не назначен человеком, — потому его и проверяет машина,
|
Место сырья **из него изъято**: оно производно от типа и заполненности, а не от
|
||||||
и потому он не противоречит правилу «порядка нет, есть цель».
|
чьего-то решения, и потому его проверяет и чинит машина. Приоритетом оно не
|
||||||
|
становится: сырьё не берут вовсе, и место в конце говорит именно это.
|
||||||
|
|
||||||
Сырьём заводится и **сырая функция**: «Подсказка следующего хода» — ещё не
|
Сырьём заводится и **сырая функция**: «Подсказка следующего хода» — ещё не
|
||||||
`feature`, потому что неизвестно, что именно делать. Работа над ней — думание, и
|
`feature`, потому что неизвестно, что именно делать. Работа над ней — думание, и
|
||||||
@@ -67,16 +68,16 @@
|
|||||||
проход ревью обязан читать как условие, а не как замер.
|
проход ревью обязан читать как условие, а не как замер.
|
||||||
5. **Разложить исход на задачи** — если он их родил. Разведка кончается одним из
|
5. **Разложить исход на задачи** — если он их родил. Разведка кончается одним из
|
||||||
трёх: заведены задачи, записано знание, отказ. **Отказ — полноправный исход**:
|
трёх: заведены задачи, записано знание, отказ. **Отказ — полноправный исход**:
|
||||||
«проверили, не проблема» экономит спринт.
|
«проверили, не проблема» экономит работу.
|
||||||
6. **Закрыть** — `close <слаг> --implemented`, когда ответ записан. Файл
|
6. **Закрыть** — `close <слаг> --implemented`, когда ответ записан. Файл
|
||||||
удаляется: запись ответа и есть след, второго не нужно. Ушла без ответа —
|
удаляется: запись ответа и есть след, второго не нужно. Ушла без ответа —
|
||||||
`close --reason`, и строка уезжает в `REJECTED.md`.
|
`close --reason`, и строка уезжает в `REJECTED.md`.
|
||||||
|
|
||||||
## Что видит машина, а что человек
|
## Что видит машина, а что человек
|
||||||
|
|
||||||
`check` и `sprint take` смотрят на **наличие непустых** разделов `Вопрос` и
|
`ready` смотрит на **наличие непустых** разделов `Вопрос` и
|
||||||
`Куда ляжет ответ`, считают сырьё отдельной строкой здоровья и держат его в конце
|
`Куда ляжет ответ`; `check` считает сырьё отдельной строкой здоровья и держит
|
||||||
секции. Годность вопроса — человеку: «вопрос это или тема» машина не различает,
|
его в конце секции. Годность вопроса — человеку: «вопрос это или тема» машина не различает,
|
||||||
и `check` о годности молчит намеренно.
|
и `check` о годности молчит намеренно.
|
||||||
|
|
||||||
Штурм сырья, дробление исхода на задачи и тест «части мерджатся порознь» —
|
Штурм сырья, дробление исхода на задачи и тест «части мерджатся порознь» —
|
||||||
+279
-520
File diff suppressed because it is too large
Load Diff
+15
-3
@@ -9,7 +9,7 @@
|
|||||||
#
|
#
|
||||||
# **Что судится — staged-файлы, а не рабочее дерево**, всюду, где проверка
|
# **Что судится — staged-файлы, а не рабочее дерево**, всюду, где проверка
|
||||||
# умеет смотреть поимённо: гейт обязан судить то, что уедет в историю, а не то,
|
# умеет смотреть поимённо: гейт обязан судить то, что уедет в историю, а не то,
|
||||||
# что случайно лежит на диске рядом. Два исключения названы у своих задач, и оба
|
# что случайно лежит на диске рядом. Три исключения названы у своих задач, и все
|
||||||
# — про то, что проверке нужен весь репозиторий по существу, а не для удобства.
|
# — про то, что проверке нужен весь репозиторий по существу, а не для удобства.
|
||||||
#
|
#
|
||||||
# Ставится `lefthook install` (см. README, «Гейт коммита»). Обойти разово —
|
# Ставится `lefthook install` (см. README, «Гейт коммита»). Обойти разово —
|
||||||
@@ -23,15 +23,27 @@ pre-commit:
|
|||||||
# copies.py сверяет копию с домом, а дом лежит в другом файле, которого в
|
# copies.py сверяет копию с домом, а дом лежит в другом файле, которого в
|
||||||
# индексе может не быть: список staged дал бы «копии дословны» там, где
|
# индексе может не быть: список staged дал бы «копии дословны» там, где
|
||||||
# правка дома их и разошлась. frontmatter.py смотрел бы поимённо, но весь
|
# правка дома их и разошлась. frontmatter.py смотрел бы поимённо, но весь
|
||||||
# обход стоит сотые доли секунды — платить за него нечем.
|
# обход стоит сотые доли секунды — платить за него нечем. Glob у него шире
|
||||||
|
# на `*.json`: тем же проходом сверяется `description` плагина в
|
||||||
|
# `plugin.json` с записью того же плагина в `marketplace.json`, а коммит,
|
||||||
|
# правящий только манифест, по глобу `*.md` проверку бы не разбудил.
|
||||||
- name: фронтматтеры
|
- name: фронтматтеры
|
||||||
glob: "*.md"
|
glob: "*.{md,json}"
|
||||||
run: python3 scripts/frontmatter.py
|
run: python3 scripts/frontmatter.py
|
||||||
|
|
||||||
- name: копии правил
|
- name: копии правил
|
||||||
glob: "*.md"
|
glob: "*.md"
|
||||||
run: python3 scripts/copies.py
|
run: python3 scripts/copies.py
|
||||||
|
|
||||||
|
# Без glob намеренно, и это третье исключение из правила «судим staged».
|
||||||
|
# Проверка сводит две стороны: перечень адресов лежит в константе скрипта
|
||||||
|
# владельца (`.py`), упоминания — в прозе плагинов (`.md`). Коммит, где
|
||||||
|
# переименован документ канона, трогает только первую сторону: по глобу
|
||||||
|
# `*.md` он бы проверку не разбудил, а расходится в нём именно вторая.
|
||||||
|
# Весь обход — семь сотых секунды.
|
||||||
|
- name: адреса документов
|
||||||
|
run: python3 scripts/addresses.py
|
||||||
|
|
||||||
# Самая дорогая проверка: каждый блок — свой запуск mermaid-cli со своим
|
# Самая дорогая проверка: каждый блок — свой запуск mermaid-cli со своим
|
||||||
# chromium. Отсюда и staged-файлы вместо обхода, и параллель внутри самого
|
# chromium. Отсюда и staged-файлы вместо обхода, и параллель внутри самого
|
||||||
# скрипта: репозиторий целиком — 3 секунды, один файл — одна.
|
# скрипта: репозиторий целиком — 3 секунды, один файл — одна.
|
||||||
|
|||||||
+9
-2
@@ -56,10 +56,17 @@ quote-style = "double"
|
|||||||
|
|
||||||
[tool.pyrefly]
|
[tool.pyrefly]
|
||||||
project-includes = [
|
project-includes = [
|
||||||
"av-dev-pm/skills/tasks/scripts/tasks.py",
|
"av-dev-tasks/skills/tasks/scripts/tasks.py",
|
||||||
"av-dev-pm/skills/canon/scripts/docs.py",
|
"av-dev-docs/skills/canon/scripts/docs.py",
|
||||||
|
"av-dev-code/skills/openspec/scripts/openspec.py",
|
||||||
|
"scripts/addresses.py",
|
||||||
"scripts/copies.py",
|
"scripts/copies.py",
|
||||||
"scripts/diagrams.py",
|
"scripts/diagrams.py",
|
||||||
"scripts/frontmatter.py",
|
"scripts/frontmatter.py",
|
||||||
|
"scripts/resync.py",
|
||||||
]
|
]
|
||||||
python-version = "3.12"
|
python-version = "3.12"
|
||||||
|
# resync.py импортирует разборщик разметки из copies.py: второй разборщик той же
|
||||||
|
# разметки разошёлся бы с первым молча. Оба лежат в scripts/ и в чужой проект не
|
||||||
|
# уезжают, поэтому импорт между ними законен — но искать его надо здесь.
|
||||||
|
search-path = ["scripts"]
|
||||||
|
|||||||
@@ -0,0 +1,218 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Сверка чужих адресов в прозе плагинов с перечнем их владельца.
|
||||||
|
|
||||||
|
Судится **упразднённое, а не незнакомое**, и это следует из канона, а не из
|
||||||
|
осторожности: список тем открытый — всё, что проект кладёт в `docs/` сверх
|
||||||
|
закрытых категорий, законная тема. Значит незнакомое имя опровергнуть нечем, а
|
||||||
|
переименование и упразднение ловятся точно: канон, убирая слот, кладёт его в
|
||||||
|
карту переездов, и именно она здесь и есть перечень запрещённого. Рядом
|
||||||
|
единственная догадка — имя, **почти** совпавшее с каноническим: это опечатка с
|
||||||
|
куда большей вероятностью, чем новая тема.
|
||||||
|
|
||||||
|
Адрес документа принадлежит одному плагину, а называют его все: `docs/*` стоит
|
||||||
|
примерно в сорока местах `av-dev-code`, `tasks/ROADMAP.md` — в четырёх местах
|
||||||
|
`av-dev-docs`. Переименование в каноне до этих мест не доходит.
|
||||||
|
|
||||||
|
**Почему тут нужна машина, а не аккуратность.** Прогон ревью умеет честно
|
||||||
|
деградировать: дома темы нет — в границах покрытия появляется строка «документа в
|
||||||
|
проекте нет» с названной ценой. Протухший адрес попадает ровно в эту машинерию —
|
||||||
|
файл не открылся, строка напечаталась, и отчёт выглядит добросовестным. То есть
|
||||||
|
единственный признак ошибки, на который можно было бы рассчитывать — громкая
|
||||||
|
поломка, — деградацией и убран. Здесь он возвращается гейтом.
|
||||||
|
|
||||||
|
Перечень адресов берётся из **константы владельца** — той самой, по которой он и
|
||||||
|
так проверяет раскладку. Второй перечень прозой был бы вторым домом ровно того
|
||||||
|
сорта, против которого всё это написано.
|
||||||
|
|
||||||
|
addresses.py [корень]
|
||||||
|
|
||||||
|
Коды выхода — общий словарь скриптов av-dev:
|
||||||
|
0 сошлось
|
||||||
|
1 дрейф: неизвестный или упразднённый адрес
|
||||||
|
2 ошибка употребления
|
||||||
|
3 окружение: не тот каталог, перечень владельца недоступен
|
||||||
|
4 внутренний сбой
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import difflib
|
||||||
|
import importlib.util
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||||
|
|
||||||
|
SKIP_DIRS = {".git", ".venv", "node_modules", "__pycache__", "tmp"}
|
||||||
|
|
||||||
|
# Владельцы: префикс адреса → скрипт, который этим каталогом и владеет.
|
||||||
|
OWNERS = {
|
||||||
|
"docs": "av-dev-docs/skills/canon/scripts/docs.py",
|
||||||
|
"tasks": "av-dev-tasks/skills/tasks/scripts/tasks.py",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Журналы: описывают прошлые состояния и задним числом не переписываются.
|
||||||
|
# Адрес, верный на момент записи, здесь останется навсегда, и это не дрейф.
|
||||||
|
JOURNALS = {
|
||||||
|
"av-dev-docs/skills/canon/references/changelog.md": "журнал версий канона",
|
||||||
|
"DECISIONS.md": "журнал решений",
|
||||||
|
"HISTORY.md": "журнал работ",
|
||||||
|
"NOTES.md": "рабочие заметки",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Файлы, где упразднённый адрес назван по делу: карта переездов и сценарии
|
||||||
|
# перевода чужой раскладки. Неизвестные адреса в них проверяются как везде.
|
||||||
|
RETIRED_OK = {
|
||||||
|
"av-dev-docs/skills/canon/references/canon.md": "карта упразднённых слотов",
|
||||||
|
"av-dev-docs/skills/canon/SKILL.md": "adopt: что где искать в чужой раскладке",
|
||||||
|
"av-dev-tasks/skills/tasks/references/adopt.md": "перевод чужого каталога задач",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Адрес в прозе: начало токена, префикс владельца, остаток пути. Отрицательный
|
||||||
|
# просмотр назад отсекает хвосты чужих путей — `openspec/changes/…/tasks.md`
|
||||||
|
# адресом каталога задач не является.
|
||||||
|
ADDRESS = re.compile(r"(?<![\w/.-])(docs|tasks)/([\w./*-]*)")
|
||||||
|
|
||||||
|
# Порог близости к каноническому имени, за которым имя читается как опечатка, а
|
||||||
|
# не как своя тема проекта. Замер по именам, встреченным в репозитории: самое
|
||||||
|
# близкое законное — `recognition` против `conventions`, 0.64; опечатки
|
||||||
|
# (`architeture`, `securty`, `revew`, `datbase`) дают 0.91–0.96. Порог стоит в
|
||||||
|
# пустоте между ними, и запас с обеих сторон больше 0.15.
|
||||||
|
NEAR = 0.8
|
||||||
|
|
||||||
|
|
||||||
|
def load(root: Path, rel: str) -> ModuleType:
|
||||||
|
"""Скрипт владельца как модуль: константы берутся у него, а не рядом."""
|
||||||
|
path = root / rel
|
||||||
|
name = f"владелец_{path.stem}"
|
||||||
|
spec = importlib.util.spec_from_file_location(name, path)
|
||||||
|
if spec is None or spec.loader is None:
|
||||||
|
raise OSError(f"не читается {rel}")
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
# Модуль обязан лежать в sys.modules **до** исполнения: `@dataclass` внутри
|
||||||
|
# ищет там своё пространство имён и без этого падает.
|
||||||
|
sys.modules[name] = mod
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
||||||
|
def stem(name: str) -> str:
|
||||||
|
"""Имя документа без формы: файл, каталог и `.*` — один и тот же адрес.
|
||||||
|
|
||||||
|
Форму дома канон оставляет проекту: `docs/security.md` и `docs/security/`
|
||||||
|
называют одно. Скрытые имена (`.pm.json`) остаются как есть — точка в них
|
||||||
|
не расширение.
|
||||||
|
"""
|
||||||
|
name = name.rstrip(".")
|
||||||
|
if name.startswith("."):
|
||||||
|
return name.lower()
|
||||||
|
return name.split(".", 1)[0].lower()
|
||||||
|
|
||||||
|
|
||||||
|
def vocabularies(root: Path) -> tuple[dict[str, set[str]], dict[str, str]]:
|
||||||
|
"""Что владельцы считают своим: префикс → имена, плюс карта упразднённого."""
|
||||||
|
docs = load(root, OWNERS["docs"])
|
||||||
|
tasks = load(root, OWNERS["tasks"])
|
||||||
|
|
||||||
|
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_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}
|
||||||
|
tasks_names |= {stem(tasks.CONFIG_NAME)}
|
||||||
|
|
||||||
|
retired = {stem(n): why for n, why in docs.RETIRED.items()}
|
||||||
|
return {"docs": docs_names, "tasks": tasks_names}, retired
|
||||||
|
|
||||||
|
|
||||||
|
def walk(root: Path) -> list[Path]:
|
||||||
|
out = []
|
||||||
|
for p in sorted(root.rglob("*.md")):
|
||||||
|
rel = p.relative_to(root)
|
||||||
|
if SKIP_DIRS & set(rel.parts):
|
||||||
|
continue
|
||||||
|
if rel.as_posix() in JOURNALS:
|
||||||
|
continue
|
||||||
|
out.append(p)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
|
||||||
|
if not (root / ".claude-plugin").is_dir():
|
||||||
|
print(f"ОТКАЗ: {root} не похож на корень маркетплейса: нет .claude-plugin/",
|
||||||
|
file=sys.stderr)
|
||||||
|
return ENV
|
||||||
|
|
||||||
|
try:
|
||||||
|
known, retired = vocabularies(root)
|
||||||
|
except Exception as e: # noqa: BLE001 — перечень владельца обязан быть доступен
|
||||||
|
print(f"ОТКАЗ: перечень адресов не взять у владельца: {e}", file=sys.stderr)
|
||||||
|
return ENV
|
||||||
|
|
||||||
|
findings: list[str] = []
|
||||||
|
files = walk(root)
|
||||||
|
seen = 0
|
||||||
|
own_themes: set[str] = set()
|
||||||
|
for path in files:
|
||||||
|
rel = path.relative_to(root).as_posix()
|
||||||
|
retired_ok = rel in RETIRED_OK
|
||||||
|
for num, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1):
|
||||||
|
for m in ADDRESS.finditer(line):
|
||||||
|
owner, rest = m.group(1), m.group(2)
|
||||||
|
first = rest.split("/", 1)[0]
|
||||||
|
if not first or first.startswith("*"):
|
||||||
|
continue # сам каталог или шаблон по всем документам
|
||||||
|
seen += 1
|
||||||
|
name = stem(first)
|
||||||
|
if name in known[owner]:
|
||||||
|
continue
|
||||||
|
if name in retired:
|
||||||
|
if not retired_ok:
|
||||||
|
findings.append(
|
||||||
|
f"{rel}:{num}: `{m.group(0)}` — слот упразднён,"
|
||||||
|
f" содержимое {retired[name]}")
|
||||||
|
continue
|
||||||
|
near = difflib.get_close_matches(name, sorted(known[owner]),
|
||||||
|
n=1, cutoff=NEAR)
|
||||||
|
if near:
|
||||||
|
findings.append(
|
||||||
|
f"{rel}:{num}: `{m.group(0)}` — у владельца ({owner})"
|
||||||
|
f" такого адреса нет, а «{near[0]}» есть: похоже на опечатку")
|
||||||
|
continue
|
||||||
|
own_themes.add(f"{owner}/{name}")
|
||||||
|
|
||||||
|
print(f"адресов встречено {seen} в {len(files)} файлах;"
|
||||||
|
f" перечни взяты из {', '.join(sorted(OWNERS.values()))}")
|
||||||
|
if findings:
|
||||||
|
print()
|
||||||
|
for f in findings:
|
||||||
|
print(f"РАСХОЖДЕНИЕ {f}")
|
||||||
|
print(f"\nИтог: расхождений {len(findings)}. Правится **упоминание**,"
|
||||||
|
f" а не перечень: перечень — то, по чему владелец проверяет"
|
||||||
|
f" раскладку проекта.")
|
||||||
|
return DRIFT
|
||||||
|
|
||||||
|
print("упразднённых адресов нет")
|
||||||
|
if own_themes:
|
||||||
|
print(f"Имён вне перечня {len(own_themes)}, и они **не судятся** —"
|
||||||
|
f" список тем открытый: {', '.join(sorted(own_themes))}.")
|
||||||
|
print(f"Не проверялось: журналы ({len(JOURNALS)} файла — они описывают"
|
||||||
|
f" прошлые состояния), адреса `openspec/*` (раскладка чужого"
|
||||||
|
f" инструмента, у нас владельца нет), упоминания в комментариях"
|
||||||
|
f" скриптов — сверяется только markdown.")
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(INTERNAL)
|
||||||
|
except Exception as e: # noqa: BLE001 — последний рубеж, код 4 по словарю
|
||||||
|
print(f"внутренний сбой ({type(e).__name__}): {e}", file=sys.stderr)
|
||||||
|
sys.exit(INTERNAL)
|
||||||
+56
-7
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Проверка фронтматтеров скиллов и charter'ов этого репозитория.
|
"""Проверка фронтматтеров скиллов и charter'ов этого репозитория и описаний плагинов.
|
||||||
|
|
||||||
Фронтматтер — единственная часть скилла, которую читает не человек, а загрузчик:
|
Фронтматтер — единственная часть скилла, которую читает не человек, а загрузчик:
|
||||||
по `name` он разрешает вызов, по `description` решает, звать ли скилл вообще.
|
по `name` он разрешает вызов, по `description` решает, звать ли скилл вообще.
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
разумную строку, а скилл либо не находится по имени, либо загружается с
|
разумную строку, а скилл либо не находится по имени, либо загружается с
|
||||||
обрезанным описанием и потому не срабатывает на своих же триггерах.
|
обрезанным описанием и потому не срабатывает на своих же триггерах.
|
||||||
|
|
||||||
Ловится три класса.
|
Ловится четыре класса.
|
||||||
|
|
||||||
**Двоеточие с пробелом в описании без кавычек.** В YAML `: ` внутри простого
|
**Двоеточие с пробелом в описании без кавычек.** В YAML `: ` внутри простого
|
||||||
скаляра начинает вложенное отображение — строка «конвейер ревью: гейт, сверка…»
|
скаляра начинает вложенное отображение — строка «конвейер ревью: гейт, сверка…»
|
||||||
@@ -21,13 +21,19 @@
|
|||||||
|
|
||||||
**Цвет, не отвечающий модели.** Цвет charter'а кодирует **модель**, на которой
|
**Цвет, не отвечающий модели.** Цвет charter'а кодирует **модель**, на которой
|
||||||
идёт проход, а не его роль: раскладка — в
|
идёт проход, а не его роль: раскладка — в
|
||||||
`av-dev-pipeline/skills/review-pipeline/SKILL.md`, раздел «Модель по проходу».
|
`av-dev-code/skills/review/SKILL.md`, раздел «Модель по проходу».
|
||||||
Правило существует ровно затем, чтобы стоимость прогона читалась взглядом по
|
Правило существует ровно затем, чтобы стоимость прогона читалась взглядом по
|
||||||
списку агентов, и держаться вниманием оно не может: цвет ставится один раз при
|
списку агентов, и держаться вниманием оно не может: цвет ставится один раз при
|
||||||
заведении charter'а, а модель потом меняется калибровкой.
|
заведении charter'а, а модель потом меняется калибровкой.
|
||||||
|
|
||||||
|
**Описание плагина, разошедшееся между манифестами.** У описания два дома:
|
||||||
|
`<плагин>/.claude-plugin/plugin.json` его показывает установленному плагину,
|
||||||
|
корневой `.claude-plugin/marketplace.json` — тому, кто выбирает, ставить ли.
|
||||||
|
Правят обычно один, и разойтись они успели уже трижды из четырёх. `copies.py`
|
||||||
|
этот класс не берёт: он смотрит markdown, а манифест — json.
|
||||||
|
|
||||||
Коды выхода — тот же словарь, что у tasks.py, docs.py, copies.py и diagrams.py:
|
Коды выхода — тот же словарь, что у tasks.py, docs.py, copies.py и diagrams.py:
|
||||||
0 все фронтматтеры в порядке
|
0 все фронтматтеры и описания в порядке
|
||||||
1 расхождение
|
1 расхождение
|
||||||
2 ошибка употребления: аргументы
|
2 ошибка употребления: аргументы
|
||||||
3 окружение: не тот каталог
|
3 окружение: не тот каталог
|
||||||
@@ -37,12 +43,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||||
|
|
||||||
# Дом раскладки — «Модель по проходу» в review-pipeline/SKILL.md; здесь её
|
# Дом раскладки — «Модель по проходу» в av-dev-code/skills/review/SKILL.md; здесь её
|
||||||
# механизация. Порядок цветов — порядок стоимости прогона.
|
# механизация. Порядок цветов — порядок стоимости прогона.
|
||||||
PALETTE = {"sonnet": "green", "opus": "yellow"}
|
PALETTE = {"sonnet": "green", "opus": "yellow"}
|
||||||
|
|
||||||
@@ -129,6 +136,40 @@ def collect(root: Path) -> list[tuple[Sheet, str, set[str]]]:
|
|||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def manifests(root: Path) -> list[tuple[str, list[str]]]:
|
||||||
|
"""Описание каждого плагина: `plugin.json` против `marketplace.json`."""
|
||||||
|
market = root / ".claude-plugin" / "marketplace.json"
|
||||||
|
where = market.relative_to(root).as_posix()
|
||||||
|
try:
|
||||||
|
listed = {
|
||||||
|
str(entry.get("name", "")): str(entry.get("description", ""))
|
||||||
|
for entry in json.loads(market.read_text(encoding="utf-8"))["plugins"]
|
||||||
|
}
|
||||||
|
except (OSError, ValueError, KeyError, TypeError) as e:
|
||||||
|
return [(where, [f"манифест маркетплейса не разбирается: {e}"])]
|
||||||
|
|
||||||
|
found: list[tuple[str, list[str]]] = []
|
||||||
|
for plugin in sorted(root.glob("av-*/")):
|
||||||
|
card = plugin / ".claude-plugin" / "plugin.json"
|
||||||
|
rel = card.relative_to(root).as_posix()
|
||||||
|
try:
|
||||||
|
own = json.loads(card.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, ValueError) as e:
|
||||||
|
found.append((rel, [f"манифест плагина не разбирается: {e}"]))
|
||||||
|
continue
|
||||||
|
name = str(own.get("name", plugin.name))
|
||||||
|
if name not in listed:
|
||||||
|
found.append((rel, [
|
||||||
|
f"плагина `{name}` нет в {where} — маркетплейс его не отдаёт"
|
||||||
|
]))
|
||||||
|
elif str(own.get("description", "")) != listed[name]:
|
||||||
|
found.append((rel, [
|
||||||
|
f"`description` разошлось с записью `{name}` в {where}:"
|
||||||
|
f" у описания один текст на два манифеста, и правят обычно один"
|
||||||
|
]))
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
ap = argparse.ArgumentParser(description="Проверка фронтматтеров.")
|
ap = argparse.ArgumentParser(description="Проверка фронтматтеров.")
|
||||||
ap.add_argument("--dir", default=".", help="корень репозитория")
|
ap.add_argument("--dir", default=".", help="корень репозитория")
|
||||||
@@ -150,17 +191,25 @@ def main() -> int:
|
|||||||
if sheet.parsed:
|
if sheet.parsed:
|
||||||
sheet.check(expected, required)
|
sheet.check(expected, required)
|
||||||
|
|
||||||
|
cards = manifests(root)
|
||||||
|
plugins = len(list(root.glob("av-*/")))
|
||||||
|
|
||||||
skills = sum(1 for _, _, required in sheets if required is SKILL_KEYS)
|
skills = sum(1 for _, _, required in sheets if required is SKILL_KEYS)
|
||||||
print(f"фронтматтеров {len(sheets)}: скиллов {skills},"
|
print(f"фронтматтеров {len(sheets)}: скиллов {skills},"
|
||||||
f" charter'ов {len(sheets) - skills}")
|
f" charter'ов {len(sheets) - skills}")
|
||||||
|
print(f"манифестов плагинов {plugins}: описание сверено с marketplace.json")
|
||||||
|
|
||||||
broken = [sheet for sheet, _, _ in sheets if sheet.problems]
|
broken = [sheet for sheet, _, _ in sheets if sheet.problems]
|
||||||
if broken:
|
if broken or cards:
|
||||||
print()
|
print()
|
||||||
for sheet in broken:
|
for sheet in broken:
|
||||||
for problem in sheet.problems:
|
for problem in sheet.problems:
|
||||||
print(f"ОШИБКА {sheet.where}\n {problem}")
|
print(f"ОШИБКА {sheet.where}\n {problem}")
|
||||||
print(f"\nИтог: с ошибками {len(broken)} из {len(sheets)}.")
|
for rel, problems in cards:
|
||||||
|
for problem in problems:
|
||||||
|
print(f"ОШИБКА {rel}\n {problem}")
|
||||||
|
print(f"\nИтог: с ошибками {len(broken) + len(cards)}"
|
||||||
|
f" из {len(sheets) + plugins}.")
|
||||||
return DRIFT
|
return DRIFT
|
||||||
|
|
||||||
print("все в порядке")
|
print("все в порядке")
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Пересборка помеченных копий из их домов.
|
||||||
|
|
||||||
|
`copies.py` ловит расхождение, но чинить его руками — та же работа, из-за
|
||||||
|
которой копии и расходятся: правка дома касается семи файлов, и седьмой
|
||||||
|
забывают. Здесь она делается машиной и потому дословна по построению.
|
||||||
|
|
||||||
|
**В гейт коммита этот скрипт не ставится, и это решение, а не недосмотр.**
|
||||||
|
Автоматическая пересборка на коммите протащила бы правку дома в семь файлов
|
||||||
|
мимо глаз автора — а правка дома, чья копия уезжает в репозиторий проекта,
|
||||||
|
обязана ещё и попасть в журнал версий канона. Гейт поэтому только **называет**
|
||||||
|
расхождение, а согласие с ним остаётся действием человека.
|
||||||
|
|
||||||
|
Разметку разбирает `copies.py` — он импортируется целиком. Второй разборщик той
|
||||||
|
же разметки разошёлся бы с первым молча, и это ровно тот класс дефекта, против
|
||||||
|
которого вся механика копий и заведена.
|
||||||
|
|
||||||
|
**Ограда блока кода принадлежит месту, а не дому.** Тело `журнал-дефектов-форма`
|
||||||
|
живёт в доме внутри ```, а в скелете канона лежит внутри чужой, объемлющей
|
||||||
|
ограды — и своей там иметь не должно. Поэтому при пересборке берётся тело дома
|
||||||
|
без крайних оград, а обратно надевается **та ограда, что была у копии**.
|
||||||
|
|
||||||
|
resync.py [корень]
|
||||||
|
|
||||||
|
Коды выхода — общий словарь скриптов av-dev:
|
||||||
|
0 готово (в том числе «нечего пересобирать»)
|
||||||
|
2 ошибка употребления: незакрытый маркер, дубль id, копия без дома
|
||||||
|
3 окружение: не тот каталог
|
||||||
|
4 внутренний сбой
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import copies
|
||||||
|
from copies import ENV, INTERNAL, OK, USAGE, Region
|
||||||
|
|
||||||
|
|
||||||
|
def edges(body: list[str]) -> tuple[list[str], list[str]]:
|
||||||
|
"""Ограда копии, если она есть: её надевают обратно на тело дома."""
|
||||||
|
trimmed = [ln.rstrip() for ln in body]
|
||||||
|
while trimmed and not trimmed[0]:
|
||||||
|
trimmed.pop(0)
|
||||||
|
while trimmed and not trimmed[-1]:
|
||||||
|
trimmed.pop()
|
||||||
|
if len(trimmed) >= 2 and trimmed[0].startswith("```") and trimmed[-1].startswith("```"):
|
||||||
|
return [trimmed[0]], [trimmed[-1]]
|
||||||
|
return [], []
|
||||||
|
|
||||||
|
|
||||||
|
def rebuild(path: Path, targets: list[tuple[Region, Region, str]]) -> None:
|
||||||
|
"""Переписать тела копий в одном файле, идя снизу вверх по номерам строк."""
|
||||||
|
lines = path.read_text(encoding="utf-8").splitlines()
|
||||||
|
for copy, home, home_rel in sorted(targets, key=lambda t: -t[0].line):
|
||||||
|
head, tail = edges(copy.body)
|
||||||
|
# Пустые строки по краям сверке не подлежат и потому принадлежат месту:
|
||||||
|
# у копии внутри объемлющей ограды их нет, у копии в прозе — есть.
|
||||||
|
# Ставим их так, как стояло, иначе пересборка правит и то, чего не
|
||||||
|
# чинила, и дифф перестаёт читаться.
|
||||||
|
lead = [""] if copy.body and not copy.body[0].strip() else []
|
||||||
|
trail = [""] if copy.body and not copy.body[-1].strip() else []
|
||||||
|
body = [*head, *home.normalized(), *tail]
|
||||||
|
start = copy.line - 1 # строка открывающего маркера
|
||||||
|
end = start + len(copy.body) + 1 # строка закрывающего
|
||||||
|
lines[start:end + 1] = [
|
||||||
|
f"<!-- копия: {copy.ident} из {home_rel} -->",
|
||||||
|
*lead, *body, *trail,
|
||||||
|
f"<!-- /копия: {copy.ident} -->",
|
||||||
|
]
|
||||||
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
root = Path(sys.argv[1] if len(sys.argv) > 1 else ".").resolve()
|
||||||
|
if not (root / ".claude-plugin").is_dir():
|
||||||
|
print(f"ОТКАЗ: {root} не похож на корень маркетплейса: нет .claude-plugin/",
|
||||||
|
file=sys.stderr)
|
||||||
|
return ENV
|
||||||
|
|
||||||
|
errors: list[str] = []
|
||||||
|
homes: dict[str, Region] = {}
|
||||||
|
by_file: dict[Path, list[Region]] = {}
|
||||||
|
for path in copies.walk(root):
|
||||||
|
file_homes, file_copies = copies.scan(path, errors)
|
||||||
|
for h in file_homes:
|
||||||
|
if h.ident in homes:
|
||||||
|
errors.append(f"{h.where}: дом «{h.ident}» уже объявлен"
|
||||||
|
f" в {homes[h.ident].where} — id обязан быть один")
|
||||||
|
continue
|
||||||
|
homes[h.ident] = h
|
||||||
|
if file_copies:
|
||||||
|
by_file[path] = file_copies
|
||||||
|
|
||||||
|
for file_copies in by_file.values():
|
||||||
|
for c in file_copies:
|
||||||
|
if c.ident not in homes:
|
||||||
|
errors.append(f"{c.where}: копия «{c.ident}» без дома —"
|
||||||
|
f" дом либо не помечен, либо переименован")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
for e in errors:
|
||||||
|
print(f"УПОТРЕБЛЕНИЕ {e}", file=sys.stderr)
|
||||||
|
print("\nПересборка не начата: разметка сломана, и починка её не лечит.",
|
||||||
|
file=sys.stderr)
|
||||||
|
return USAGE
|
||||||
|
|
||||||
|
total, touched = 0, 0
|
||||||
|
for path, file_copies in sorted(by_file.items()):
|
||||||
|
targets = []
|
||||||
|
for c in file_copies:
|
||||||
|
home = homes[c.ident]
|
||||||
|
home_rel = home.path.relative_to(root).as_posix()
|
||||||
|
total += 1
|
||||||
|
same_text = c.normalized() == home.normalized()
|
||||||
|
same_addr = bool(c.declared_home) and home_rel.endswith(
|
||||||
|
c.declared_home.lstrip("./"))
|
||||||
|
if same_text and same_addr:
|
||||||
|
continue
|
||||||
|
targets.append((c, home, home_rel))
|
||||||
|
if not targets:
|
||||||
|
continue
|
||||||
|
rebuild(path, targets)
|
||||||
|
touched += len(targets)
|
||||||
|
rel = path.relative_to(root).as_posix()
|
||||||
|
print(f"{rel}: пересобрано {len(targets)} —"
|
||||||
|
f" {', '.join(t[0].ident for t in targets)}")
|
||||||
|
|
||||||
|
print(f"копий {total}, домов {len(homes)}; пересобрано {touched}")
|
||||||
|
if touched:
|
||||||
|
print("\nПрочитай дифф перед коммитом. Копия, уезжающая в репозиторий "
|
||||||
|
"проекта, тянет ещё и запись в журнал версий канона — её машина "
|
||||||
|
"не напишет.")
|
||||||
|
return OK
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
sys.exit(main())
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit(INTERNAL)
|
||||||
|
except Exception as e: # noqa: BLE001 — последний рубеж, код 4 по словарю
|
||||||
|
print(f"внутренний сбой ({type(e).__name__}): {e}", file=sys.stderr)
|
||||||
|
sys.exit(INTERNAL)
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
# Язык проектных текстов
|
||||||
|
|
||||||
|
**Это дом.** Файл не входит ни в один плагин: язык общий для документов канона и
|
||||||
|
для задач, и хранить его внутри одного из них значило бы отдать общее правило во
|
||||||
|
владение половине. Плагины везут **копии**, помеченные разметкой `copies.py`, и
|
||||||
|
расхождение ловит гейт коммита, а не внимание.
|
||||||
|
|
||||||
|
Правится **здесь**. Копия, поправленная у себя, — расхождение, а не правка.
|
||||||
|
|
||||||
|
Три блока, и делятся они по потребителю, а не по теме:
|
||||||
|
|
||||||
|
| Блок | Что в нём | Кто копирует |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `язык-доктрина` | зачем стиль нужен, что взято сверх устава, что отброшено | справочник языка в плагине |
|
||||||
|
| `язык-правила` | девять правил, по которым судят текст | справочник и уставы вычитки |
|
||||||
|
| `порог-правки` | когда находка не заводится | справочник, уставы вычитки, `task-form` |
|
||||||
|
|
||||||
|
`порог-правки` вынесен из правил намеренно: он нужен и тому, кто правил языка не
|
||||||
|
проверяет вовсе, — а вложенных блоков разметка не знает, и внутри `язык-правила`
|
||||||
|
его было бы не забрать отдельно.
|
||||||
|
|
||||||
|
<!-- дом: язык-доктрина -->
|
||||||
|
|
||||||
|
Правила — для всего, что пишется словами: задачи и цели, документы канона,
|
||||||
|
решения ADR, записки разведки, сообщения коммитов. Не для кода и не для
|
||||||
|
сообщений программы пользователю — там свои конвенции проекта.
|
||||||
|
|
||||||
|
Основа — **информационный стиль** Максима Ильяхова ([учебник
|
||||||
|
бюро](https://bureau.ru/projects/book-text/), книга «Пиши, сокращай»). Он
|
||||||
|
написан для рекламы, статей и писем, поэтому взят не целиком.
|
||||||
|
|
||||||
|
## Зачем он здесь
|
||||||
|
|
||||||
|
Проектный текст читают в двух положениях, и оба неудобные: **выбирают, брать ли
|
||||||
|
задачу**, глядя в строку индекса и один экран тела; и **возвращаются через
|
||||||
|
квартал**, не помня контекста. Оба положения наказывают одно и то же — слова, не
|
||||||
|
несущие сведений. Информационный стиль ровно про это, и его польза здесь не
|
||||||
|
эстетическая: текст, из которого нельзя достать факт, заставляет открывать код,
|
||||||
|
а это и есть цена, которой мы избегаем.
|
||||||
|
|
||||||
|
## Что взято сверх правил вычитки
|
||||||
|
|
||||||
|
Эти три требования судит человек, а не проход вычитки: находка по ним требует
|
||||||
|
увидеть текст целиком, а не фразу.
|
||||||
|
|
||||||
|
**Полезное действие.** У каждого текста есть вопрос, на который он отвечает, и
|
||||||
|
читатель, который этот вопрос задаёт. Не отвечает — не пишется. У задачи это
|
||||||
|
«зачем она нужна» и «что станет наблюдаемо иначе»; у документа канона — его
|
||||||
|
собственный вопрос («что это за система», «как сложено», «почему так решили»).
|
||||||
|
Текст, который не отвечает ни на чей вопрос, сокращается до нуля — это законный
|
||||||
|
исход правки.
|
||||||
|
|
||||||
|
**Параллельность.** Однородное пишется одинаково: пункты списка — одной
|
||||||
|
грамматической формой, разделы одного вида — одним порядком, заголовки одного
|
||||||
|
уровня — одним типом фразы. Разнобой читатель принимает за разницу по существу и
|
||||||
|
ищет её.
|
||||||
|
|
||||||
|
**Заголовок работает.** Заголовок называет содержание раздела, а не тему
|
||||||
|
вообще: «Что проверяет `check`», а не «О проверках». Заголовков ставится
|
||||||
|
столько, чтобы длинный текст можно было просматривать, а не только читать
|
||||||
|
подряд.
|
||||||
|
|
||||||
|
## Что отброшено намеренно
|
||||||
|
|
||||||
|
Инфостиль написан для текстов, где читателя надо удержать. Проектный текст
|
||||||
|
читают потому, что надо, и держать его нечем. Отсюда три расхождения:
|
||||||
|
|
||||||
|
- **Парцелляция и рубленые фразы — нет.** Приём «Коротко. Ещё короче. Вот так»
|
||||||
|
ломает причинную связь, а в решении и в задаче ценность именно в ней:
|
||||||
|
«поэтому», «иначе», «раз так» несут смысл и остаются.
|
||||||
|
- **Не всякое вводное — мусор.** «Если», «иначе», «при таком-то условии»,
|
||||||
|
«в отличие от» — это условия и противопоставления, то есть сведения. Режутся
|
||||||
|
вводные, которые не меняют смысл предложения.
|
||||||
|
- **Скобки и точка с запятой остаются.** В технической записи скобки несут
|
||||||
|
уточнение — имя команды, единицы, слаг, — и запрет на них удлинил бы текст, а
|
||||||
|
не сократил. Запрет на многоточие принимаем: в проектном тексте оно значит
|
||||||
|
«дописать позже», и такой текст лучше не публиковать.
|
||||||
|
|
||||||
|
И общее: инфостиль призывает «снять корону с себя и надеть на читателя». Здесь
|
||||||
|
читатель — **ты сам через квартал** и тот, кто возьмёт задачу. Писать для них
|
||||||
|
значит называть состояние и остаток, а не пересказывать, как было интересно
|
||||||
|
разбираться.
|
||||||
|
|
||||||
|
<!-- /дом: язык-доктрина -->
|
||||||
|
|
||||||
|
## Правила
|
||||||
|
|
||||||
|
<!-- дом: язык-правила -->
|
||||||
|
|
||||||
|
У каждого правила названа причина: она же говорит, где правило **не**
|
||||||
|
применяется.
|
||||||
|
|
||||||
|
1. **Глагол вместо отглагольного существительного, активный залог.** «Обработчик
|
||||||
|
не проверяет владельца», а не «проверка владельца не осуществляется»;
|
||||||
|
«скрипт переписывает индекс», а не «индекс переписывается скриптом».
|
||||||
|
Отглагольное существительное прячет того, кто действует, — а в техническом
|
||||||
|
тексте важен именно он. Страдательный залог **остаётся**, когда деятель
|
||||||
|
неизвестен или неважен: «файл удаляется» верно, если удаляет любая из трёх
|
||||||
|
команд.
|
||||||
|
|
||||||
|
2. **Факт вместо оценки.** «Время ответа доходит до 800 мс», а не «работает
|
||||||
|
медленно»; «тело 40 МиБ держит блокировку 5 секунд», а не «большие тела
|
||||||
|
тормозят». Оценка допустима, когда факт стоит рядом, в той же фразе. Без
|
||||||
|
факта это настроение, а не сведение, — и находка тем ценнее, что оценку
|
||||||
|
потом не проверить.
|
||||||
|
|
||||||
|
3. **Стоп-слова.** Канцелярит (является, осуществляется, в целях, в рамках,
|
||||||
|
данный, вышеуказанный), вводные-паразиты (в общем, как известно, стоит
|
||||||
|
отметить), усилители (очень, крайне, достаточно, абсолютно, максимально),
|
||||||
|
синонимы одного качества («понятный и простой»), неопределённое
|
||||||
|
(соответствующий, определённый, некоторый).
|
||||||
|
|
||||||
|
Проверка одна: **вычеркни слово — смысл изменился, оставляй.** И осторожно с
|
||||||
|
вводными: «если», «иначе», «при таком-то условии», «в отличие от» несут
|
||||||
|
условие и противопоставление, то есть сведения, — их не трогают.
|
||||||
|
|
||||||
|
4. **Одна мысль — одно предложение.** Предложение с двумя независимыми
|
||||||
|
утверждениями делится. **Причинную связь не режут**: «поэтому», «иначе», «раз
|
||||||
|
так» — смысл, а не длина; рубленые фразы ради краткости тут вредят.
|
||||||
|
|
||||||
|
**Поля меты не делятся.** «Зачем» в мете задачи по формату — одно
|
||||||
|
предложение: оно повторяется строкой индекса, и второму там не поместиться.
|
||||||
|
Тесно — сокращают, но не делят. То же с любым полем вида `- **Имя:** …`.
|
||||||
|
|
||||||
|
5. **Англицизм, у которого есть живое русское слово, заменяется.**
|
||||||
|
|
||||||
|
| Калька | Русский аналог |
|
||||||
|
| --- | --- |
|
||||||
|
| флоу | поток, процесс, сценарий |
|
||||||
|
| фикс, зафиксить | исправление, исправить, починить |
|
||||||
|
| чекать | проверять |
|
||||||
|
| апрув, заапрувить | согласование, согласовать |
|
||||||
|
| best-effort | по возможности |
|
||||||
|
| кейс | случай, сценарий |
|
||||||
|
| перформанс | производительность |
|
||||||
|
| матчинг, смэтчить | сопоставление, сопоставить |
|
||||||
|
| зарелизить | выпустить, выложить |
|
||||||
|
| отрефакторить | переписать, разделить, убрать второй путь |
|
||||||
|
|
||||||
|
Насильно не переводится то, что является **именем вещи**: термины технологий
|
||||||
|
и протоколов (`SQL`, `API`, `CSV`, `N+1`, `IDOR`), имена классов, методов,
|
||||||
|
полей, таблиц и команд, слаг, а также термин, у которого нет точного русского
|
||||||
|
эквивалента и который в команде уже прижился.
|
||||||
|
|
||||||
|
Цель — простой и точный текст, а не пуризм. Русский аналог звучит коряво или
|
||||||
|
искажает смысл — остаётся термин.
|
||||||
|
|
||||||
|
6. **Слово из своего словаря не трогается — список закрыт.** Оговорка «термин
|
||||||
|
прижился» без списка проверяема на глаз и потому не проверяема: прижившимся
|
||||||
|
выглядит любое слово, встреченное трижды.
|
||||||
|
|
||||||
|
| Термин | Что называет |
|
||||||
|
| --- | --- |
|
||||||
|
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
|
||||||
|
| триаж | стадия конвейера, сводящая находки в решение |
|
||||||
|
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
|
||||||
|
| дедуп, дедупликация | сверка нового против уже лежащего |
|
||||||
|
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
|
||||||
|
| дифф, `--base` | разница между состояниями в git |
|
||||||
|
| промпт | текст, которым зовут модель |
|
||||||
|
| change, capability, spec | сущности OpenSpec, имена вещей чужого инструмента |
|
||||||
|
| generative, applicative | роды проходов ревью, вводятся определением по месту |
|
||||||
|
|
||||||
|
**Список закрыт.** Слово не отсюда и не из таблицы имён вещей выше — находка,
|
||||||
|
а не «принятый стиль»: у него либо есть живой русский аналог, либо оно
|
||||||
|
требует ввода одной строкой при первом употреблении.
|
||||||
|
|
||||||
|
Отсюда же читается снятое. Эти слова из текстов убраны, и возвращать их не
|
||||||
|
надо: **конфляция** (смешение), **декорреляция** (разведённость, разведён с
|
||||||
|
кем-то), **непоймание** (почему не поймали), **эвал-сет** (проверочный
|
||||||
|
набор), **гайд** (руководство). Каждое было латинизмом или калькой при живом
|
||||||
|
русском слове, и каждое к моменту снятия жило в трёх-шести файлах разом — то
|
||||||
|
есть выглядело словарём, не будучи им.
|
||||||
|
|
||||||
|
7. **Жаргон и метафоры заменяются прямым называнием.** Автору образ понятен,
|
||||||
|
читателю — нет.
|
||||||
|
|
||||||
|
| Метафора-жаргон | Прямо |
|
||||||
|
| --- | --- |
|
||||||
|
| рычаг (кэша, отбора) | условие отбора, параметр |
|
||||||
|
| навешен не на тот счётчик | завязан не на тот счётчик |
|
||||||
|
| переширокий матчинг по имени | слишком грубое сопоставление по имени, слишком много слабых совпадений |
|
||||||
|
| костыль | временное решение, обходной путь — и в чём именно |
|
||||||
|
| просело, отвалилось | стало медленнее на столько-то, перестало отвечать |
|
||||||
|
|
||||||
|
Проверка: **фраза требует, чтобы читатель додумал образ, — заменяется
|
||||||
|
буквальным описанием того, что происходит.**
|
||||||
|
|
||||||
|
8. **Термин, которого нет в документах проекта, вводится одной строкой или не
|
||||||
|
употребляется.** Термин, не встречающийся ни в паспорте, ни в архитектуре, ни
|
||||||
|
в конвенциях, — свой словарь у отдельной записи, а это самый дешёвый способ
|
||||||
|
сделать беклог нечитаемым для того, кто вернётся к нему через квартал.
|
||||||
|
Заменять незнакомый термин догадкой нельзя: догадка о предметной области
|
||||||
|
дороже непонятного слова, потому что выглядит понятной.
|
||||||
|
|
||||||
|
**Слово, занятое в другом смысле, — то же нарушение.** Термин, который в
|
||||||
|
одном документе проекта значит одно, а здесь другое, ломает оба.
|
||||||
|
|
||||||
|
9. **Имя файла — английское слово по сути, а не транслит.** `queue-as-table`, а
|
||||||
|
не `ochered-tablicej`; `move-parse-strict`, а не `razbor-hoda`. Транслит
|
||||||
|
нечитаем тому, кто ищет по смыслу, и не сокращается, а имя стоит в ссылках,
|
||||||
|
коммитах и путях, которые набирают руками. Переименование — **перенос ссылок
|
||||||
|
одним проходом**, а не правка одного файла.
|
||||||
|
|
||||||
|
<!-- /дом: язык-правила -->
|
||||||
|
|
||||||
|
## Порог правки
|
||||||
|
|
||||||
|
<!-- дом: порог-правки -->
|
||||||
|
|
||||||
|
**Правка без нарушенного правила не делается.** Текст, переписанный «чтобы
|
||||||
|
звучало лучше», обесценивает список замечаний: когда половина из них вкусовая,
|
||||||
|
перестают читать весь список, и вместе с ним пропадают настоящие находки.
|
||||||
|
Сомневаешься — не правь. Формулировка, которая просто **не твоя**, — не находка.
|
||||||
|
|
||||||
|
**Систематичность нарушения — не довод в его пользу.** Одна и та же ошибка в
|
||||||
|
пяти файлах не становится «принятым стилем»: чаще это значит, что правило не
|
||||||
|
применялось вовсе, — и находка тем важнее. «Так сделано везде» годится как
|
||||||
|
основание для **одной находки на весь набор** («правило N нарушено в пяти
|
||||||
|
записях, перечень: …»), но не как основание промолчать. Принятым считается
|
||||||
|
только то, что назвал зовущий или что записано в конвенциях проекта.
|
||||||
|
|
||||||
|
<!-- /дом: порог-правки -->
|
||||||
|
|
||||||
|
И обратное: язык правится **по ходу той операции, которая записи касается**.
|
||||||
|
Беклог не переписывают ради языка.
|
||||||
|
|
||||||
|
## Доклад вычитки
|
||||||
|
|
||||||
|
Не правило языка, а **контракт прохода**: форма, в которой находка приходит к
|
||||||
|
человеку. Живёт здесь потому, что проходов вычитки несколько — по одному на
|
||||||
|
плагин, — и разойтись формой они не должны.
|
||||||
|
|
||||||
|
<!-- дом: вычитка-доклад -->
|
||||||
|
|
||||||
|
Находки по одной, в порядке важности: залог и оценки → жаргон и англицизмы →
|
||||||
|
стоп-слова. Первые меняют, **что** читатель понимает; последние — только сколько
|
||||||
|
он на это тратит.
|
||||||
|
|
||||||
|
```
|
||||||
|
<файл>
|
||||||
|
правило: <номер и короткое имя>
|
||||||
|
сейчас: <как написано>
|
||||||
|
предложение: <готовая формулировка, подставляемая как есть>
|
||||||
|
почему: <одна фраза>
|
||||||
|
```
|
||||||
|
|
||||||
|
В конце — **границы покрытия**: сколько файлов просмотрено из скольких, какие не
|
||||||
|
смотрел и почему, и по чему проверялись термины (документы проекта названы или
|
||||||
|
нет). Отчёт без этой строки читается как «всё вычитано», не сообщая, какая часть
|
||||||
|
осталась нетронутой. Туда же — строка «замечено не по моей части»; машинно
|
||||||
|
проверяемое в неё **не идёт**.
|
||||||
|
|
||||||
|
Ничего не нашёл — так и скажи одной строкой. Пустой доклад с границами покрытия
|
||||||
|
полезнее выдуманной находки.
|
||||||
|
|
||||||
|
<!-- /дом: вычитка-доклад -->
|
||||||
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# Сопровождение и эксплуатация
|
||||||
|
|
||||||
|
**Это дом.** Словарь «чем держат проект» назван в трёх местах трёх разных
|
||||||
|
плагинов: секция `Сопровождение` в роадмапе (`av-dev-tasks`), раздел
|
||||||
|
«Эксплуатация» в `architecture.md` (`av-dev-docs`) и тема ревью `operations`
|
||||||
|
(`av-dev-code`). Ни один из трёх им не владеет, поэтому дом стоит снаружи, а
|
||||||
|
плагины везут копии.
|
||||||
|
|
||||||
|
Три перечня «чем держат проект» уже разъезжались — на «метриках и логах» против
|
||||||
|
«мониторинга», — и разъехались молча. Отсюда дословная копия вместо ссылки.
|
||||||
|
|
||||||
|
<!-- дом: сопровождение-словарь -->
|
||||||
|
|
||||||
|
Одна тема живёт в трёх местах, и путать их слова нельзя.
|
||||||
|
|
||||||
|
**Сопровождение** — всё, чем держат проект: инструмент и сборка, процесс,
|
||||||
|
выкладка, метрики и логи, инфраструктура, дежурство. **Эксплуатация** — его
|
||||||
|
часть: работа системы на проде. Целое и часть, и никогда наоборот.
|
||||||
|
|
||||||
|
| Место | Уровень | Что там |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `ROADMAP.md`, секция `Сопровождение` | план | **работы**, которые собираемся делать: цели и их задачи |
|
||||||
|
| `architecture.md`, раздел «Эксплуатация» | состояние | **как устроено сейчас**: где работает, что рядом, кто перезапускает |
|
||||||
|
| тема ревью `operations` | оптика | **чем проверяем**: «это упало через неделю на проде» |
|
||||||
|
|
||||||
|
Слово **«поддержка» не употребляется вовсе** — в нём слышится помощь
|
||||||
|
пользователю, а это другая работа.
|
||||||
|
|
||||||
|
**Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
|
||||||
|
сообщает о своём состоянии» — возможность приложения, её место среди прочих
|
||||||
|
целей: наблюдает пользователь сервиса. «Дежурный видит состояние на одном
|
||||||
|
экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в разные
|
||||||
|
секции роадмапа, и это верно — секции отвечают на разные вопросы.
|
||||||
|
|
||||||
|
<!-- /дом: сопровождение-словарь -->
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Граница между плагинами
|
||||||
|
|
||||||
|
**Это дом.** Правило обращения к соседнему плагину нужно всем, кто зовёт чужой
|
||||||
|
скилл, — а таких скиллов больше половины всех, и ни один плагин правилом не
|
||||||
|
владеет. (Числа здесь нет намеренно: оно уже дважды протухало за один день.) Поэтому дом стоит снаружи, а плагины везут **копии**, помеченные
|
||||||
|
разметкой `copies.py`.
|
||||||
|
|
||||||
|
Правится **здесь**. Копия, поправленная у себя, — расхождение, а не правка.
|
||||||
|
|
||||||
|
Дом заведён по замеру, а не на всякий случай. К моменту раскола правило стояло в
|
||||||
|
пяти местах в пяти редакциях:
|
||||||
|
|
||||||
|
| Где стояло | Довод | Ветка «не разрешился» |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `task-pipeline` | устаревшая проектная копия | нет |
|
||||||
|
| `task-batch` | то же | нет |
|
||||||
|
| `review-pipeline` | вшито в пункт про удаление проектных копий | нет |
|
||||||
|
| `openspec` | путём в чужое дерево — никогда | есть |
|
||||||
|
| `canon` | — | есть |
|
||||||
|
|
||||||
|
Имена с тех пор изменились — `task-pipeline` стал `resolve`, `review-pipeline` —
|
||||||
|
`review`, `task-batch` удалён, — но замер относится к местам, а не к названиям.
|
||||||
|
|
||||||
|
Два разных довода, и ни в одном месте не было обоих. Три места из пяти молчали о
|
||||||
|
том, что делать, когда вызов не разрешился, — то есть о единственном, ради чего
|
||||||
|
правило и написано.
|
||||||
|
|
||||||
|
**Что в дом не идёт: чем оборачивается отсутствие конкретного соседа.** «Нет
|
||||||
|
`av-dev-tasks` — учёт остаётся владельцу» знает только конвейер; «нет конвейера —
|
||||||
|
`docs.py` о каталоге `openspec/` молчит» знает только канон. Правило общее,
|
||||||
|
последствие местное, и держать последствия здесь значило бы завести дом, который
|
||||||
|
знает про всех своих потребителей.
|
||||||
|
|
||||||
|
<!-- дом: граница-плагинов -->
|
||||||
|
|
||||||
|
Плагины `av-dev` ставятся порознь, и ни один не вправе считать, что сосед на
|
||||||
|
месте.
|
||||||
|
|
||||||
|
**Чужой скилл зовётся полным именем** — `av-dev-docs:canon`, `av-dev-tasks:tasks`,
|
||||||
|
`av-dev-code:review`. Короткое имя может разрешиться в устаревшую проектную копию
|
||||||
|
из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении.
|
||||||
|
|
||||||
|
**Путь в дерево чужого плагина не пишется никогда.** `$CLAUDE_PLUGIN_ROOT` ведёт
|
||||||
|
только в свой плагин; вычисленный от него путь к соседу либо не откроется, либо
|
||||||
|
откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он
|
||||||
|
прочитает его сам.
|
||||||
|
|
||||||
|
**Вызов не разрешился — плагина в проекте нет.** Это исход, а не поломка: назови
|
||||||
|
строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя,
|
||||||
|
пропуск неотличим от сделанного; выдумывать обходной путь нельзя тоже.
|
||||||
|
|
||||||
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
||||||
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
|
||||||
|
<!-- /дом: граница-плагинов -->
|
||||||
Reference in New Issue
Block a user