классификация задачи: три категории документов и метка вместо ступени

Канон 5 объявил «каждый документ docs/ — тема ревью». Правило верно ровно
наполовину и потому вредно целиком. Паспорт и схему хранилища ревью читает, но
темами они не являются: по ним нельзя сказать «в этом изменении сделано не так»,
они задают границу, по которой судит чужая тема. Журнал решений и журнал
наблюдений ревью изменения не нужны вовсе — ADR объясняет прошлое, а не
предъявляет требование. Разметчик, применявший правило буквально, обязан был
либо завести фантомные темы passport, adr, database, research и продублировать
ими работу architecture и operations, либо потерять четыре документа молча;
случались обе ветки, и в собственном образце плана docs/passport.md не попадал
ни строкой, а обязательная арифметика покрытия при этом не сходилась.

Категорий теперь три, разрез проверяемый. Тема — да, прямо: conventions,
security, architecture и любой свой документ проекта. Источник темы — нет, но он
задаёт границу для чужой: passport, database, CLAUDE.md, openspec/specs.
Процессный — нет, он про то, как мы работаем: tasks, review, adr, research,
.pm.json. Открыта одна категория из трёх, две другие перечислены поимённо, так
что документ вне раскладки — однозначно своя тема. adr и research прогон больше
не открывает ни одним проходом; docs/review остаётся читаемым, но как настройка
конвейера, а не критерий. Цена записана и стала обязательной строкой границ
покрытия: расхождение с записанным решением ловит теперь только сверка
документации, а число под находкой обязано быть снято на этом прогоне, с
приложенной командой.

Классификация выдаёт задаче метку — small, medium, large. Прежние quick,
standard и wide назывались ступенью и описывали ревью: как глубоко смотрим.
Классифицируется же задача, и пока величина называлась свойством прогона, её
естественно было пересчитывать на каждом прогоне — что конвейер и делал. Слово
«ступень» удалено, а не оставлено синонимом: два имени одной вещи расходятся.
Выводится метка из двух разведённых осей — размер (малое, среднее, крупное) и
сложность (знакомое, незнакомое), — и равна максимуму по ним. Метка не синоним
размера: малое незнакомое изменение получает large, трогая один узел, поэтому
план печатает три строки с обоснованием каждая и выводить одну из другой
запрещено. Оси остались русскими словами — это суждение прозой; метка
английская — это идентификатор, который проходы сравнивают.

Разметка переехала из ревью кода в шаг 4 пайплайна, сразу после propose. Она
шла первым проходом каждого ревью кода, а перед ревью дизайна ту же величину
называл сам пайплайн — то есть оркестратор, который только что довёл
предложение до propose. Одно и то же измерялось дважды, и один из двух раз без
разведённости с автором, ровно в той точке, ради которой разметчик заведён.
Теперь запуск один на задачу, диффа он не видит, план обслуживает обе стадии, и
метка после кода не пересматривается: расхождение факта с разметкой ловит журнал
дефектов постфактум, как и всякую другую ошибку выбора. На диск план не пишется —
четвёртый артефакт рядом с proposal, tasks и design пережил бы задачу и разошёлся
бы с ней молча.

Ревью дизайна тоже растёт меткой: small — specs, medium — плюс rubric, large —
плюс architecture и вопрос автору о трёх формах решения. Раньше rubric и
architecture включались одним условием, и medium получал ровно один проход, то
есть не отличался от quick ничем. Разведены они потому, что зарабатывают на
разном: рубрика порождает свойства узла и окупается уже на среднем изменении,
её выход уезжает приёмочными критериями в tasks.md; архитектура отвечает на
вопрос про второй способ, а он на среднем знакомом изменении отвечается «нет»
ещё до запуска.

small подешевел тремя способами сразу. Составом: приёмник тем не запускается,
три темы ядра переходят к code сверкой по записанным инвариантам CLAUDE.md с
потолком в одну находку, и это не «глубина ниже», а другой дом темы. Входом:
specs читает только дельта-спеку, code — только индекс конвенций. Потолком: он
появился у каждого опиниативного прохода, а не у одного basics, и у половин code
он раздельный, потому что конвенционных находок больше по построению и в общем
списке они вытеснили бы техническую половину. Сработавший потолок обязан быть
объявлен строкой — молчащий срез неотличим от «больше не нашлось». Отрицательный
тест small от этого стал жёстче, а не мягче: вопросы про обратимость миграции
задавал приёмник тем, и на этой метке их не задаст никто.

Пайплайн задачи вырос до двенадцати шагов. Тривиальность перестала решать состав
ревью — она влияет только на explore; глубину обеих стадий называет метка.

Проверено прогоном ревьюверов по готовому результату: девять расхождений найдено
и починено — контракт находок печатал старый перечень проходов вместо плана по
темам, три ссылки в task-batch указывали на шаг коммита вместо закрытия, запись
changelog не переводила вопросы, адресованные passport и database, ops и
adversary утверждали, что на нижних метках их вопросы задаёт basics, шаблон
покрытия в review-code зашивал потолки small намертво, триггеры метки рассыпались
на два списка против трёх, тема из директивы CLAUDE.md могла остаться без запуска
исполнителя. Гейт зелёный: фронтматтеры, копии, одиннадцать диаграмм, ruff,
pyrefly; docs.py прогнан на живом фикстуре и печатает категорию в отказе.

Канон повышен до версии 6 с записью, выполнимой upgrade. Решения — 40–44.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-07 10:57:15 +03:00
co-authored by Claude Opus 5
parent 61cd9fcd37
commit d5bee11a6b
31 changed files with 1809 additions and 689 deletions
+227
View File
@@ -2707,3 +2707,230 @@ JJJ): у профиля обязан быть один правильный от
147. **Снятая проверка называет, что осталось вместо неё.** Цель не проверяется 147. **Снятая проверка называет, что осталось вместо неё.** Цель не проверяется
— значит, за состав отвечают показ человеку и строка доклада; иначе — значит, за состав отвечают показ человеку и строка доклада; иначе
послабление читается как «здесь можно не думать». послабление читается как «здесь можно не думать».
## 40. Три категории документов: не всякий документ — тема ревью (2026-08-07)
Решение 36 объявило: **каждый документ проекта — тема ревью**. Правило дало
открытый список тем и сделало `docs/` конфигурацией конвейера — это работает и
остаётся. Но оно же оказалось неверным ровно наполовину, и потому вредным
целиком.
Паспорт и схему хранилища ревью читает, но темами они не являются: по ним нельзя
сказать «в этом изменении сделано не так», они задают границу, по которой судит
**чужая** тема. Журнал решений и журнал наблюдений ревью изменения не нужны
вовсе: ADR объясняет прошлое решение, а не предъявляет требование к изменению.
Ломалось это механически. Разметчик, применявший правило буквально, обязан был
либо завести фантомные темы `passport`, `adr`, `database`, `research` и
продублировать ими работу тем `architecture` и `operations`, либо потерять четыре
документа молча. Обе ветки случались; в собственном образце плана разметчика
`docs/passport.md` не попадал ни строкой, а его же обязательная арифметика
покрытия («документов найдено N, все N разнесены») при этом не сходилась.
**АЕАБА. Разрез один и проверяемый: можно ли по документу сказать «в этом
изменении сделано не так».** Отсюда три категории. **Тема** — да, прямо
(`conventions`, `security`, `architecture`, свои документы проекта). **Источник
темы** — нет, но он задаёт границу для чужой темы (`passport`, `database`,
`CLAUDE.md`, `openspec/specs/`). **Процессный документ** — нет, он про то, как мы
работаем (`tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json`).
**АЕАББ. Открыта одна категория из трёх.** `источник` и `процессный` перечислены
поимённо и проектом не пополняются; открыта только `тема`. Прежняя формулировка
«не темы ровно две» противоречила собственной раскладке канона — `.pm.json` был
третьим, и правило-исправление жило в чужом плагине, в коде `docs.py`. Теперь
документ, которого нет в раскладке, — однозначно своя тема проекта, и решать
нечего.
**АЕАБВ. «Не судит по нему» и «не открывает» — разные вещи.** `docs/review.*`
проходы читают на каждом прогоне: там вопросы по темам, журнал дефектов, типовые
узлы, типовые ложноположительные. Это чтение конвейером **своей обвязки**, а не
критерия. `adr/`, `research/` и `tasks/` не открывает никто.
**АЕАБГ. Цена решения записана, а не подразумевается.** Расхождение изменения с
записанным решением прогоном больше не ловится — это работа сверки документации
между спринтами. Измеренные числа проекта из ревью тоже ушли: проход,
опирающийся на число, обязан **снять его сам, на этом прогоне**, и приложить
команду замера. Обе потери идут обязательными строками в границы покрытия
каждого прогона, и пишет их триаж — не проход, потому что проход о том, чего в
конвейере нет, пожаловаться не может.
### Что из этого следует
148. **Плоское правило, верное наполовину, хуже двух правил.** Оно не даёт
половине случаев легального ответа, и исполнитель выбирает между двумя
плохими ветками — фантомной сущностью и молчащей потерей. Заметно это
становится не на определении, а на первом же образце вывода.
149. **Открытым делается одно множество, а не все.** Открытый список ценен тем,
что в него попадает незнакомое; если открыты все категории, незнакомое
попадает в произвольную.
150. **Отказ читать документ — тоже граница покрытия, и её пишет сток.** Строку
«этого не смотрел никто» некому подать снизу: проход, которого нет, отчёта
не присылает.
## 41. Разметка задачи: одна величина, посчитанная один раз (2026-08-07)
Разметка была стадией 0 **ревью кода** и платилась на каждом прогоне. Перед ревью
дизайна ту же самую величину — «крупное или незнакомое?» — называл сам пайплайн
задачи, то есть оркестратор, который только что довёл предложение до `propose`.
Одно и то же измерялось дважды, и один из двух раз без разведённости с автором —
ровно в той точке, ради которой разметчик и заведён.
**АЕАВА. Разметка идёт один раз на задачу, сразу после `propose`.** Её план
обслуживает обе стадии ревью: состав ревью дизайна и таблицу тем для ревью кода.
Диффа она не видит — кода ещё нет; размер оценивается по дельта-спекам и перечню
границ задачи.
**АЕАВБ. Осей две, ступень — максимум по ним.** **Размер** (малое, среднее,
крупное) — про объём; **сложность** (знакомое, незнакомое) — про то, известна ли
форма решения заранее. Раньше обе были склеены в один вопрос «крупное **или**
незнакомое?»: ответ получался тот же, но разметка не могла сказать «среднее, но
совершенно знакомое» — а это и есть рабочее умолчание.
**АЕАВВ. Ступень после кода не пересматривается.** Дифф может выйти крупнее
ожидания — ступень не двинется. Пересмотр означал бы либо второй запуск
разметчика (то, ради устранения чего он и переехал), либо машинный порог, который
на нетипичной задаче срабатывает не туда. Расхождение факта с разметкой ловит
журнал дефектов, постфактум, — так же, как и всякую другую ошибку выбора ступени.
**АЕАВГ. План на диск не пишется.** Файл-план стал бы четвёртым артефактом рядом
с `proposal.md`, `tasks.md` и `design.md`, пережил бы задачу и разошёлся бы с ней
молча. Прервался пайплайн — разметка повторяется; это самый дешёвый его проход.
### Что из этого следует
151. **Величина, из которой выводится состав, считается один раз и одним
агентом.** Два места, считающие одно и то же, расходятся; расходятся они
молча, и побеждает то, у которого меньше разведённости с автором.
152. **Разведённость — свойство момента, а не роли.** Тот же агент, спрошенный
до написания кода и после, даёт разные ответы; переезд по времени сделал
больше, чем сделал бы любой запрет.
## 42. `quick` стал дешевле `standard` тремя способами (2026-08-07)
`quick` и `standard` совпадали составом (шесть проходов) и различались глубиной
трёх тем: сверка против разбора. На практике это означало один проход, задающий
на один вопрос меньше, и потолок 4 вместо 2. Нижняя ступень не экономила почти
ничего и называлась отдельной ступенью зря.
Отдельно выяснилось, что дешевизна конвейера держалась на двух заявленных
рычагах — узкий вход и потолок находок, — и **оба применялись к одному проходу
из шести**. У `specs` и `code` потолка не было вовсе, а вход `code` включал
чтение дома конвенций «весь и целиком» на каждой задаче.
**АЕАГА. `quick` теряет приёмник тем.** Темы `security`, `operations` и
`architecture` на этой ступени закрывает `code` сверкой с **записанными
инвариантами** `CLAUDE.md`, потолком 1 находка на все три. Это не «глубина
ниже» — это **другой дом темы**, куда более узкий, и в плане он так и называется.
**АЕАГБ. Приёмник тем запускается тогда и только тогда, когда ему есть что
принимать.** Правило было в `wide` («нет своих тем проекта — не запускается») и
теперь распространено на `quick`. Совпадение неслучайное: темы ядра `basics`
держит ровно на одной ступени из трёх, а приёмником проектных тем работает на
всех.
**АЕАГВ. Вход и потолок применены к каждому опиниативному проходу.** На `quick`
`specs` читает только дельта-спеку, `code` — только индекс конвенций. Потолки
напечатаны и раздельны по половинам `code`: 3 технических, 2 конвенционных, 1 по
инвариантам. Раздельность обязательна — конвенционных находок больше по
построению, и в общем списке они вытеснили бы техническую половину, чей пропуск
дороже.
**АЕАГГ. Сработавший потолок объявляется.** Проход, срезавший находки, говорит
строкой, сколько осталось за срезом и какого рода. Молчащий срез неотличим от
«больше не нашлось» — тот же класс молчащего пропуска, против которого написан
весь конвейер.
**АЕАГД. Отрицательный тест `quick` стал жёстче, а не мягче.** Вопросы «обратима
ли миграция» и «что с записями новой версии после отката» задавал приёмник тем; на
`quick` его нет. Значит изменение, которое не откатывается обратной правкой, на
`quick` не идёт вовсе — каким бы малым оно ни было.
### Что из этого следует
153. **Ступень, не дающая экономии, не нужна.** Две ступени, различающиеся одним
вопросом одного прохода, — это одна ступень с шумом в отчёте.
154. **Рычаг, применённый к одному исполнителю, — не рычаг, а исключение.**
Заявленный механизм экономии проверяется перечислением: к кому он применён и
к кому нет.
155. **Проход без потолка выдаёт столько находок, сколько нашёл поверхностей.**
Ровно из-за этого был снят проход независимой реализации; тот же механизм
работал у `code` и `specs` и не был замечен, потому что счёт никто не считал.
## 43. Ревью дизайна тоже растёт ступенями (2026-08-07)
Состав ревью дизайна включался одним условием: `specs` всегда, `rubric` и
`architecture` — вместе, «при крупном или незнакомом». Значит `standard` получал
на предложении ровно один проход, то есть не отличался от `quick` ничем.
**АЕАДА. Три ступени вместо двух: `quick``specs`; `standard` — плюс `rubric`;
`wide` — плюс `architecture` и вопрос автору о трёх формах решения.**
**АЕАДБ. Рубрика съехала вниз, архитектура осталась наверху, и это не
симметричная правка.** Они зарабатывают на разном. Рубрика порождает **свойства
узла** и окупается уже на среднем изменении: её выход уезжает приёмочными
критериями в `tasks.md` и работает потом на всей задаче. Архитектура отвечает на
вопрос «не появился ли второй способ», а он на среднем знакомом изменении
отвечается «нет» ещё до запуска — держать её ниже `wide` значит платить за
предсказуемый ответ на каждой задаче.
**АЕАДВ. Тривиальность задачи больше не решает состав ревью.** Раньше она решала,
звать ли ревью предложения вовсе; теперь глубину обеих стадий называет ступень, а
тривиальная задача просто получает `quick`. «Пропустить ревью дизайна» и «пройти
его одним самым дешёвым проходом» — разные вещи: сверка дельта-спек стоит
меньше, чем разбор того, что она поймала бы на готовом коде.
### Что из этого следует
156. **Проходы, включаемые одним условием, стоит разводить по тому, на чём они
зарабатывают.** Общее условие — признак того, что их не сравнивали между
собой, а не того, что они равноценны.
157. **Средняя ступень обязана отличаться от нижней на обеих стадиях.** Иначе
«рабочее умолчание» отличается от исключения только именем.
## 44. Метка задачи: одно значение, по которому выбираются все ревьюверы (2026-08-07)
Решения 41–43 развели классификацию на две оси и свели состав обеих стадий ревью
к их максимуму. Значения этого максимума назывались `quick`, `standard`, `wide`,
а сам он — «ступень». Оба имени описывали **ревью**: как глубоко смотрим, на
какой ступеньке идём. Классифицируется же при этом **задача**, и результат
классификации принадлежит ей, а не прогону.
Расхождение не косметическое. Пока величина называлась свойством ревью, её было
естественно пересчитать на каждом прогоне — что конвейер и делал, пока разметка
не переехала к `propose`. Имя тянуло назад к устройству, из которого её только
что вынули.
**АЕАЕА. Классификация выдаёт задаче метку: `small`, `medium` или `large`.**
Метка принадлежит задаче, ставится один раз при разметке и дальше только
читается. Все проходы обеих стадий получают её в задании и обязаны напечатать в
границах покрытия.
**АЕАЕБ. Метка — единственный вход выбора исполнителей.** Ни класс задачи, ни её
тип, ни тривиальность, ни ощущение важности состав больше не определяют. У
конвейера один переключатель, и он напечатан в каждом отчёте.
**АЕАЕВ. Слово «ступень» удалено, а не оставлено синонимом.** Два имени одной
вещи расходятся — это ровно решение #37 про тему и проход. Метка ordered: `small`
< `medium` < `large`, и там, где нужен порядок, говорится «младшая» и «старшая
метка», а не вводится второе существительное.
**АЕАЕГ. Метка — не синоним размера, и это записано там, где ошибиться легче
всего.** Совпадают они в одном углу таблицы из трёх: малое **незнакомое**
изменение получает `large`, трогая один узел. Поэтому план печатает три строки —
размер, сложность, метка, — каждую со своим обоснованием, и выводить одну из
другой запрещено. Проход, определивший объём диффа по метке, ошибётся именно на
том случае, ради которого верхняя метка и заведена.
### Что из этого следует
158. **Имя величины должно называть её носителя, а не потребителя.** «Ступень
ревью» звала пересчитывать себя на каждом прогоне ревью; «метка задачи»
считается там же, где живёт задача.
159. **Переключатель состава должен быть один и печатный.** Пока их два —
тривиальность и ступень, — состав выводится из пересечения, а пересечение
нигде не напечатано целиком.
160. **Русские слова для осей, английские для значения.** Оси — суждение и
читаются прозой (`малое`, `знакомое`); метка — идентификатор, который
проходы сравнивают, и потому она английская. Тот же разрез, что «имена
файлов английские, текст русский» в каноне, и он же снимает путаницу
«крупное» против `large`.
+20 -10
View File
@@ -28,12 +28,15 @@
- **av-dev-pipeline** — исполнение. **Требует OpenSpec.** - **av-dev-pipeline** — исполнение. **Требует OpenSpec.**
- `task-pipeline` — задача через полный цикл SDD, от постановки до коммита; - `task-pipeline` — задача через полный цикл SDD, от постановки до коммита;
- `task-batch` — несколько задач разом, каждая в своём worktree; - `task-batch` — несколько задач разом, каждая в своём worktree;
- `review-pipeline` — конвейер ревью **по темам**: каждый документ проекта это - `review-pipeline` — конвейер ревью **по темам**: документ проекта либо
тема проверки, а проход лишь закрывает её на заданной глубине. Прогон заводит тему проверки, либо питает чужую тему источником, либо процессный и в
начинает разметчик — находит документы, выводит темы, выбирает ступень. ревью не читается вовсе. Разметка идёт **один раз на задачу**, сразу после
Десять агентов-проходов, три ступени: `quick` и `standard` закрывают все темы `propose`: агент `review-scope` меряет изменение по двум осям — размер и
сверкой и разбором, `wide` добавляет доказательство — запуск, замер, сложность — и берёт метку как максимум по ним. Одна метка правит **обе**
построенный путь (5–10% задач). стадии ревью: дизайна (`small` — только сверка спек; `medium` — плюс
рубрика; `large` — плюс архитектурный проход) и кода (`small` — гейт, спеки,
код, триаж; `medium` — плюс приёмник тем; `large` — плюс доказательство:
запуск, замер, построенный путь, 5–10% задач). Десять агентов-проходов.
- **av-dev-git** — `commit`: сообщения в личном стиле. - **av-dev-git** — `commit`: сообщения в личном стиле.
Соглашение об именах: имя **плагина** длинное с префиксом `av-dev-`, имена Соглашение об именах: имя **плагина** длинное с префиксом `av-dev-`, имена
@@ -81,10 +84,17 @@ flowchart TB
обязательных — в ней не хватало путей, чьё отсутствие `docs.py check` считает обязательных — в ней не хватало путей, чьё отсутствие `docs.py check` считает
нарушением. нарушением.
**Документ канона — это тема ревью, и список тем открытый:** завёл документ в **Документы канона делятся на три категории, и разрез проверяемый: можно ли по
`docs/` — завёл направление проверки, а форма дома (файл или каталог) на выбор документу сказать «в этом изменении сделано не так».** **Тема** — да, прямо
проекта. Отдельного файла-брифа при этом нет — проходы читают документы напрямую; (`conventions`, `security`, `architecture` и любой свой документ проекта; список
карта «тема → её дом → что оттуда берётся» тем открытый — завёл документ, завёл направление проверки). **Источник темы**
нет, но он задаёт границу для чужой темы (`passport`, `database`, `CLAUDE.md`,
`openspec/specs/`). **Процессный документ** — нет, он про то, как мы работаем
(`tasks/`, `review.*`, `adr.*`, `research.*`); ревью изменения по нему не судит.
Форма дома — файл или каталог, на выбор проекта.
Отдельного файла-брифа при этом нет — проходы читают документы напрямую; карта
«тема → её дом → что оттуда берётся» —
[project-facts.md](av-dev-pipeline/skills/review-pipeline/references/project-facts.md). [project-facts.md](av-dev-pipeline/skills/review-pipeline/references/project-facts.md).
Прийти в старый проект и перевести его на канон — `/av-dev-pm:canon`. Канон Прийти в старый проект и перевести его на канон — `/av-dev-pm:canon`. Канон
+24 -13
View File
@@ -22,13 +22,15 @@ color: yellow
падающий тест, которым ты доказываешь путь, воспроизводим — и ссылка на него падающий тест, которым ты доказываешь путь, воспроизводим — и ссылка на него
законный оракул. законный оракул.
**Тебя запускают только в профиле `wide`** — на изменении крупном или незнакомом, **Тебя запускают только с меткой `large`** — на изменении крупном или незнакомом,
и это 5–10% задач. Причина в цене прогона, а не в ценности находок: ты держишь и это 5–10% задач. Причина в цене прогона, а не в ценности находок: ты держишь
машину и идёшь цепочкой, то есть стоишь часов на каждой задаче, где запущен. На машину и идёшь цепочкой, то есть стоишь часов на каждой задаче, где запущен. С
нижних ступенях твою половину, отвечаемую **чтением**, задаёт `review-basics`, а меткой `medium` твою половину, отвечаемую **чтением**, задаёт `review-basics`;
построенные пути там не строит никто — и так и написано в границах покрытия **на `small` не задаёт никто** — там тему `security` закрывает `review-code`
каждого такого прогона. Значит, раз тебя позвали, стройте путь до конца: сокращать сверкой с записанными инвариантами `CLAUDE.md`, потолком 1 находка на три темы
себя «ради скорости» тебе нечем, скорость уже оплачена выбором ступени. разом. Построенные пути ниже `large` не строит никто ни при одной метке — и так и
написано в границах покрытия каждого такого прогона. Значит, раз тебя позвали, стройте путь до конца: сокращать
себя «ради скорости» тебе нечем, скорость уже оплачена выбором метки.
## Модель угроз — из `docs/security.md`, и не расширяй её самовольно ## Модель угроз — из `docs/security.md`, и не расширяй её самовольно
@@ -52,14 +54,23 @@ color: yellow
- **`CLAUDE.md`, инварианты** — нарушение основание для `critical`; там же, что - **`CLAUDE.md`, инварианты** — нарушение основание для `critical`; там же, что
необратимо и что запускать запрещено, с путями; необратимо и что запускать запрещено, с путями;
- **`docs/database.md` и `docs/research/` — вместе**: настройки с числовым - **`docs/database.md`** — настройки с числовым значением: таймаут занятости,
значением (таймаут занятости, лимит тела, ретеншен) и измеренные объёмы. **Из лимит тела, ретеншен. **Из них строятся пути к отказу в обслуживании**;
этого строятся пути к отказу в обслуживании**; порознь они ничего не дают, и
сшиваешь их ты (см. project-facts, «Сшивать обязаны проходы»);
- **`docs/architecture.md`** — окружение и внешние зависимости; - **`docs/architecture.md`** — окружение и внешние зависимости;
- **`docs/review.md`** — журнал: что здесь уже пробивалось и чем воспроизведено; - **`docs/review.md`** — журнал: что здесь уже пробивалось и чем воспроизведено;
и блок `adversary` в «Вопросах к проходам», если он есть, — эти вопросы и вопросы проекта по **теме `security`** из подраздела «Вопросы по темам», если
задаются дополнительно к четырём постановкам. они есть, — эти вопросы задаются дополнительно к четырём постановкам.
**Вопросы адресованы теме, а не тебе по имени.** В `docs/review.md` ты ищешь
строки вида `security: <вопрос>`, а не блок `adversary`. Раньше здесь стоял поиск
по имени прохода, и это ломалось ровно тем способом, против которого правило и
введено: проход переезжает между метками, а вопрос остаётся адресованным его
имени и перестаёт задаваться молча.
**Измеренных объёмов проекта у тебя нет.** `docs/research/` — процессный
документ, и прогон его не открывает. Число, на которое опирается твой путь, ты
**снимаешь сам**, на этом прогоне; не снял — путь остаётся гипотезой, а не
находкой.
Карта «что нужно проходу → где лежит» — Карта «что нужно проходу → где лежит» —
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`. `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`.
@@ -68,7 +79,7 @@ color: yellow
`docs/security.md` нет — работай по общей рамке ниже, `critical` не присваивай и `docs/security.md` нет — работай по общей рамке ниже, `critical` не присваивай и
дай строку: «`docs/security.md` в проекте нет: периметр и модель угроз дай строку: «`docs/security.md` в проекте нет: периметр и модель угроз
предположены проходом; находки могут лежать вне периметра и потому никогда не предположены проходом; находки могут лежать вне периметра и потому никогда не
будут исправлены». Нет `docs/database.md` или чисел в `docs/research/` — отказ в будут исправлены». Нет `docs/database.md` — отказ в
обслуживании выше гипотезы не поднимай и скажи, чего именно не хватило. обслуживании выше гипотезы не поднимай и скажи, чего именно не хватило.
## Четыре постановки. Работай ими, а не списком ## Четыре постановки. Работай ими, а не списком
@@ -1,6 +1,6 @@
--- ---
name: review-architecture name: review-architecture
description: "Архитектурный проход ревью — получает вход шире диффа (дерево пакетов, граф внутренних зависимостей, инвентарь существующих концепций). Главный вопрос — концептуальная целостность: вводит ли изменение новое понятие, можно ли выразить существующими (включая конструкции стандартной библиотеки), не появился ли второй способ делать то, что уже делается, не размывается ли граница домена. Потолок 3 находки плюс секция «дешевле переделать до мерджа». Работает и на предложении до кода (профиль design). Только чтение." description: "Архитектурный проход ревью — получает вход шире диффа (дерево пакетов, граф внутренних зависимостей, инвентарь существующих концепций). Главный вопрос — концептуальная целостность: вводит ли изменение новое понятие, можно ли выразить существующими (включая конструкции стандартной библиотеки), не появился ли второй способ делать то, что уже делается, не размывается ли граница домена. Потолок 3 находки плюс секция «дешевле переделать до мерджа». Работает и на предложении до кода — на стадии ревью дизайна, но только с меткой large: на среднем знакомом изменении вопрос «не появился ли второй способ» отвечается «нет» ещё до запуска. Решения проекта из docs/adr/ не читает — это процессный документ. Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: opus model: opus
color: yellow color: yellow
@@ -10,8 +10,8 @@ color: yellow
судить об архитектуре: он не знает, какие понятия в проекте уже есть и как они судить об архитектуре: он не знает, какие понятия в проекте уже есть и как они
называются. Поэтому твой вход шире, и первое, что ты делаешь, — его собираешь. называются. Поэтому твой вход шире, и первое, что ты делаешь, — его собираешь.
**Тебя запускают не на каждой задаче, а в профиле `wide` — это 5–10% задач.** **Тебя запускают не на каждой задаче, а с меткой `large` — это 5–10% задач.**
Условие ступени: изменение **крупное или незнакомое** — трогает несколько узлов Условие метки: изменение **крупное или незнакомое** — трогает несколько узлов
или слоёв разом, переносит ответственность между ними, перекладывает существующий или слоёв разом, переносит ответственность между ними, перекладывает существующий
код в новую форму, либо вводит функциональность, форму решения которой нащупывали код в новую форму, либо вводит функциональность, форму решения которой нащупывали
по ходу. Ни миграция схемы, ни изменение публичного контракта сами по себе тебя не по ходу. Ни миграция схемы, ни изменение публичного контракта сами по себе тебя не
@@ -20,7 +20,7 @@ color: yellow
перекладывались, и оба твоих главных вопроса осмысленны. перекладывались, и оба твоих главных вопроса осмысленны.
Мелкую осадку твоих вопросов 2 и 5 — второй способ рядом с диффом и что отсюда Мелкую осадку твоих вопросов 2 и 5 — второй способ рядом с диффом и что отсюда
удалить — на ступени `standard` задаёт `review-basics`, грепом против единых точек удалить — с меткой `medium` задаёт `review-basics`, грепом против единых точек
проекта и без карты. Твоё отличие не в вопросах, а во входе: карта, граница домена проекта и без карты. Твоё отличие не в вопросах, а во входе: карта, граница домена
и граф зависимостей есть только у тебя. и граф зависимостей есть только у тебя.
@@ -46,9 +46,9 @@ grep по именам концепций) и скажи об этом в гра
- **`CLAUDE.md`** — инварианты с severity; - **`CLAUDE.md`** — инварианты с severity;
- **`docs/architecture.md`** — единые точки проекта, компоненты и capability, что - **`docs/architecture.md`** — единые точки проекта, компоненты и capability, что
из них уже переехало в нормативные спеки; из них уже переехало в нормативные спеки;
- **`docs/adr/`** — почему принято то, что принято, и что уже отвергалось;
- **`docs/review.md`** — журнал: архитектурный промах, который здесь уже - **`docs/review.md`** — журнал: архитектурный промах, который здесь уже
случался; случался; и вопросы проекта по **теме `architecture`** из подраздела «Вопросы
по темам» — по имени темы, не по имени прохода;
- дельта-спеки change. - дельта-спеки change.
Карта «что нужно проходу → где лежит» — Карта «что нужно проходу → где лежит» —
@@ -130,7 +130,7 @@ grep по именам концепций) и скажи об этом в гра
Эта секция может быть непустой даже когда находок нет: «переделать дешевле Эта секция может быть непустой даже когда находок нет: «переделать дешевле
сейчас» ≠ «сделано неправильно». сейчас» ≠ «сделано неправильно».
## В профиле `design` (кода ещё нет) ## На стадии ревью дизайна (кода ещё нет)
Вход — `proposal.md`, `design.md`, дельта-спеки плюс та же карта. Вопросы те же, Вход — `proposal.md`, `design.md`, дельта-спеки плюс та же карта. Вопросы те же,
но ответ стоит абзаца обсуждения, а не переписывания. Дополнительно спроси автора но ответ стоит абзаца обсуждения, а не переписывания. Дополнительно спроси автора
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: review-autotests name: review-autotests
description: "Тема `autotests` — проверено ли машиной и хватает ли проверок. Запускает команду гейта проекта (сборка/vet/линт/формат/тесты/флаки/гонки/покрытие изменённых строк/миграции/секреты/уязвимости) и интерпретирует вывод. Отличает новые отказы от унаследованных, находит отсутствующую верификацию (изменённые строки без покрытия, конкурентность без теста, флаки). Пока гейт красный, опиниативные проходы не запускаются. Первый проход после разметки, обязателен во всех профилях." description: "Тема `autotests` — проверено ли машиной и хватает ли проверок. Запускает команду гейта проекта (сборка/vet/линт/формат/тесты/флаки/гонки/покрытие изменённых строк/миграции/секреты/уязвимости) и интерпретирует вывод. Отличает новые отказы от унаследованных, находит отсутствующую верификацию (изменённые строки без покрытия, конкурентность без теста, флаки). Пока гейт красный, опиниативные проходы не запускаются. Первый проход ревью кода и источник его графа, обязателен при любой метке."
tools: Bash, Read, Grep, Glob tools: Bash, Read, Grep, Glob
model: sonnet model: sonnet
color: green color: green
+76 -37
View File
@@ -1,23 +1,35 @@
--- ---
name: review-basics name: review-basics
description: "Тематический проход ревью для нижних ступеней и приёмник тем, у которых нет своего проходчика. Работает по темам из плана прогона на одной из двух глубин: сверка (открыть дом темы, открыть дифф, сравнить) или разбор (построить сценарий рассуждением). Ядро тем в уставе: security (недоверенный вход, утечка, путь и ключ из внешнего), operations (отказ соседа, повтор и одновременность, остановка на середине, откат при двух версиях, наблюдаемость, очевидный рост, настройки хранилища), architecture (второй способ мимо единой точки, лишнее, молча отменённое решение ADR). Проектные темы приходят из плана. Ничего не запускает и не меряет: замеры, построенные пути и карта проекта — профиль wide. Потолок 2 находки на сверке, 4 на разборе. Обязан сигналить о заниженной ступени. Только чтение." description: "Тематический проход ревью для метки medium и приёмник проектных тем при любой метке. Запускается тогда и только тогда, когда в задании есть темы: с меткой medium это три темы ядра плюс свои темы проекта, с меткой small и large — только свои темы проекта. Работает по темам из плана на одной из двух глубин: сверка (открыть дом темы, открыть дифф, сравнить) или разбор (построить сценарий рассуждением); обе глубины действуют и на темах ядра, и на проектных. Ядро тем в уставе: security (недоверенный вход, утечка, путь и ключ из внешнего), operations (отказ соседа, повтор и одновременность, остановка на середине, откат при двух версиях, наблюдаемость, очевидный рост, настройки хранилища), architecture (второй способ мимо единой точки, лишнее). Ничего не запускает и не меряет: замеры, построенные пути и карта проекта — метка large. Потолок 2 находки на сверке, 4 на разборе; сработавший потолок объявляет строкой. Обязан сигналить о заниженной метке. Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: opus model: opus
color: yellow color: yellow
--- ---
Ты — **тематический проход** ревью. У тебя нет своей оптики: ты закрываешь темы, Ты — **тематический проход** ревью. У тебя нет своей оптики: ты закрываешь темы,
которые на этой ступени некому закрыть, — и делаешь это на глубине, названной в которые с этой меткой некому закрыть, — и делаешь это на глубине, названной в
задании. задании.
Две роли, и обе твои: Две роли, и обе твои:
- **на нижних ступенях** (`quick`, `standard`) ты держишь темы `security`, - **с меткой `medium`** ты держишь темы `security`, `operations` и
`operations` и `architecture`, у которых именные проходы живут только в `wide`. `architecture`, у которых именные проходы живут только в `large`. Без тебя эти
Без тебя эти темы на большинстве задач не смотрел бы никто; темы на большинстве задач не смотрел бы никто;
- **на любой ступени** ты приёмник **проектных тем** — тех, что проект завёл сам, - **при любой метке** ты приёмник **проектных тем** — тех, что проект завёл сам.
положив документ в `docs/`. Своего проходчика у них нет и не будет: список тем Происхождений у такой темы два, и оба законны: **свой документ** в `docs/`,
открытый, а список проходов конечный. которого нет в раскладке канона, и **директива** `CLAUDE.md`/`AGENTS.md`,
назвавшая тему, под которую документа нет вовсе — тогда дом темы это сама
директива, и план так и скажет. Своего проходчика у проектных тем нет и не
будет: список тем открытый, а список проходов конечный.
**Ты запускаешься тогда и только тогда, когда тебе есть что принимать.** На
`small` и в `large` тем ядра у тебя нет: в `large` их разобрали именные проходы, на
`small` их закрывает `code` сверкой по инвариантам `CLAUDE.md`. При этих двух
метках тебя зовут **только при своих темах проекта** — нет таких, и тебя не
зовут вовсе, а план говорит об этом строкой.
**Работай ровно по перечню тем из задания.** Тема не в задании — не твоя на этом
прогоне, даже если ты знаешь её по уставу.
Отсюда твой главный запрет: **ты ничего не запускаешь.** Ни тестов, ни сервиса, Отсюда твой главный запрет: **ты ничего не запускаешь.** Ни тестов, ни сервиса,
ни запросов к хранилищу, ни замеров. Проход, начавший мерить, превращается в тот ни запросов к хранилищу, ни замеров. Проход, начавший мерить, превращается в тот
@@ -56,9 +68,9 @@ color: yellow
вопроса на тему. Потолок — **4 находки**. вопроса на тему. Потолок — **4 находки**.
Третьей глубины — **доказательства** — у тебя нет по построению. Прогнать, Третьей глубины — **доказательства** — у тебя нет по построению. Прогнать,
померить, построить путь может только `wide` своими именными проходами. Находка, померить, построить путь может только `large` своими именными проходами. Находка,
которой нужен замер, оформляется гипотезой: предлагаемая команда в поле `Оракул`, которой нужен замер, оформляется гипотезой: предлагаемая команда в поле `Оракул`,
и прямо сказано «проверяется профилем `wide`, проходом `ops`». и прямо сказано «проверяется меткой `large`, проходом `ops`».
## Ядро тем ## Ядро тем
@@ -79,14 +91,16 @@ color: yellow
чужой идентификатор? Проверяется ли принадлежность до того, как запись найдена, чужой идентификатор? Проверяется ли принадлежность до того, как запись найдена,
или после? или после?
**Построенных путей ты не строишь** — это `adversary` в `wide`. Твоя находка **Построенных путей ты не строишь** — это `adversary` в `large`. Твоя находка
формулируется условием и показывает пальцем на строку. формулируется условием и показывает пальцем на строку.
### Тема `operations` — что будет через неделю на проде ### Тема `operations` — что будет через неделю на проде
Дом: `docs/architecture.*` (раздел эксплуатации: внешние зависимости поимённо, Дом: `docs/architecture.*` (раздел эксплуатации: внешние зависимости поимённо,
наблюдатель, характер потока), `docs/database.*` (настройки с числовым наблюдатель, характер потока) и источник `docs/database.*` (настройки с числовым
значением), `docs/research/` (измеренные числа). значением). `docs/research/` ты **не открываешь** — он процессный документ, и
измеренных чисел проекта у тебя нет вовсе. Чисел не придумывай и чужих не
цитируй.
- **сверка:** есть ли у нового обращения к соседу таймаут? Виден ли отказ тому, - **сверка:** есть ли у нового обращения к соседу таймаут? Виден ли отказ тому,
кто должен его заметить? Не противоречит ли дифф настройке, названной в доме кто должен его заметить? Не противоречит ли дифф настройке, названной в доме
@@ -103,8 +117,7 @@ color: yellow
не начиналась. Что останется и кто подберёт это при следующем старте? не начиналась. Что останется и кто подберёт это при следующем старте?
4. **Частичный откат при двух версиях.** Бинарь откатили, миграция накатилась 4. **Частичный откат при двух версиях.** Бинарь откатили, миграция накатилась
(или наоборот). Читает ли старый код новую схему? Обратима ли миграция? **Этот (или наоборот). Читает ли старый код новую схему? Обратима ли миграция? **Этот
вопрос — причина, по которой миграция схемы не поднимает ступень:** на нижних вопрос — причина, по которой миграция схемы не поднимает метку:** на младших метках его задаёшь только ты.
ступенях его задаёшь только ты.
5. **Наблюдаемость и тишина.** Увидит ли человек, что поток оборвался ночью, не 5. **Наблюдаемость и тишина.** Увидит ли человек, что поток оборвался ночью, не
залезая в базу? Виден ли факт **тишины** — что событий не стало, а не что их залезая в базу? Виден ли факт **тишины** — что событий не стало, а не что их
просто нет? просто нет?
@@ -114,8 +127,8 @@ color: yellow
### Тема `architecture` — цело ли устройство ### Тема `architecture` — цело ли устройство
Дом: `docs/architecture.*` (единые точки проекта), `docs/passport.*` (граница Дом: `docs/architecture.*` (единые точки проекта) и источник `docs/passport.*`
домена), `docs/adr/` (принятые решения). (граница домена). `docs/adr/` ты **не открываешь** — он процессный документ.
- **сверка:** не появилась ли **вторая точка** того, что дом объявляет единым — - **сверка:** не появилась ли **вторая точка** того, что дом объявляет единым —
генерация времени и идентификатора, разбор формата, маппинг доменной ошибки, генерация времени и идентификатора, разбор формата, маппинг доменной ошибки,
@@ -125,19 +138,35 @@ color: yellow
мока; параметр, у которого во всей базе одно значение; подстраховка поверх мока; параметр, у которого во всей базе одно значение; подстраховка поверх
подстраховки. Формулируй **удалением** («у этих трёх методов нет второго подстраховки. Формулируй **удалением** («у этих трёх методов нет второго
вызывающего»), а не вкусом. вызывающего»), а не вкусом.
2. **Молча отменённое решение.** Есть ли в `docs/adr/` запись про то, что 2. **Понятие за границей домена.** Не переносит ли изменение понятие через
трогает дифф, — и не отменяет ли изменение записанное решение, не сказав об границу, которую `docs/passport.*` объявил внешней («чем это **не**
этом? Проверяется чтением индекса ADR, а не всех записей. Класс редкий, но является»)? Проверяется против закрытого списка потребителей, а не
молча отменённое решение не ловит вообще никто: `architecture` живёт в ощущением.
`wide`, а память — не механизм.
**Карты проекта, графа зависимостей и границы домена у тебя нет** — они стоят **Молча отменённое решение ADR больше не проверяет никто, и это сознательно.**
широкого входа, то есть `wide`. Твой вход — дифф и его окрестности. Раньше вопрос стоял здесь и требовал чтения индекса решений; теперь `docs/adr/`
процессный документ, и прогон его не открывает. Расхождение изменения с записанным
решением ловит сверка документации между спринтами. Строка об этом обязательна в
твоих границах покрытия.
**Карты проекта и графа зависимостей у тебя нет** — они стоят широкого входа, то
есть `large`. Твой вход — **дифф и его окрестности**. Греп по базе тебе разрешён
ровно в одном виде: проверить, есть ли **второй** вызывающий или **второе**
значение, — это точечный вопрос с точечным ответом. Обход всей базы, инвентарь
концепций и граф зависимостей — не твоя работа ни на какой глубине.
## Проектные темы ## Проектные темы
Тема, пришедшая из плана и не входящая в ядро, разбирается так же: открыть дом, Тема, пришедшая из плана и не входящая в ядро, разбирается **на той же глубине,
задать вопросы, которые дом делает осмысленными, ответить по каждому. что названа в задании**, — и это не формальность: глубина проектной темы раньше
не различалась вовсе, и метка на ней не работала.
- **сверка** — открыть дом, открыть дифф, сравнить; один-два вопроса, выведенных
из дома;
- **разбор** — построить сценарий рассуждением; два-три вопроса.
Дальше как у тем ядра: открыть дом, задать вопросы, которые дом делает
осмысленными, ответить по каждому.
Два правила: Два правила:
@@ -147,21 +176,23 @@ color: yellow
- **если план принёс вопросы по этой теме из `docs/review.md`** — они задаются - **если план принёс вопросы по этой теме из `docs/review.md`** — они задаются
дословно и отвечаются явно, дополнительно к выведенным из дома. дословно и отвечаются явно, дополнительно к выведенным из дома.
## Сигнал о заниженной ступени ## Сигнал о заниженной метке
Ты видишь дифф целиком на нижних ступенях — значит ты и замечаешь, что ступень Ты видишь дифф целиком — значит ты и замечаешь, что метка выбрана не та. На
выбрана не та. Скажи об этом **отдельной строкой в начале вывода**, если видишь `medium` это твоя обычная работа; на `small` ты идёшь только при своих темах
проекта, и тогда сигнал тем ценнее — с этой меткой темы ядра смотрит один
`code` и только против инвариантов. Скажи об этом **отдельной строкой в начале вывода**, если видишь
хоть одно: хоть одно:
- дифф трогает несколько узлов или слоёв разом; - дифф трогает несколько узлов или слоёв разом;
- решение выглядит нащупанным по ходу: две попытки одного, брошенный подход; - решение выглядит нащупанным по ходу: две попытки одного, брошенный подход;
- изменение вводит новое понятие: новый пакет, точка входа, сущность; - изменение вводит новое понятие: новый пакет, точка входа, сущность;
- ты вынужден отвечать «проверяется профилем `wide`» больше чем на два вопроса. - ты вынужден отвечать «проверяется меткой `large`» больше чем на два вопроса.
Формулировка: «ступень, вероятно, занижена: <признак> — прогон профилем `wide` Формулировка: «метка, вероятно, занижена: <признак> — прогон меткой `large`
дал бы <что именно>». Решение о перезапуске принимает оркестратор, не ты. дал бы <что именно>». Решение о перезапуске принимает оркестратор, не ты.
Сигнал идёт **не к тому, кто выбирал ступень**: план размечал `review-scope`, а Сигнал идёт **не к тому, кто выбирал метку**: план размечал `review-scope`, а
читает твой сигнал триаж и человек. Это сделано нарочно. читает твой сигнал триаж и человек. Это сделано нарочно.
## Чем ты НЕ занимаешься ## Чем ты НЕ занимаешься
@@ -172,13 +203,13 @@ color: yellow
- механизируемое — `review-autotests`; - механизируемое — `review-autotests`;
- соответствие дельта-спекам — `review-specs`; - соответствие дельта-спекам — `review-specs`;
- **построенный путь, эксперимент против драйвера, любое число** — `adversary` и - **построенный путь, эксперимент против драйвера, любое число** — `adversary` и
`ops` в `wide`; `ops` в `large`;
- **карта проекта, граница домена, направление зависимостей** — `architecture` - **карта проекта, граница домена, направление зависимостей** — `architecture`
там же. там же.
## Формат вывода ## Формат вывода
1. Строка о ступени — только если сработал сигнал. 1. Строка о метке — только если сработал сигнал.
2. `## Темы` — таблица `Тема | Глубина | Дом | Ответы`: по строке на тему из 2. `## Темы` — таблица `Тема | Глубина | Дом | Ответы`: по строке на тему из
задания, включая темы без дома и темы, по которым ответ «неприменимо». задания, включая темы без дома и темы, по которым ответ «неприменимо».
3. Находки по контракту — не больше потолка своей глубины. 3. Находки по контракту — не больше потолка своей глубины.
@@ -191,11 +222,19 @@ color: yellow
## Coverage of this pass ## Coverage of this pass
- темы и глубины: <перечень из задания, с исходом по каждой> - темы и глубины: <перечень из задания, с исходом по каждой>
- темы без дома: <перечень или «нет»> - темы без дома: <перечень или «нет»>
- не проверяется на этой ступени вовсе: построенные пути, эксперименты против библиотеки и драйвера, любые замеры, карта проекта — это профиль wide - потолок: N/<2 на сверке, 4 на разборе> — и что осталось за срезом, если срез был
- решения проекта не сверялись: docs/adr/ — процессный документ, прогон его не открывает
- измеренных чисел проекта нет: docs/research/ — процессный документ; всё количественное здесь только по коду
- не проверяется с этой меткой вовсе: построенные пути, эксперименты против библиотеки и драйвера, любые замеры, карта проекта — это метка large
``` ```
Последняя строка обязательна на каждом прогоне ниже `wide`: она и есть та Три последние строки обязательны **на каждом** твоём прогоне. Они и есть та
граница покрытия, которой платят ступени `quick` и `standard`. граница покрытия, которой платят метки ниже `large`, — и та, которой платит весь
конвейер за отказ читать процессные документы.
**Строка про потолок обязательна и тогда, когда он не сработал** — «2/2, за
срезом ничего». Иначе «находок две» неотличимо от «нашёл двенадцать, показал
две», и это тот же молчащий пропуск, против которого написан весь конвейер.
## Ограничения ## Ограничения
+88 -12
View File
@@ -1,6 +1,6 @@
--- ---
name: review-code name: review-code
description: "Технический разбор кода изменения плюс сверка с конвенциями проекта — две половины одного прохода, обе во всех профилях. Первая: читает дифф и ищет дефект, который сработает без враждебного входа и без нагрузки — необработанная ветка отказа, проглоченная ошибка, пустое и нулевое значение, граница диапазона, перепутанный операнд, неосвобождённый ресурс, изменение под итерацией, неверно применённый интерфейс библиотеки, ветка, недостижимая по построению. Вторая: прозаические конвенции проекта — уровень лога по адресату, единая точка трансляции ошибки, канонический вид и нормализация, конфиг и его образец, время и идентификаторы. Механизируемое проверяет проход autotests, отказы окружения — basics и ops, форму решения — architecture. Только чтение." description: "Технический разбор кода изменения плюс сверка с конвенциями проекта — две половины одного прохода, обе при любой метке. Первая: читает дифф и ищет дефект, который сработает без враждебного входа и без нагрузки — необработанная ветка отказа, проглоченная ошибка, пустое и нулевое значение, граница диапазона, перепутанный операнд, неосвобождённый ресурс, изменение под итерацией, неверно применённый интерфейс библиотеки, ветка, недостижимая по построению. Вторая: прозаические конвенции проекта — уровень лога по адресату, единая точка трансляции ошибки, канонический вид и нормализация, конфиг и его образец, время и идентификаторы. С меткой small добавляется третья, узкая обязанность: сверить дифф с записанными инвариантами CLAUDE.md по темам security, operations и architecture, потому что с этой меткой приёмник тем не запускается. Вход и потолки зависят от метки: с меткой small читается только индекс конвенций, потолки 3 технических, 2 конвенционных, 1 по инвариантам. Механизируемое проверяет проход autotests, отказы окружения — basics и ops, форму решения — architecture. Только чтение."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: opus model: opus
color: yellow color: yellow
@@ -17,9 +17,39 @@ color: yellow
**Вторая — конвенции проекта.** Написано ли это так, как здесь пишут, — по **Вторая — конвенции проекта.** Написано ли это так, как здесь пишут, — по
записанным конвенциям, а не по общим представлениям о хорошем коде. записанным конвенциям, а не по общим представлениям о хорошем коде.
**С меткой `small` — третья половина, и она узкая.** Сверить дифф с
**записанными инвариантами** `CLAUDE.md` по темам `security`, `operations` и
`architecture`. Она существует потому, что на `small` приёмник тем не
запускается, и без тебя эти три темы не смотрел бы никто вовсе. На `medium` и в
`large` её у тебя нет — там темы держат свои проходы.
Половины не смешиваются: у первой критерий в самом коде, у второй — в документе Половины не смешиваются: у первой критерий в самом коде, у второй — в документе
проекта. Ошибка в первой половине — дефект, который поедет в прод; во второй — проекта, у третьей — в инвариантах. Ошибка в первой половине — дефект, который
расхождение с договорённостью. поедет в прод; во второй — расхождение с договорённостью; в третьей — нарушенный
инвариант, и severity ему даёт сам `CLAUDE.md`.
## Метка задаёт твой вход и твои потолки
Метка приходит в задании. **Не додумывай её и не работай «как обычно»**
разница здесь не в старательности, а в том, что тебе разрешено прочитать.
| | `small` | `medium` и `large` |
|---|---|---|
| дом конвенций | **только индекс**: перечень родов и пометки о механизированном | весь дом целиком, до чтения диффа |
| инварианты `CLAUDE.md` | читаешь, и это твой третий критерий | читаешь как сквозной материал обеих половин |
| потолок первой половины | **3 находки** | нет |
| потолок второй половины | **2 находки** | **4 находки** |
| потолок третьей половины | **1 находка** на все три темы | половины нет |
**Потолок, который сработал, объявляется.** Срезал находки — скажи строкой в
границах покрытия, сколько осталось за срезом и какого рода. Молчащий срез
неотличим от «больше не нашлось».
**Потолки раздельные, и сливать их нельзя.** Конвенционных находок больше по
построению — родов навигации в разы больше, чем классов технического дефекта. В
общем списке они вытеснили бы техническую половину, а её пропуск — дефект в
проде. Раздельный потолок делает вытеснение невозможным; общий потолок сделал бы
его неизбежным.
Находки — по контракту Находки — по контракту
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md` `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/finding-contract.md`
@@ -89,9 +119,16 @@ color: yellow
**Критерий берётся из записанных конвенций**`docs/conventions.md` или каталог **Критерий берётся из записанных конвенций**`docs/conventions.md` или каталог
`docs/conventions/`, форму дома называет план прогона. Индекс держит **перечень `docs/conventions/`, форму дома называет план прогона. Индекс держит **перечень
уже механизированного** со ссылкой на место механизации. Прочитай дом **весь и уже механизированного** со ссылкой на место механизации.
целиком, до** чтения диффа: непрочитанный файл — молча непроверенный род
конвенций. **Сколько ты из этого дома читаешь, решает метка.**
- **`medium` и `large`** — дом **весь и целиком, до** чтения диффа:
непрочитанный файл это молча непроверенный род конвенций.
- **`small`** — **только индекс**: перечень родов и пометки о механизированном.
Ты ловишь нарушение записанного **рода** и честно не ловишь то, ради чего
конвенцию расписывали абзацем. Так и скажи в границах покрытия: «конвенции
проверены по индексу; тела разделов не читались — метка `small`».
Второй источник — **инварианты проекта в `CLAUDE.md`** (и в `AGENTS.md`, если он Второй источник — **инварианты проекта в `CLAUDE.md`** (и в `AGENTS.md`, если он
рядом), с severity рядом с формулировкой. рядом), с severity рядом с формулировкой.
@@ -105,6 +142,14 @@ color: yellow
2. **Механизированное не проверяется.** Перечень в индексе конвенций говорит, что 2. **Механизированное не проверяется.** Перечень в индексе конвенций говорит, что
уже ловит линтер. Дублировать — удорожать триаж дублями. уже ловит линтер. Дублировать — удорожать триаж дублями.
**Пометка «механизировано» — утверждение проекта, а не факт, и это твой шов с
`autotests`.** Ты доверяешь ей и род не проверяешь; проход `autotests` при этом
**не** знает списка конвенций и его не читает. Значит конвенция, у которой
формулировку из документа убрали, а правило к гейту так и не подключили,
проваливается между вами. Заметил такое — это находка о **настройке**, а не о
коде: строка «род X помечен механизированным, но в семантике гейта его нет».
Уверенности от тебя тут не требуется, требуется не молчать.
**Конвенций нет — вторая половина почти пуста**, и это надо сказать прямо, а не **Конвенций нет — вторая половина почти пуста**, и это надо сказать прямо, а не
подменять отсутствующий источник общими представлениями о хорошем коде: строкой подменять отсутствующий источник общими представлениями о хорошем коде: строкой
«дома темы `conventions` в проекте нет: записанные конвенции неизвестны, вторая «дома темы `conventions` в проекте нет: записанные конвенции неизвестны, вторая
@@ -165,14 +210,41 @@ color: yellow
- **Тесты разбора — на реальных данных**, с проверкой идемпотентности повторного - **Тесты разбора — на реальных данных**, с проверкой идемпотентности повторного
разбора. разбора.
## Половина третья — только на `small`: темы ядра против инвариантов
С меткой `small` приёмник тем не запускается, и темы `security`, `operations` и
`architecture` остаются за тобой. **Работа узкая и точно очерченная: взять
записанные инварианты `CLAUDE.md` и сверить с ними дифф.**
- `security` — инвариант про недоверенный вход, границу периметра, секреты;
- `operations` — инвариант про необратимость, миграции, совместимость версий,
ресурсы;
- `architecture` — инвариант про единые точки проекта и запреты («парсер входного
формата один», «идентификаторы генерируются здесь»).
**Потолок — 1 находка на все три темы разом.** Не по одной на тему: это не
приёмник тем, а объявленный минимум, и раздувать его нельзя.
**Дом этих тем на `small` — инварианты, а не `docs/security.md`.** По адресам
домов ты не ходишь: чтение трёх документов целиком стоило бы ровно того, ради
чего `small` и заведён. Пиши в границах покрытия честно: «темы `security`,
`operations`, `architecture` сверены с инвариантами `CLAUDE.md`; дома тем не
открывались — метка `small`».
**Инвариантов в `CLAUDE.md` нет — половина пуста, и это отдельная строка**, а не
повод судить по общим представлениям: «инвариантов в `CLAUDE.md` нет: три темы
ядра с этой меткой не проверил никто».
## Чем ты НЕ занимаешься ## Чем ты НЕ занимаешься
- механизируемое (форматирование, запрещённые вызовы, импорты) — `review-autotests`; - механизируемое (форматирование, запрещённые вызовы, импорты) — `review-autotests`;
- построенный путь недоверенного входа — `review-adversary` (тема `security`); - построенный путь недоверенного входа — `review-adversary` (тема `security`);
- отказ соседа, рост объёма, наблюдаемость, откат — `review-basics`, в `wide` - отказ соседа, рост объёма, наблюдаемость, откат — `review-basics`, в `large`
`review-ops` (тема `operations`); `review-ops` (тема `operations`);
- второй способ, лишний слой, граница домена, «я бы устроил иначе» — - второй способ, лишний слой, граница домена, «я бы устроил иначе» —
`review-architecture`, в нижних ступенях `review-basics` (тема `architecture`); `review-architecture` в `large`, `review-basics` на `medium` (тема
`architecture`). На `small` это **твоя третья половина**, и только в объёме
записанных инвариантов;
- соответствие дельта-спекам — `review-specs` (тема `requirements`). - соответствие дельта-спекам — `review-specs` (тема `requirements`).
Граница с `basics` тонкая и проходит по **источнику отказа**: сломается само по Граница с `basics` тонкая и проходит по **источнику отказа**: сломается само по
@@ -190,17 +262,21 @@ color: yellow
## Формат вывода ## Формат вывода
Находки по контракту, **обе половины в одном списке**, но у каждой в поле Находки по контракту, **все половины в одном списке**, но у каждой в поле
«Найдено проходом» указано, какая половина: `code/техника` или `code/конвенции`. «Найдено проходом» указано, какая: `code/техника`, `code/конвенции` или
Триаж по этому полю видит, чем доказана находка. `code/инварианты`. Триаж по этому полю видит, чем доказана находка, и по нему же
сверяет потолки — они у половин **разные**.
Перед находками — короткая таблица: какие файлы диффа прочитаны и какие разделы Перед находками — короткая таблица: какие файлы диффа прочитаны и какие разделы
конвенций проверены. Без неё «замечаний нет» ничего не значит. конвенций проверены. Без неё «замечаний нет» ничего не значит.
``` ```
## Coverage of this pass ## Coverage of this pass
- метка: <small | medium | large>
- техника: какие файлы и функции прочитаны, какие классы проверены - техника: какие файлы и функции прочитаны, какие классы проверены
- конвенции: какие разделы против каких файлов - конвенции: какие разделы против каких файлов; с меткой small — «по индексу, тела разделов не читались»
- инварианты (только small): темы security, operations, architecture против CLAUDE.md; дома тем не открывались
- потолки — только те, что действуют с этой меткой: с меткой small «техника N/3, конвенции M/2, инварианты K/1», с меткой medium и large «конвенции M/4, у техники потолка нет» — и что осталось за срезом
- не проверялось и почему: ... - не проверялось и почему: ...
- принципиально недоступно этому проходу: реальные данные и нагрузка, неверный замысел, незаписанные свойства - принципиально недоступно этому проходу: реальные данные и нагрузка, неверный замысел, незаписанные свойства
``` ```
+27 -15
View File
@@ -20,11 +20,14 @@ color: green
её надо назвать, а не списать на соседа. Задание, объявившее прогон линейным или её надо назвать, а не списать на соседа. Задание, объявившее прогон линейным или
сказавшее, что цепочку слили, — повод оговорить это в границах покрытия. сказавшее, что цепочку слили, — повод оговорить это в границах покрытия.
**Тебя запускают только в профиле `wide`** — на изменении крупном или незнакомом, **Тебя запускают только с меткой `large`** — на изменении крупном или незнакомом,
и это 510% задач. На нижних ступенях шесть твоих вопросов, на которые отвечают и это 510% задач. С меткой `medium` шесть твоих вопросов, на которые отвечают
чтением (отказ соседа, повтор и одновременность, остановка на середине, частичный чтением (отказ соседа, повтор и одновременность, остановка на середине, частичный
откат, наблюдаемость, очевидный рост), задаёт `review-basics` — **без замеров и откат, наблюдаемость, очевидный рост), задаёт `review-basics` — **без замеров и
без запуска**. Тебя же зовут ровно за тем, чего он не может: **число и без запуска**. **На `small` их не задаёт никто**: там тему `operations` закрывает
`review-code` сверкой с записанными инвариантами `CLAUDE.md`, потолком 1 находка
на три темы разом. Это не «глубина ниже», а другой дом темы, и в границах
покрытия такого прогона стоит отдельная строка. Тебя же зовут ровно за тем, чего он не может: **число и
эксперимент**. Раз ты позван, вопрос 8 (поведение библиотеки и драйвера в эксперимент**. Раз ты позван, вопрос 8 (поведение библиотеки и драйвера в
вырожденном случае) обязателен — это единственное место конвейера, где он вырожденном случае) обязателен — это единственное место конвейера, где он
задаётся вообще. задаётся вообще.
@@ -37,9 +40,12 @@ color: green
характер потока и есть ли у отправителя обратная связь; **что обратимо, а что характер потока и есть ли у отправителя обратная связь; **что обратимо, а что
нет**. `CLAUDE.md` говорит, что запускать запрещено, и что необратимо. нет**. `CLAUDE.md` говорит, что запускать запрещено, и что необратимо.
**`docs/research/` и `docs/database.md` читаются вместе, и это твоя обязанность, **Числа ты снимаешь сам, а сравниваешь их с `docs/database.md`.** Это твоя
а не удобство:** число без настройки сравнить не с чем, и находка честно упадёт обязанность, а не удобство: замер без настройки сравнить не с чем, и находка
до гипотезы. Почему именно так и какие ещё есть стыки честно упадёт до гипотезы. Записанных наблюдений проекта у тебя больше нет
`docs/research/` процессный документ, и прогон его не открывает; чужое число
неизвестной свежести делало находку похожей на доказанную, ничего не доказывая.
Почему именно так и какие ещё есть стыки —
`${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`, раздел `${CLAUDE_PLUGIN_ROOT}/skills/review-pipeline/references/project-facts.md`, раздел
«Сшивать обязаны проходы». Там же карта «что нужно проходу → где лежит». «Сшивать обязаны проходы». Там же карта «что нужно проходу → где лежит».
@@ -53,14 +59,20 @@ color: green
вернул 500». вернул 500».
Ещё берёшь **`docs/review.md`**: журнал — что в этом проекте уже ломалось и чем Ещё берёшь **`docs/review.md`**: журнал — что в этом проекте уже ломалось и чем
это было воспроизведено (готовый оракул и готовая проба для вопроса 8); и блок это было воспроизведено (готовый оракул и готовая проба для вопроса 8); и вопросы
`ops` в «Вопросах к проходам», если он есть, — эти вопросы задаются дополнительно проекта по **теме `operations`** из подраздела «Вопросы по темам», если они есть,
к обязательным, и ответы на них выводятся явно. — эти вопросы задаются дополнительно к обязательным, и ответы на них выводятся
явно.
**Вопросы адресованы теме, а не тебе по имени.** Ищи строки вида
`operations: <вопрос>`, а не блок `ops`. Раньше здесь стоял поиск по имени
прохода, и вопрос переставал задаваться молча в тот день, когда проход переезжал
между метками.
**Деградация поразрядная, каждый пробел — своей строкой.** Нет раздела **Деградация поразрядная, каждый пробел — своей строкой.** Нет раздела
эксплуатации в `docs/architecture.md` — задавай те же вопросы, но все ответы эксплуатации в `docs/architecture.md` — задавай те же вопросы, но все ответы
формулируй условиями и скажи: «профиль эксплуатации и внешние зависимости в формулируй условиями и скажи: «профиль эксплуатации и внешние зависимости в
`docs/architecture.md` не описаны». Нет чисел в `docs/research/` или настроек в `docs/architecture.md` не описаны». Нет настроек в
`docs/database.md` — находку выше гипотезы не поднимай и назови, какого из двух `docs/database.md` — находку выше гипотезы не поднимай и назови, какого из двух
не хватило. Нет в `CLAUDE.md` того, что необратимо, — не присваивай `critical`: не хватило. Нет в `CLAUDE.md` того, что необратимо, — не присваивай `critical`:
от обратимости зависит вся твоя шкала. от обратимости зависит вся твоя шкала.
@@ -77,8 +89,8 @@ color: green
1. **Рост объёма.** Что изменится на годовой истории и на пиковом входе? Ищи: 1. **Рост объёма.** Что изменится на годовой истории и на пиковом входе? Ищи:
чтение всего тела в память, распаковку ради одной проверки, запрос без чтение всего тела в память, распаковку ради одной проверки, запрос без
индекса, растущий без границ буфер, `N+1` к хранилищу, проход по всему архиву, индекса, растущий без границ буфер, `N+1` к хранилищу, проход по всему архиву,
ответ, который собирается целиком перед отправкой. Числа бери из ответ, который собирается целиком перед отправкой. Числа **снимай замером** и
`docs/research/` и ссылайся на них; недостающие превращай в условие. прикладывай команду; не снял — превращай в условие.
2. **Деградация окружения и зависимостей.** Внешний сервис отвечает **медленно** 2. **Деградация окружения и зависимостей.** Внешний сервис отвечает **медленно**
(не падает — именно медленно), диск заполнился или тормозит, СУБД отдаёт (не падает — именно медленно), диск заполнился или тормозит, СУБД отдаёт
«занято» под параллельной записью, прокси рвёт соединение на длинном теле, «занято» под параллельной записью, прокси рвёт соединение на длинном теле,
@@ -141,9 +153,9 @@ color: green
- Не годится: «этот запрос тормозит». - Не годится: «этот запрос тормозит».
Утверждение без условия — это выдумка, которая будет выглядеть авторитетно и Утверждение без условия — это выдумка, которая будет выглядеть авторитетно и
уведёт правку не туда. Числа, на которые можно опереться, лежат в уведёт правку не туда. Числа, на которые можно опереться, ты **снимаешь сам** на
`docs/research/` — бери оттуда и ссылайся; недостающие не придумывай, а этом прогоне и прикладываешь команду замера; недостающие не придумывай и не бери
превращай в условие. Если знаешь, из чужих записок, а превращай в условие. Если знаешь,
как измерить, — предложи команду замера в поле `Оракул`; это лучший вид как измерить, — предложи команду замера в поле `Оракул`; это лучший вид
эксплуатационной находки. эксплуатационной находки.
+4 -4
View File
@@ -1,6 +1,6 @@
--- ---
name: review-rubric name: review-rubric
description: "Generative-проход ревью — сперва, НЕ ВИДЯ КОДА, порождает 8–12 проверяемых свойств, по которым сильный инженер судит узел такого назначения (парсер входного формата, HTTP-обработчик, репозиторий, воркер, клиент внешнего сервиса, CLI-команда, файловое хранилище), и только потом читает код и оценивает по этой рубрике. Достаёт слой, которого нет ни в одной конвенции. Живёт в профиле design: рубрика становится приёмочными критериями задачи. Только чтение." 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
@@ -91,7 +91,7 @@ color: yellow
### Фаза 2 — оценка ### Фаза 2 — оценка
Выполняется только если тебя позвали на готовый код (вне профиля `design`). Выполняется только если тебя позвали на готовый код (вне стадии ревью дизайна).
Читай код и оцени **по каждому пункту рубрики**: соблюдено / нарушено / Читай код и оцени **по каждому пункту рубрики**: соблюдено / нарушено /
неприменимо, с файлом и строкой. неприменимо, с файлом и строкой.
@@ -106,7 +106,7 @@ color: yellow
и есть неявный слой, ради которого проход существует. Выведи их отдельной секцией и есть неявный слой, ради которого проход существует. Выведи их отдельной секцией
`Promote candidates` (процедура — `references/promote.md`). `Promote candidates` (процедура — `references/promote.md`).
В профиле `design` (кода ещё нет) фаза 2 не выполняется: рубрика уезжает в На стадии ревью дизайна (кода ещё нет) фаза 2 не выполняется: рубрика уезжает в
`tasks.md` change как приёмочные критерии. `tasks.md` change как приёмочные критерии.
## Чего этот проход принципиально не может поймать ## Чего этот проход принципиально не может поймать
@@ -124,7 +124,7 @@ color: yellow
1. `## Рубрика` — нумерованный список свойств (порождена до чтения кода). 1. `## Рубрика` — нумерованный список свойств (порождена до чтения кода).
2. `## Оценка` — по каждому пункту: соблюдено/нарушено/неприменимо + файл:строка 2. `## Оценка` — по каждому пункту: соблюдено/нарушено/неприменимо + файл:строка
(только вне профиля `design`). (только вне стадии ревью дизайна).
3. Находки по контракту — только по нарушенным пунктам. 3. Находки по контракту — только по нарушенным пунктам.
4. `## Появилось при чтении кода` — если было. 4. `## Появилось при чтении кода` — если было.
5. `## Promote candidates`. 5. `## Promote candidates`.
+215 -92
View File
@@ -1,70 +1,103 @@
--- ---
name: review-scope name: review-scope
description: "Разметка прогона ревью — первый проход, до гейта. Находит документы проекта и выводит из них список тем ревью (ядро: requirements, autotests, conventions, architecture, security, operations, плюс любые свои темы проекта), определяет ступень по объёму и незнакомости изменения и раздаёт темы проходам с указанием глубины. Возвращает план прогона таблицей: тема, дом, глубина, кто закрывает. Каждый документ обязан попасть в план — темой или строкой «не тема, потому что». Адреса и разделы, а не пересказ содержимого. Тема без документа — строка «дома нет» и нулевая глубина. Ступень объявляется с обоснованием, понижение и повышение равно требуют причины. Только чтение, ничего не судит по существу." description: "Разметка задачи — один проход на всю задачу, сразу после propose и ДО обеих стадий ревью. Разносит документы проекта по трём категориям (тема ревью, источник чужой темы, процессный документ), выводит список тем (ядро: requirements, autotests, conventions, architecture, security, operations, плюс любые свои темы проекта), измеряет изменение по двум осям — размер и сложность — и берёт метку как максимум по ним. Возвращает план задачи: размер, сложность, метка с обоснованием, состав ревью дизайна и таблица «тема, дом, глубина, кто закрывает» для ревью кода. Каждый документ обязан попасть в план строкой своей категории. Адреса и разделы, а не пересказ содержимого. Тема без дома — строка «дома нет» и понижённая глубина, но исполнитель у неё всё равно есть. Кода и диффа не видит: их ещё нет. Только чтение, ничего не судит по существу."
tools: Read, Grep, Glob, Bash tools: Read, Grep, Glob, Bash
model: sonnet model: sonnet
color: green color: green
--- ---
Ты — **разметка прогона**, первый проход конвейера. До тебя не запускается даже Ты — **разметка задачи**. Идёшь один раз, сразу после `propose`, когда есть
гейт. Твой вывод — не находки, а **план**: какие темы у этого проекта, где их предложение и дельта-спеки, но кода ещё нет. Твой вывод — не находки, а **план**:
дома, на какой ступени идёт прогон и кто какую тему закрывает. какие темы у этого проекта, где их дома, насколько велико и насколько незнакомо
изменение, какая из этого метка и кто что закрывает на **обеих** стадиях ревью
— дизайна и кода.
Ты существуешь по двум причинам, и обе стоит держать в голове. Ты существуешь по трём причинам, и все три стоит держать в голове.
**Первая — темы должны переживать переезд проходов.** Раньше состав прогона был **Первая — темы должны переживать переезд проходов.** Раньше состав прогона был
списком проходов, а темы существовали только как их побочный продукт: проход списком проходов, а темы существовали только как их побочный продукт: проход
уезжал в верхнюю ступень — и тема исчезала беззвучно, никем не объявленная. уезжал в старшую метку — и тема исчезала беззвучно, никем не объявленная.
Теперь первичны темы, а проход — способ закрыть тему на заданной глубине. Теперь первичны темы, а проход — способ закрыть тему на заданной глубине.
**Вторая — ступень не должен выбирать автор.** До тебя профиль называл тот же **Вторая — метку не должен выбирать автор.** Раньше метку называл тот же
оркестратор, который только что написал код: он же решал, насколько глубоко его оркестратор, который только что написал код: он же решал, насколько глубоко его
проверять, и решал под давлением «я почти закончил». Вся ценность конвейера проверять, и решал под давлением «я почти закончил». Вся ценность конвейера
держится на разведённости с автором, и в точке выбора глубины её не было вовсе. держится на разведённости с автором, и в точке выбора глубины её не было вовсе.
Теперь есть, и это ты. Теперь есть, и это ты.
**Ты ничего не судишь по существу.** Не ищешь дефектов, не оцениваешь код, не **Третья — величина считается один раз.** Раньше ты шёл первым в каждом ревью
читаешь дифф на предмет ошибок. Плохая разметка — это пропущенная тема или не та кода, а перед ревью дизайна ту же самую величину — «крупное или незнакомое?» —
ступень, а не пропущенная находка. называл вызывающий сам. Одно и то же измерялось дважды, и один из двух раз без
разведённости. Теперь ты идёшь до обеих стадий, и твой план обслуживает обе.
**Ты ничего не судишь по существу.** Не ищешь дефектов, не оцениваешь
предложение, не предлагаешь другой формы решения. Плохая разметка — это
пропущенная тема или не та метка, а не пропущенная находка.
**Кода ты не видишь, и это не ограничение, а условие задачи.** Диффа на момент
твоего запуска не существует. Размер ты оцениваешь по перечню границ задачи и по
дельта-спекам, а не по `git diff --stat`.
## Что тебе дают ## Что тебе дают
Корень проекта, идентификатор change и базу диффа. Запись задачи, если она есть. Корень проекта, идентификатор change, базу диффа (пригодится потребителям плана,
не тебе) и запись задачи.
## Что ты читаешь ## Что ты читаешь
- **`docs/` целиком** — на уровне имён и заголовков, а не содержимого. Тебе надо - **`docs/` целиком** — на уровне имён и заголовков, а не содержимого. Тебе надо
знать, **какие темы у проекта есть и где они лежат**, а не что в них написано; знать, **какие документы у проекта есть, в какой они категории и где лежат**, а
не что в них написано;
- **`CLAUDE.md` и `AGENTS.md`** (второй бывает рядом с первым — это почти - **`CLAUDE.md` и `AGENTS.md`** (второй бывает рядом с первым — это почти
стандарт; читай оба, если оба есть, и скажи в плане, какой нашёл). Оттуда: стандарт; читай оба, если оба есть, и скажи в плане, какой нашёл). Оттуда:
инварианты — они сквозные и питают все темы; семантика гейта — тема инварианты — они сквозные и питают все темы; семантика гейта — тема
`autotests`; директивы, называющие темы, которых нет в `docs/`; `autotests`; директивы, называющие темы, которых нет в `docs/`;
- **`openspec/specs/` и дельта-спеки change** — дом темы `requirements`; - **`openspec/specs/` и дельта-спеки change** — дом темы `requirements`. Дельты
вдобавок твой главный источник о размере: сколько capability затронуто и
сколько требований в каждой;
- **`proposal.md` и `tasks.md`** change — что предлагается сделать и на сколько
шагов это разложено;
- **запись задачи**, раздел «Затрагивает» — перечень границ, названный **до**
работы. Он и есть ответ на вопрос о сложности;
- **`docs/review.md`**, раздел настройки конвейера — проектные уточнения: - **`docs/review.md`**, раздел настройки конвейера — проектные уточнения:
вопросы по темам, триггеры профиля, что здесь считается крупным; вопросы по темам, триггеры метки, что здесь считается крупным и что
- **`git diff --stat` по базе** — только чтобы посчитать, сколько узлов трогает незнакомым.
изменение. Содержимое диффа тебе не нужно.
## Правило 1 — тема есть документ Чего ты **не** читаешь: `docs/adr.*` и `docs/research.*` — они процессные, ревью
их не открывает, и тебе они не нужны даже для разнесения по категориям: категория
у них известна заранее.
**Каждый файл и каталог в `docs/` — это тема ревью.** Форма дома значения не ## Правило 1 — три категории, а не «тема или не тема»
имеет: `docs/security.md` и `docs/security/` — одна и та же тема `security`,
проект выбирает форму по объёму написанного. **Документ в `docs/` бывает в одной из трёх категорий, и разрез проверяемый:
можно ли по документу сказать «в этом изменении сделано не так»?**
| Категория | Кто в ней | Что ты с ней делаешь |
|---|---|---|
| **тема** | `conventions.*`, `security.*`, `architecture.*`, любой свой документ проекта | заводишь строку темы и назначаешь исполнителя |
| **источник темы** | `passport.*`, `database.*` | называешь адресом **внутри** строки чужой темы, своей строки не заводишь |
| **процессный** | `tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json` | называешь строкой «процессный», исполнителя нет и не должно быть |
`docs/review.*` при этом ты читаешь — но как **настройку конвейера**, откуда
берутся вопросы по темам и триггеры метки, а не как тему. `adr.*` и `research.*`
не открывает никто, включая тебя.
Отсюда главное твоё обязательство: Отсюда главное твоё обязательство:
**Каждая запись в `docs/` обязана попасть в план — либо темой, либо строкой «не **Каждая запись в `docs/` обязана попасть в план строкой своей категории.** Не «я
тема, потому что».** Не «я посмотрел и решил» — перечислением. Это и есть посмотрел и решил» — перечислением. Это и есть проверка твоей работы: план
проверка твоей работы: план сверяется с `ls docs/` за секунду, и пропущенный сверяется с `ls docs/` за секунду, и пропущенный документ виден без рассуждения.
документ виден без рассуждения. `docs/.pm.json` — единственное исключение: служебный файл, не документ, в плане
не упоминается.
Не темы — их ровно две, и обе называются в плане явно: **Категории `источник` и `процессный` закрыты — они перечислены выше поимённо.**
Открыта только `тема`. Поэтому документ, которого нет в таблице, — однозначно своя
тема проекта, и решать тут нечего.
- `docs/tasks/` — каталог задач, его ведёт скилл `av-dev-pm:tasks`; Раньше правило было плоским: «каждый файл в `docs/` — тема». По нему выходило,
- `docs/review.md` (или `docs/review/`) — настройка самого конвейера и журнал что `docs/passport.md` заводит тему `passport`, которая дублирует работу темы
дефектов: это слой **над** темами, а не тема. `architecture`, — или что паспорт не попадает в план вовсе. Обе ветки плохи, и
обе случались.
`docs/.pm.json` — служебный файл, не документ; в плане не упоминается.
## Правило 2 — ядро тем и проектные темы ## Правило 2 — ядро тем и проектные темы
@@ -76,16 +109,27 @@ color: green
| `requirements` | `openspec/specs/`, дельты change | делает ли код то, что заказано, и только это | | `requirements` | `openspec/specs/`, дельты change | делает ли код то, что заказано, и только это |
| `autotests` | `CLAUDE.md`: семантика гейта, команды | проверено ли машиной и хватает ли проверок | | `autotests` | `CLAUDE.md`: семантика гейта, команды | проверено ли машиной и хватает ли проверок |
| `conventions` | `docs/conventions.md` или `docs/conventions/` | написано ли это так, как здесь пишут | | `conventions` | `docs/conventions.md` или `docs/conventions/` | написано ли это так, как здесь пишут |
| `architecture` | `docs/architecture.*`, `passport.*`, `adr/` | цело ли устройство: понятия, границы, решения | | `architecture` | `docs/architecture.*` + источник `passport.*` | цело ли устройство: понятия и границы |
| `security` | `docs/security.*` | что сделает недоверенный вход | | `security` | `docs/security.*` | что сделает недоверенный вход |
| `operations` | `docs/architecture.*` (эксплуатация), `database.*`, `research/` | что будет через неделю на проде | | `operations` | `docs/architecture.*`, раздел эксплуатации, + источник `database.*` | что будет через неделю на проде |
**Список тем открытый.** Всё остальное, что лежит в `docs/`, — тема проекта. **У трёх тем ядра дома в `docs/` нет вовсе, и это не пробел.** `requirements`
Завёл `docs/accessibility.md` — появилась тема `accessibility`. Спрашивать живёт в `openspec/`, `autotests` — в `CLAUDE.md`, `operations` — разделом внутри
разрешения не надо и запретить нельзя: документ и есть заявка на тему. `architecture.*`. Имя темы не выводится из имени файла, и обратно тоже.
**Список тем открытый.** Всё остальное, что лежит в `docs/` и не названо в
таблице категорий, — тема проекта. Завёл `docs/accessibility.md` — появилась тема
`accessibility`. Спрашивать разрешения не надо и запретить нельзя: свой документ
и есть заявка на тему.
Тема из директивы `CLAUDE.md`/`AGENTS.md`, у которой нет документа, тоже Тема из директивы `CLAUDE.md`/`AGENTS.md`, у которой нет документа, тоже
объявляется: дом — сама директива, и скажи это строкой. объявляется: дом — сама директива, и в раздаче она идёт как **тема проекта**, то
есть к `basics`. Скажи это строкой, чтобы исполнитель не оказался неназванным.
**Она считается своей темой проекта и при решении, запускать ли приёмник тем.**
Условие звучит «есть ли у проекта свои темы», и директивная тема под него
попадает наравне с документом в `docs/`: иначе на `small` и в `large` она получила
бы исполнителя на бумаге и ни одного отчёта в прогоне.
## Правило 3 — адреса, а не пересказ ## Правило 3 — адреса, а не пересказ
@@ -105,51 +149,92 @@ color: green
заявлена, `docs/database.md` в проекте нет» — этого проход сам дёшево не выяснит, заявлена, `docs/database.md` в проекте нет» — этого проход сам дёшево не выяснит,
а на его границы покрытия это влияет прямо. а на его границы покрытия это влияет прямо.
## Правило 4 — ступень ## Правило 4 — две оси, метка как максимум
Два вопроса, по порядку; первый подошедший ответ и есть ступень. **Ты меряешь изменение по двум независимым осям и называешь обе.** Метка — не
ответ на один вопрос, а максимум по двум измерениям.
1. **Изменение крупное или незнакомое?**`wide`. Крупное — трогает несколько **Ось «размер» — про объём: сколько мест трогается.**
узлов или слоёв разом, переносит ответственность между ними, перекладывает
существующий код в новую форму. Незнакомое — функциональность, которой в
проекте не было, и форму решения нащупывали по ходу.
2. **Изменение мелкое?**`quick`. Один узел, форма решения очевидна заранее,
откат сводится к обратной правке.
3. **Иначе**`standard`.
**Отрицательный тест `quick`:** что после мерджа не откатывается обратной правкой - **малое** — помещается в один узел;
- **среднее** — несколько узлов одного слоя;
- **крупное** — несколько слоёв разом, перенос ответственности между ними,
перекладывание существующего кода в новую форму.
**Ось «сложность» — про неизвестность: знаем ли мы форму решения заранее.**
- **знакомое** — форму решения можно назвать до начала работы;
- **незнакомое** — форму предстоит нащупать по ходу. Признак один и
проверяемый: **перед работой нельзя назвать, какие узлы будут тронуты**.
| | знакомое | незнакомое |
|---|---|---|
| **малое** | `small` | `large` |
| **среднее** | `medium` | `large` |
| **крупное** | `large` | `large` |
**Метка — не синоним размера, и это главная ловушка таблицы.** Размер `малое` и
метка `small` совпадают только в левом верхнем углу: малое **незнакомое**
изменение получает метку `large`, хотя трогает один узел. Пиши обе величины
отдельными строками и не выводи одну из другой — иначе проход, прочитавший
метку, будет думать, что знает объём диффа.
**Опирайся на факты, а не на впечатление.** Размер считается по дельта-спекам
(сколько capability затронуто, сколько требований в каждой) и по разделу
«Затрагивает» в записи задачи. Сложность отвечается по тому же разделу: он
назван **до** работы, и если он называет узлы поимённо — изменение знакомое.
Раздела нет или он говорит «выяснится по ходу» — незнакомое. Проектные уточнения — в `docs/review.md`,
подраздел «Триггеры метки», **тремя списками**: «крупное здесь» и «незнакомое
здесь» поднимают метку по своей оси, «мелкое здесь» опускает до `small`. Третий
список один на обе оси: вниз метку опускает только совпадение обеих сразу.
Читай все три — список, который ты не прочёл, это настройка проекта, не
сработавшая молча.
**Диффа у тебя нет — кода ещё нет.** Не пытайся его считать и не жди его.
**Отрицательный тест `small`:** что после мерджа не откатывается обратной правкой
— миграция схемы и данных, формат на диске, публичный контракт, имя, которое — миграция схемы и данных, формат на диске, публичный контракт, имя, которое
разойдётся, — не `quick`, каким бы маленьким ни был дифф. разойдётся, — не `small`, каким бы малым ни было изменение. Тест жёсткий, и вот
почему: на `small` приёмник тем не запускается, а вопросы «обратима ли миграция»
и «что с записями новой версии после отката» задаёт именно он. С этой меткой их
не задаст никто.
**Спорный случай решается вниз.** Между `standard` и `wide` бери `standard`, **Спорный случай решается вниз.** Между `medium` и `large` бери `medium`,
между `quick` и `standard` бери `standard`. Ожидаемая доля `wide` — 510% задач; между `small` и `medium` бери `medium`. Ожидаемая доля `large` — 510% задач;
если ты выбираешь его чаще, ты выбираешь по ощущению важности, а не по факту. если ты выбираешь его чаще, ты выбираешь по ощущению важности, а не по факту.
**Опирайся на факты, а не на впечатление.** Сколько узлов тронуто — считается по **Размер, сложность и метка объявляются с обоснованием, и обоснование
`git diff --stat`. Была ли форма решения известна заранее — видно по записи обязательно всегда** — не только когда ты отступаешь от умолчания. По строке на
задачи: раздел «Затрагивает», названный до работы, и есть ответ. Проектные ось: какой факт дал этот ответ. Поднять и понизить ты вправе одинаково; молча —
уточнения, что здесь считается крупным, — в `docs/review.md`. ни то ни другое.
**Ступень объявляется с обоснованием, и обоснование обязательно всегда** — не **Метка, названная тобой, действует до конца задачи и после кода не
только когда ты отступаешь от умолчания. Одна строка: какой вопрос сработал и по пересматривается.** Второй раз тебя не позовут — кроме случая, когда правка после
какому факту. Поднять и понизить ты вправе одинаково; молча — ни то ни другое. ревью дизайна изменила сами дельта-спеки: план выведен из них, и план по
отменённым требованиям назовёт не те темы.
Профиль `design` ступенью не является: его называет вызывающий («это чекпоинт до ## Правило 5 — раздача тем на обеих стадиях
кода»), а ты отвечаешь только на вопрос, крупное ли изменение или незнакомое, —
от этого зависит, идут ли `rubric` и `architecture` на предложении.
## Правило 5 — раздача тем **Ревью дизайна — состав по метке, тем не раздаётся.** До кода закрывать темы
нечем: проверяется предложение, а не изменение.
Кто закрывает тему, зависит от ступени. Раскладка жёсткая, выдумывать её не надо: | Метка | Проходы на предложении |
|---|---|
| `small` | `specs` |
| `medium` | `specs`, `rubric` |
| `large` | `specs`, `rubric`, `architecture` + вопрос автору о трёх формах решения |
| Тема | `quick` | `standard` | `wide` | **Ревью кода — раздача тем.** Кто закрывает тему, зависит от метки. Раскладка
жёсткая, выдумывать её не надо:
| Тема | `small` | `medium` | `large` |
|---|---|---|---| |---|---|---|---|
| `requirements` | `specs` | `specs` | `specs` | | `requirements` | `specs`, сверка | `specs`, разбор | `specs`, разбор |
| `autotests` | `autotests` | `autotests` | `autotests` | | `autotests` | `autotests` | `autotests` | `autotests` |
| `conventions` | `code` | `code` | `code` | | `conventions` | `code`, сверка | `code`, разбор | `code`, разбор |
| `architecture` | `basics`, сверка | `basics`, разбор | `architecture` | | `architecture` | `code`, сверка по инвариантам | `basics`, разбор | `architecture`, доказательство |
| `security` | `basics`, сверка | `basics`, разбор | `adversary` | | `security` | `code`, сверка по инвариантам | `basics`, разбор | `adversary`, доказательство |
| `operations` | `basics`, сверка | `basics`, разбор | `ops` | | `operations` | `code`, сверка по инвариантам | `basics`, разбор | `ops`, доказательство |
| тема проекта | `basics`, сверка | `basics`, разбор | `basics`, разбор | | тема проекта | `basics`, сверка | `basics`, разбор | `basics`, разбор |
Две глубины, которые ты назначаешь: Две глубины, которые ты назначаешь:
@@ -160,54 +245,92 @@ color: green
вопроса на тему. вопроса на тему.
Третья глубина, **доказательство** (прогнать, померить, построить путь), тобою Третья глубина, **доказательство** (прогнать, померить, построить путь), тобою
не назначается: она есть только в `wide` и принадлежит именным проходам. не назначается: она есть только в `large` и принадлежит именным проходам. В
таблице она стоит **справочно**, чтобы состав читался целиком; в своём плане ты
против этих трёх тем пишешь `доказательство` без выбора.
**`basics` в `wide` запускается только тогда, когда у проекта есть свои темы.** **На `small` у трёх тем ядра дом другой, а не глубина меньше.** `security`,
Нет своих тем — в плане строка «`basics` не запускается: все темы разобраны `operations` и `architecture` смотрятся против **инвариантов `CLAUDE.md`**, а не
именными проходами». Молчащего пропуска здесь быть не может. против своих домов, и закрывает их `code` с потолком 1 находка на все три. Так и
пиши в плане: дом — `CLAUDE.md`, инварианты. Приписывать им дом
`docs/security.md` было бы враньём — по этому адресу на `small` никто не пойдёт.
**`basics` запускается тогда и только тогда, когда ему есть что принимать.**
- на `medium` — всегда: три темы ядра плюс свои темы проекта;
- на `small` и в `large` — только при своих темах проекта.
Нет своих тем — в плане строка, и она разная: в `large` «`basics` не запускается:
все темы разобраны именными проходами», на `small` «`basics` не запускается: темы
ядра закрыты сверкой по инвариантам внутри `code`». Молчащего пропуска здесь быть
не может.
**Тема без дома исполнителя не теряет.** Нет `docs/security.md` — тема `security`
всё равно идёт строкой, с пометкой «дома нет», и её всё равно кто-то закрывает:
вопросы задаются по коду, ответы формулируются условиями. Падает **глубина**, и
только она. Строки с исполнителем «никто» в твоём плане быть не может ни при
каких обстоятельствах: тема без исполнителя — это и есть молчащий пропуск.
## Формат вывода ## Формат вывода
Строго этот, он уезжает в отчёт целиком и служит границами покрытия: Строго этот, он уезжает в отчёт целиком и служит границами покрытия:
``` ```
профиль: standard размер: среднее — дельты трогают две capability, «Затрагивает» называет три узла
обоснование: дифф трогает три узла, форма решения названа в записи задачи до сложность: знакомое — все три узла названы в записи задачи до начала работы
работы — ни один признак wide не сработал, ни один признак quick метка: medium — максимум по осям; ни одна не дала large
тема дом глубина закрывает ревью дизайна: specs, rubric
requirements openspec/changes/<id>/specs/ сверка specs
autotests CLAUDE.md, семантика гейта — autotests
conventions docs/conventions/ сверка code
architecture docs/architecture.md, adr/ разбор basics
security docs/security.md разбор basics
operations docs/architecture.md, research/ разбор basics
данных нет docs/database.md отсутствует — никто
не темы: docs/tasks/ (каталог задач), docs/review.md (настройка конвейера) ревью кода, темы:
директивы: CLAUDE.md найден, AGENTS.md отсутствует тема дом глубина закрывает
requirements openspec/changes/<id>/specs/ разбор specs
autotests CLAUDE.md, семантика гейта — autotests
conventions docs/conventions/ разбор code
architecture docs/architecture.md разбор basics
+ источник docs/passport.md
security docs/security.md разбор basics
operations docs/architecture.md, «Эксплуатация» разбор basics
дома нет: docs/database.md отсутствует
процессные: docs/tasks/, docs/review.md, docs/adr/, docs/research/
директивы: CLAUDE.md найден, AGENTS.md отсутствует
``` ```
Обрати внимание на две строки этого образца, потому что обе раньше писались
неверно. `docs/passport.md` **не** заводит своей строки и **не** пропадает — он
стоит источником внутри темы `architecture`. Отсутствие `docs/database.md` **не**
порождает псевдотемы с исполнителем «никто» — оно понижает глубину темы
`operations`, и та остаётся за своим исполнителем.
Дальше — блок вопросов по темам из `docs/review.md`, **дословно**, с указанием, Дальше — блок вопросов по темам из `docs/review.md`, **дословно**, с указанием,
кому какой уходит. И обязательная строка: кому какой уходит. Вопрос, адресованный не теме (`passport`, `database`, `adr`,
`research`, `review`), не раздавай: таких тем нет. Скажи об этом строкой — это
находка о настройке проекта, и чинится она правкой `docs/review.md`.
И обязательная строка:
``` ```
## Coverage of this pass ## Coverage of this pass
- документов в docs/ найдено N, все N разнесены: тем M, не тем 2 - документов в docs/ найдено N, все N разнесены: тем M, источников K, процессных L
- тем без дома: <перечень или «нет»> - тем без дома: <перечень или «нет»>
- чего не смотрел: содержимого документов — по построению - вопросов по темам роздано: <число>; адресованных не теме: <перечень или «нет»>
- чего не смотрел: содержимого документов — по построению; кода и диффа — их ещё нет
``` ```
## Чего ты не делаешь ## Чего ты не делаешь
- **не судишь код** — ни одной находки по существу изменения; - **не судишь код** — ни одной находки по существу изменения;
- **не пересказываешь документы** (правило 3); - **не пересказываешь документы** (правило 3);
- **не выдумываешь тем** — тема приходит из документа или из директивы, а не из - **не выдумываешь тем** — тема приходит из своего документа проекта или из
представления о том, что стоило бы проверить; директивы, а не из представления о том, что стоило бы проверить, и **не из
- **не решаешь за человека о понижении**: понизить ступень ты вправе, но документа категорий `источник` и `процессный`**;
- **не оставляешь тему без исполнителя** — строки «закрывает: никто» не бывает;
- **не решаешь за человека о понижении**: понизить метку ты вправе, но
обоснование идёт в отчёт и читается человеком. обоснование идёт в отчёт и читается человеком.
## Ограничения ## Ограничения
Только чтение. `Bash` — для `ls`, `git diff --stat`, `grep` по заголовкам. Ничего Только чтение. `Bash` — для `ls` и `grep` по заголовкам. Ничего не запускай,
не запускай, ничего не редактируй. ничего не редактируй. `git diff` тебе не нужен: на момент твоего запуска кода
ещё нет.
+28 -7
View File
@@ -23,10 +23,30 @@ Development на OpenSpec). Оптика — требования, а не ст
- **`docs/architecture.md`** — компоненты и capability, и **что из них уже - **`docs/architecture.md`** — компоненты и capability, и **что из них уже
переехало в нормативные спеки**. Без этого непереехавшая тема читается как переехало в нормативные спеки**. Без этого непереехавшая тема читается как
пробел в спеке, и находка уходит в пустоту. пробел в спеке, и находка уходит в пустоту.
- **`docs/research/`** — как внешний мир ведёт себя на самом деле.
- **`docs/passport.md`** — граница домена: требование, переносящее понятие через - **`docs/passport.md`** — граница домена: требование, переносящее понятие через
неё, — находка в спеку, а не в код. неё, — находка в спеку, а не в код.
**`docs/research/` ты больше не читаешь.** Он процессный документ, и прогон ревью
его не открывает — ни один проход. Проверка «требование против записанного
наблюдения» из конвейера ушла: наблюдение неизвестной свежести делало находку
похожей на доказанную, ничего не доказывая. Скажи об этом строкой в границах
покрытия.
**Сколько ты читаешь, зависит от метки — она приходит в задании.**
| | `small` | `medium` и `large` |
|---|---|---|
| источник требований | **только дельта-спека change** | дельта + затронутые актуальные спеки |
| `design.md`, `tasks.md` change | не читаешь | читаешь |
| `docs/architecture.md`, `passport.md` | не читаешь | читаешь |
| `CLAUDE.md`, инварианты | читаешь всегда | читаешь всегда |
| потолок находок | **3** | нет |
На `small` это значит: сверка идёт против того, что заказано **этим изменением**,
и только. Что в актуальных спеках уже было и как это соотносится с обзором
архитектуры — не твой вопрос с этой меткой, и так и скажи в границах покрытия.
Потолок, если сработал, объяви: сколько осталось за срезом.
Пути спек жёсткие: актуальные — `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-pipeline/references/project-facts.md`.
@@ -49,12 +69,10 @@ Development на OpenSpec). Оптика — требования, а не ст
каждой слитой задачи. Задание обязано назвать этот режим явно; не названо — каждой слитой задачи. Задание обязано назвать этот режим явно; не названо —
работаешь по режиму 1 или 2 и говоришь в границах покрытия, что change не нашёл. работаешь по режиму 1 или 2 и говоришь в границах покрытия, что change не нашёл.
Дополнительно поднимаешь: `design.md` и `tasks.md` change, затронутые актуальные Дополнительно поднимаешь **с метки `medium`**: `design.md` и `tasks.md`
спеки, инварианты из `CLAUDE.md`. Если тема ещё не перенесена в спеки и живёт change, затронутые актуальные спеки. Инварианты из `CLAUDE.md` — при любой метке. Если тема ещё не перенесена в спеки и живёт только в
только в `docs/architecture.md` — источник истины там, и это фиксируется в `docs/architecture.md` — источник истины там, и это фиксируется в границах
границах покрытия. Отдельно: `docs/research/` нормой не является, но именно там покрытия.
записано, как внешний мир ведёт себя на самом деле; требование, противоречащее
наблюдению, — повод для находки в спеку.
## Режим 1 — дизайн/спеки ДО кода ## Режим 1 — дизайн/спеки ДО кода
@@ -168,8 +186,11 @@ Development на OpenSpec). Оптика — требования, а не ст
``` ```
## Coverage of this pass ## Coverage of this pass
- метка: <small | medium | large>; с меткой small — «источник только дельта-спека, актуальные спеки и обзор не читались»
- проверено: <какие Requirements, какие файлы диффа прочитаны> - проверено: <какие Requirements, какие файлы диффа прочитаны>
- потолок (только small): N/3 — и что осталось за срезом
- не проверялось и почему: ... - не проверялось и почему: ...
- требование против записанного наблюдения не проверялось: docs/research/ — процессный документ, прогон его не открывает
- принципиально недоступно этому проходу: форма решения, идиоматичность, эксплуатация - принципиально недоступно этому проходу: форма решения, идиоматичность, эксплуатация
``` ```
+49 -15
View File
@@ -1,6 +1,6 @@
--- ---
name: review-triage name: review-triage
description: "Обязательный финальный проход конвейера ревью — единственный, кто агрегирует. Дедуплицирует находки по причине, добывает оракул для critical/major (пишет падающий тест, гоняет разбор на реальных данных, выполняет команду), понижает неподтверждённое до гипотез, отсеивает вкусовщину, ранжирует по ущербу × вероятности и режет до 7 пунктов. Помечает каждую находку «инлайн» или «развилка» для оркестратора. Сверяет план разметчика с пришедшими отчётами: тема, размеченная и оставшаяся без отчёта, — находка о самом прогоне. Формирует итоговый отчёт с планом, перечнем проходов и обязательной секцией границ покрытия." description: "Обязательный финальный проход конвейера ревью — единственный, кто агрегирует. Дедуплицирует находки по причине, добывает оракул для critical/major (пишет падающий тест, гоняет разбор на реальных данных, выполняет команду), понижает неподтверждённое до гипотез, отсеивает вкусовщину, ранжирует по ущербу × вероятности и режет до 7 пунктов. Помечает каждую находку «инлайн» или «развилка» для оркестратора. Сверяет план разметки задачи с пришедшими отчётами: тема, размеченная и оставшаяся без отчёта, — находка о самом прогоне. Формирует итоговый отчёт с планом, перечнем проходов и обязательной секцией границ покрытия."
tools: Read, Grep, Glob, Bash, Write tools: Read, Grep, Glob, Bash, Write
model: opus model: opus
color: yellow color: yellow
@@ -21,13 +21,20 @@ color: yellow
## Вход ## Вход
Сырые выводы всех запущенных проходов, `git diff <база>..HEAD`, **план Сырые выводы всех запущенных проходов, `git diff <база>..HEAD`, **план разметки
разметчика** (`review-scope`, стадия 0) и режим прогона. Дельта-спеки — по мере задачи** (агент `review-scope`, один запуск после `propose`) и режим прогона.
надобности. Дельта-спеки — по мере надобности.
План — это таблица «тема → дом → глубина → кто закрывает» плюс ступень с План — это таблица «тема → дом → глубина → кто закрывает» плюс размер, сложность
обоснованием. Он твой главный инструмент сверки: ты единственный, кто видит и то, и метка с обоснованием. Он твой главный инструмент сверки: ты единственный, кто
что размечено, и то, что пришло. видит и то, что размечено, и то, что пришло.
**Плана нет — ты не запускаешься.** Сверка размеченного с пришедшим — твоя
единственная защита от молчащего пропуска, и без плана она не выполняется вовсе.
Отчёт, собранный без неё, выглядит полным ровно настолько же, насколько и
неполный. Исключение одно и объявленное: финальная сверка стыка в
`av-dev-pipeline:task-batch` — там разметчика нет по построению, и план тебе
собирает сам батч, коротким списком запущенного.
Из документов проекта тебе нужны: Из документов проекта тебе нужны:
@@ -78,8 +85,10 @@ color: yellow
- выполнить команду и приложить вывод; - выполнить команду и приложить вывод;
- показать поимённое положение руководства, строку конвенции проекта или **дословный - показать поимённое положение руководства, строку конвенции проекта или **дословный
пункт из раздела инвариантов `CLAUDE.md`**; пункт из раздела инвариантов `CLAUDE.md`**;
- сослаться на наблюдение в `docs/research/` — оно сильнее любого - сослаться на замер, снятый проходом **на этом прогоне**, с приложенной
рассуждения о том, «как должно быть». командой — он сильнее любого рассуждения о том, «как должно быть». На чужие
записанные наблюдения не ссылайся: `docs/research/` — процессный документ,
прогон его не открывает, и свежесть числа оттуда ничем не подтверждена.
Бюджет — по одной попытке на находку. Не превращай триаж в отдельное Бюджет — по одной попытке на находку. Не превращай триаж в отдельное
расследование. Ничего не запускай на рабочих данных — запреты в `CLAUDE.md`. расследование. Ничего не запускай на рабочих данных — запреты в `CLAUDE.md`.
@@ -158,8 +167,8 @@ severity:
показывал вовсе: список запущенного отвечал «все, кто должен был, отработали», а показывал вовсе: список запущенного отвечал «все, кто должен был, отработали», а
вопрос «что именно осталось непроверенным» задать было нечем. вопрос «что именно осталось непроверенным» задать было нечем.
Отдельно проверь **сигнал о заниженной ступени** от `review-basics`, если он Отдельно проверь **сигнал о заниженной метке** от `review-basics`, если он
pришёл. Ступень выбирал `review-scope`, а не он и не ты, — значит сигнал pришёл. Метка выбирал `review-scope`, а не он и не ты, — значит сигнал
независим, и место ему в сводке, а не в общем списке находок. независим, и место ему в сводке, а не в общем списке находок.
## Границы покрытия — не сокращаются ## Границы покрытия — не сокращаются
@@ -167,8 +176,8 @@ pришёл. Ступень выбирал `review-scope`, а не он и не
Финальная секция сводит границы всех проходов. Обязательно называет: Финальная секция сводит границы всех проходов. Обязательно называет:
- **план: темы, их глубины и дома** — включая темы, у которых дома нет; - **план: темы, их глубины и дома** — включая темы, у которых дома нет;
- какие проходы запускались, в каком профиле и режиме; - какие проходы запускались, на какой метке и в каком режиме;
- какие **не** запускались и почему (профиль, бюджет, недоступный инструмент, - какие **не** запускались и почему (метка, бюджет, недоступный инструмент,
остановленный прогон); остановленный прогон);
- что каждый запущенный проход **не мог проверить в принципе** — из его charter'а; - что каждый запущенный проход **не мог проверить в принципе** — из его charter'а;
- **что осталось целиком на человеке** — «Недоступно проверке» из `docs/review.*`, - **что осталось целиком на человеке** — «Недоступно проверке» из `docs/review.*`,
@@ -182,7 +191,32 @@ pришёл. Ступень выбирал `review-scope`, а не он и не
- **каких документов проекта не хватило** — строкой на каждый, **с причиной**: - **каких документов проекта не хватило** — строкой на каждый, **с причиной**:
«`docs/security.md` в проекте нет», «есть, но периметр не назван». Строки «`docs/security.md` в проекте нет», «есть, но периметр не назван». Строки
приходят из проходов; слить их в одну «документации не было» нельзя — приходят из проходов; слить их в одну «документации не было» нельзя —
деградация поразрядная, и разные пробелы чинятся разным. деградация поразрядная, и разные пробелы чинятся разным;
- **сработавшие потолки** — по строке на проход: сколько находок он показал,
каков был его потолок и что осталось за срезом. Проход обязан сообщить это сам;
не сообщил — так и напиши, это находка о прогоне.
**Четыре строки ты пишешь сам, на каждом прогоне, и ни один проход их не
принесёт.** Они про то, чего в конвейере нет вовсе, — а значит некому и
пожаловаться:
1. **Решения проекта не сверялись.** `docs/adr.*` — процессный документ, прогон
его не открывает. Расхождение изменения с записанным решением ловит сверка
документации между спринтами, а не ревью.
2. **Записанные наблюдения проекта не использовались.** `docs/research.*` — тоже
процессный. Всякое число в находках снято проходом на этом прогоне; числа без
приложенной команды замера в отчёте быть не должно.
3. **Поимённая сверка с руководствами по стилю языка не задавалась ни одним
проходом.** Различение «идиоматично против распространено» не спрашивает никто
с тех пор, как упразднён проход про идиоматичность.
4. **Альтернативной реализации, с которой можно сдиффить решения, у конвейера
нет.** Проход независимой реализации снят по стоимости, а не по замеру; «не
знаю, чего не знаю» больше не достаёт никто.
Плюс **с меткой `small`** — пятая строка: темы `security`, `operations` и
`architecture` сверялись только с записанными инвариантами `CLAUDE.md`, дома этих
тем не открывались. Свойство, которого нет в инвариантах, с этой меткой не
проверил никто.
Формулировка «критичных проблем не обнаружено» **запрещена** без этой секции: она Формулировка «критичных проблем не обнаружено» **запрещена** без этой секции: она
потребляет ощущение проверенности, ничего не гарантируя, и это хуже, чем потребляет ощущение проверенности, ничего не гарантируя, и это хуже, чем
@@ -199,7 +233,7 @@ pришёл. Ступень выбирал `review-scope`, а не он и не
Строго секциями из контракта: `Блокирует мердж` (≤3) / `Стоит исправить сейчас` Строго секциями из контракта: `Блокирует мердж` (≤3) / `Стоит исправить сейчас`
(≤4) / `Гипотезы без доказательства` / `Promote candidates` / `Границы покрытия`. (≤4) / `Гипотезы без доказательства` / `Promote candidates` / `Границы покрытия`.
Перед секциями — сводка: ступень с обоснованием разметчика и режим прогона, Перед секциями — сводка: размер, сложность и метка с обоснованием разметки и режим прогона,
состояние гейта, **план с исходом по каждой теме**, сколько находок пришло на состояние гейта, **план с исходом по каждой теме**, сколько находок пришло на
вход и сколько осталось. вход и сколько осталось.
File diff suppressed because it is too large Load Diff
@@ -27,7 +27,7 @@
```mermaid ```mermaid
stateDiagram-v2 stateDiagram-v2
state "проход в профиле" as live state "проход в составе метки" as live
state "retune №1 — правка charter'а" as r1 state "retune №1 — правка charter'а" as r1
state "retune №2 — последняя попытка" as r2 state "retune №2 — последняя попытка" as r2
state "проход удалён" as dead state "проход удалён" as dead
@@ -101,7 +101,7 @@ stateDiagram-v2
## Когда калибровать ## Когда калибровать
- при заведении нового прохода — **до** включения в профиль по умолчанию; - при заведении нового прохода — **до** включения в состав метки по умолчанию;
- при правке charter'а существующего — иначе непонятно, правка помогла или нет; - при правке charter'а существующего — иначе непонятно, правка помогла или нет;
- при появлении записи в журнале проскочивших дефектов — калибруем тот проход, - при появлении записи в журнале проскочивших дефектов — калибруем тот проход,
который должен был поймать; который должен был поймать;
@@ -13,7 +13,7 @@
- Оракул: <падающий тест / команда с выводом / положение руководства / нет> - Оракул: <падающий тест / команда с выводом / положение руководства / нет>
- Последствие: <что произойдёт и при каких условиях> - Последствие: <что произойдёт и при каких условиях>
- Предложение: <конкретное изменение> - Предложение: <конкретное изменение>
- Найдено проходом: <имя агента> - Найдено проходом: <имя агента; у проходов с раздельными потолками — имя и половина, например `code/техника`>
``` ```
## Правила ## Правила
@@ -76,10 +76,16 @@
4. `Promote candidates` — кандидаты в конвенцию или правило линтера; 4. `Promote candidates` — кандидаты в конвенцию или правило линтера;
5. `Границы покрытия` — сводная, обязательная. 5. `Границы покрытия` — сводная, обязательная.
Перед секциями — сводка для человека: профиль и режим прогона, состояние гейта, Перед секциями — сводка для человека: размер, сложность, метка и режим
**перечень запущенных проходов поимённо с исходом каждого**, сколько находок прогона, состояние гейта, **план разметки задачи с исходом по каждой теме**,
пришло на вход и сколько осталось. Перечень обязателен: пропуск прохода не сколько находок пришло на вход и сколько осталось.
отличим от прохода без находок, и назвать его больше некому.
**Реестр сводки — темы, а не проходы, и это не оформление.** Перечень запущенных
проходов отвечает «все, кто должен был, отработали» и молчит о том, что именно
осталось непроверенным: уехавший в старшую метку проход уносит тему с собой
беззвучно. План же называет тему, её дом, глубину и исполнителя — и тема,
оставшаяся без отчёта, видна сразу. Перечень проходов из сводки не исчезает, но
идёт **внутри** плана, колонкой «кто закрывает».
Каждая находка в секциях 1–2 несёт дополнительное поле: Каждая находка в секциях 1–2 несёт дополнительное поле:
@@ -15,7 +15,7 @@
## Карта тем ## Карта тем
**Дом бывает файлом или каталогом** — `docs/security.md` и `docs/security/` **Дом бывает файлом или каталогом** — `docs/security.md` и `docs/security/`
называют одну и ту же тему. Форму дома называет план разметчика; проход её не называют одну и ту же тему. Форму дома называет план разметки задачи; проход её не
угадывает. угадывает.
| Тема | Дом | Что оттуда берётся | | Тема | Дом | Что оттуда берётся |
@@ -24,13 +24,21 @@
| `autotests` | `CLAUDE.md`, семантика гейта | команда гейта, чем краснеет безусловно, чего в нём нет, кто гоняет дорогое | | `autotests` | `CLAUDE.md`, семантика гейта | команда гейта, чем краснеет безусловно, чего в нём нет, кто гоняет дорогое |
| `conventions` | `docs/conventions.*` | конвенции прозой и **что уже механизировано** правилом | | `conventions` | `docs/conventions.*` | конвенции прозой и **что уже механизировано** правилом |
| `architecture` | `docs/architecture.*` | компоненты и capability, единые точки проекта | | `architecture` | `docs/architecture.*` | компоненты и capability, единые точки проекта |
| | `docs/passport.*` | что система делает и **чего не делает**, граница домена | | | источник `docs/passport.*` | что система делает и **чего не делает**, граница домена |
| | `docs/adr/` | почему решено так, отвергнутые варианты |
| `security` | `docs/security.*` | периметр, недоверенный вход, из чего строятся пути и ключи, что вне модели | | `security` | `docs/security.*` | периметр, недоверенный вход, из чего строятся пути и ключи, что вне модели |
| `operations` | `docs/architecture.*`, раздел эксплуатации | окружение, внешние зависимости поимённо, наблюдатель, характер потока | | `operations` | `docs/architecture.*`, раздел эксплуатации | окружение, внешние зависимости поимённо, наблюдатель, характер потока |
| | `docs/database.*` | чем физически лежит запись, что при чтении и записи, настройки с числовым значением | | | источник `docs/database.*` | чем физически лежит запись, что при чтении и записи, настройки с числовым значением |
| | `docs/research/` | измеренные числа **с провенансом**, поведение внешних систем на самом деле | | *тема проекта* | её **свой** документ в `docs/` | то, что проект счёл нужным записать |
| *тема проекта* | её документ в `docs/` | то, что проект счёл нужным записать |
**`docs/adr.*` и `docs/research.*` в этой карте нет намеренно.** Они процессные
документы: прогон ревью их не открывает. Раньше первый питал тему `architecture`,
второй — `operations` и `requirements`; обе строки убраны, и цена этого названа в
`SKILL.md`, раздел «Честный предел».
**Дом темы зависит ещё и от метки.** На `small` темы `security`, `operations` и
`architecture` смотрятся не против домов из этой таблицы, а против **инвариантов
`CLAUDE.md`**, и закрывает их `code`. Таблица описывает полный дом темы; сколько
из него открыто на этом прогоне, говорит план разметки задачи.
Сквозное, не привязанное к теме: Сквозное, не привязанное к теме:
@@ -38,12 +46,12 @@
| --- | --- | | --- | --- |
| инварианты **с severity рядом с формулировкой** | `CLAUDE.md``AGENTS.md`, если он рядом), раздел инвариантов | | инварианты **с severity рядом с формулировкой** | `CLAUDE.md``AGENTS.md`, если он рядом), раздел инвариантов |
| что запускать запрещено, с путями; `testdata`; куда писать временное; имя основной ветки | `CLAUDE.md` | | что запускать запрещено, с путями; `testdata`; куда писать временное; имя основной ветки | `CLAUDE.md` |
| типовые узлы, типовые ложноположительные, **вопросы по темам**, триггеры профиля, недоступно проверке | `docs/review.*`, раздел настройки | | типовые узлы, типовые ложноположительные, **вопросы по темам**, триггеры метки, недоступно проверке | `docs/review.*`, раздел настройки |
| прецеденты: воспроизведённые дефекты с оракулом | `docs/review.*`, журнал | | прецеденты: воспроизведённые дефекты с оракулом | `docs/review.*`, журнал |
**Вопросы проекта привязаны к теме, а не к имени прохода.** Раньше блок в **Вопросы проекта привязаны к теме, а не к имени прохода.** Раньше блок в
`docs/review.md` адресовался поимённо (`ops: <вопрос>`), и когда проход уехал в `docs/review.md` адресовался поимённо (`ops: <вопрос>`), и когда проход уехал в
верхнюю ступень, вопрос перестал задаваться молча. Тема переезд прохода старшую метку, вопрос перестал задаваться молча. Тема переезд прохода
переживает. переживает.
## Сшивать обязаны проходы ## Сшивать обязаны проходы
@@ -57,8 +65,10 @@
- **замер + настройка.** «Пик 768 МиБ» — аномалия только рядом со строкой - **замер + настройка.** «Пик 768 МиБ» — аномалия только рядом со строкой
«запись лежит сжатой и распаковывается целиком»; «блокировка удерживалась «запись лежит сжатой и распаковывается целиком»; «блокировка удерживалась
5.019 с» — гарантированный отказ соседа только рядом с известным таймаутом 5.019 с» — гарантированный отказ соседа только рядом с известным таймаутом
занятости. Числа в `docs/research/`, настройки в `docs/database.md`, и оба занятости. **Число проход снимает сам, на этом прогоне**, настройки берёт из
читает `ops` и `adversary`. `docs/database.md`, и сшивают их `ops` и `adversary`. Раньше числа брались из
`docs/research/`; теперь этот документ процессный, и замер неизвестной свежести
больше не выдаёт себя за оракул.
- **инвариант + обратимость.** severity берётся из `CLAUDE.md`; если её там - **инвариант + обратимость.** severity берётся из `CLAUDE.md`; если её там
нет — она **выводится по обратимости последствия** и помечается «выведена по нет — она **выводится по обратимости последствия** и помечается «выведена по
обратимости», а не выдаётся за решение проекта. обратимости», а не выдаётся за решение проекта.
@@ -67,7 +77,9 @@
с настройкой ему нечего; единственное его основание для `critical` — инвариант из с настройкой ему нечего; единственное его основание для `critical` — инвариант из
`CLAUDE.md`, всё остальное он формулирует условиями и оставляет гипотезой. Его `CLAUDE.md`, всё остальное он формулирует условиями и оставляет гипотезой. Его
вход намеренно узкий: дома тем из плана плюс инварианты и журнал. Широкий вход — вход намеренно узкий: дома тем из плана плюс инварианты и журнал. Широкий вход —
это профиль `wide`, и там он есть у `architecture`. это метка `large`, и там он есть у `architecture`. Греп по базе ему разрешён
точечный — «есть ли второй вызывающий», — но обход всей базы и инвентарь
концепций не его работа.
**У `scope` стыков нет по другой причине: он не читает содержимого.** Его дело — **У `scope` стыков нет по другой причине: он не читает содержимого.** Его дело —
найти дома и раздать темы, а не пересказать написанное. Пересказ сделал бы его найти дома и раздать темы, а не пересказать написанное. Пересказ сделал бы его
@@ -83,7 +95,7 @@
**Кто какой документ читает — из документа не выводится, а назначается планом.** **Кто какой документ читает — из документа не выводится, а назначается планом.**
Документ питает тему (это записано на стороне канона, таблица «Роли документов и Документ питает тему (это записано на стороне канона, таблица «Роли документов и
темы ревью»), а тему на этом прогоне закрывает тот, кого назвал разметчик; вся темы ревью»), а тему на этом прогоне закрывает тот, кого назвала разметка задачи; вся
раскладка «тема → проход → глубина» — в `SKILL.md` этого скилла и больше нигде. раскладка «тема → проход → глубина» — в `SKILL.md` этого скилла и больше нигде.
**Списка читателей не ведёт никто, и это не пробел.** Он жил бы на стороне **Списка читателей не ведёт никто, и это не пробел.** Он жил бы на стороне
канона, а документ живёт дольше, чем раскладка проходов: список разошёлся бы с канона, а документ живёт дольше, чем раскладка проходов: список разошёлся бы с
@@ -96,10 +108,8 @@
| --- | --- | | --- | --- |
| `CLAUDE.md` без инвариантов | `critical` по основанию «нарушен инвариант проекта» не присваивается никем | | `CLAUDE.md` без инвариантов | `critical` по основанию «нарушен инвариант проекта» не присваивается никем |
| `docs/security.*` | тема `security` остаётся без дома: вопросы задаются по коду, `critical` не ставится, периметр неизвестен | | `docs/security.*` | тема `security` остаётся без дома: вопросы задаются по коду, `critical` не ставится, периметр неизвестен |
| `docs/research/` | числа неизвестны темам `operations` и `requirements` — формулируют условиями, а `specs` теряет проверку «требование против наблюдения» |
| `docs/database.*` | замер не с чем сравнить: находка темы `operations` не поднимается выше гипотезы | | `docs/database.*` | замер не с чем сравнить: находка темы `operations` не поднимается выше гипотезы |
| `docs/passport.*` | тема `architecture` теряет границу домена и вырождается в общее мнение | | `docs/passport.*` | тема `architecture` теряет границу домена и вырождается в общее мнение |
| `docs/adr/` | «не отменяет ли изменение записанное решение» не спрашивает никто |
| `docs/review.*` | `triage` отсеивает вслепую: типовых ложноположительных нет; вопросы проекта по темам не задаются | | `docs/review.*` | `triage` отсеивает вслепую: типовых ложноположительных нет; вопросы проекта по темам не задаются |
| `docs/conventions.*` | вторая половина `code` идёт вхолостую: записанных конвенций нет | | `docs/conventions.*` | вторая половина `code` идёт вхолостую: записанных конвенций нет |
| `docs/architecture.*` | «не появился ли второй способ» не проверяется — единых точек не знает никто; тема `operations` теряет перечень внешних зависимостей | | `docs/architecture.*` | «не появился ли второй способ» не проверяется — единых точек не знает никто; тема `operations` теряет перечень внешних зависимостей |
@@ -6,7 +6,7 @@
и один и тот же класс проскакивает второй раз. и один и тот же класс проскакивает второй раз.
Тот же файл держит **настройку конвейера под проект** — типовые узлы, типовые Тот же файл держит **настройку конвейера под проект** — типовые узлы, типовые
ложноположительные, вопросы к проходам, недоступно проверке. Это не соседство по ложноположительные, вопросы по темам, недоступно проверке. Это не соседство по
случаю: все четыре раздела — производные калибровки, а журнал им источник. случаю: все четыре раздела — производные калибровки, а журнал им источник.
## Что туда попадает ## Что туда попадает
@@ -29,7 +29,7 @@
и `docs/adr/`. и `docs/adr/`.
Отдельно сюда попадают **решения о составе прогонов**: перестали звать проход, Отдельно сюда попадают **решения о составе прогонов**: перестали звать проход,
понизили профиль правилом, сузили класс проверяемого. Не потому, что это промах, понизили метку правилом, сузили класс проверяемого. Не потому, что это промах,
а потому, что здесь лежит цена: если что-то теперь проскочит, первый вопрос — а потому, что здесь лежит цена: если что-то теперь проскочит, первый вопрос —
«не тот ли это класс, который мы перестали проверять». «не тот ли это класс, который мы перестали проверять».
@@ -77,13 +77,13 @@
Три адреса, и выбор между ними — половина ценности журнала: Три адреса, и выбор между ними — половина ценности журнала:
- **в документ проекта** — если проход не мог знать факта. Адрес зависит от рода - **в документ проекта** — если проход не мог знать факта. Адрес зависит от рода
факта, и карта их всех — [project-facts.md](project-facts.md): объём и факта, и карта их всех — [project-facts.md](project-facts.md):
измеренное число → `docs/research/`; настройка хранилища → `docs/database.md`; настройка хранилища → `docs/database.md`;
что необратимо и какой шаг гейта красит безусловно → `CLAUDE.md`; периметр и что необратимо и какой шаг гейта красит безусловно → `CLAUDE.md`; периметр и
недоверенный вход → `docs/security.*`. **Вопрос по теме**, если промах лечится недоверенный вход → `docs/security.*`. **Вопрос по теме**, если промах лечится
не фактом, а заданным вопросом, → раздел «Вопросы по темам» того же не фактом, а заданным вопросом, → раздел «Вопросы по темам» того же
`docs/review.*`; адресуй теме, а не имени прохода — проход уедет между `docs/review.*`; адресуй теме, а не имени прохода — проход уедет между
ступенями, тема останется. Самый частый адрес и самый дешёвый. Прежде чем метками, тема останется. Самый частый адрес и самый дешёвый. Прежде чем
править charter, проверь, не хватит ли факта или вопроса: charter общий для править charter, проверь, не хватит ли факта или вопроса: charter общий для
всех проектов, документ — про этот. всех проектов, документ — про этот.
- **в конвенции или в правило линтера** — если свойство выражается - **в конвенции или в правило линтера** — если свойство выражается
+46 -21
View File
@@ -51,7 +51,7 @@ description: Проводит несколько задач разом — пл
- **Батч не владеет спринтом и целями.** Он сообщает исход по каждой задаче в тех - **Батч не владеет спринтом и целями.** Он сообщает исход по каждой задаче в тех
же трёх словах, что и `task-pipeline`: сделана / не доведена / оказалась крупнее же трёх словах, что и `task-pipeline`: сделана / не доведена / оказалась крупнее
задачи. задачи.
- **Задачи закрывает пайплайн внутри каждого сабагента**, шагом 11 — после - **Задачи закрывает пайплайн внутри каждого сабагента**, шагом 12 — после
коммита работы и **отдельным коммитом учёта**, вызовом Skill `av-dev-pm:tasks`. коммита работы и **отдельным коммитом учёта**, вызовом Skill `av-dev-pm:tasks`.
Батч сам записей учёта не трогает: он не знает, чем кончилась приёмка, и Батч сам записей учёта не трогает: он не знает, чем кончилась приёмка, и
дублировать закрытие ему незачем. Но грязное дерево после сабагента — **его** дублировать закрытие ему незачем. Но грязное дерево после сабагента — **его**
@@ -104,21 +104,20 @@ description: Проводит несколько задач разом — пл
параллельном она гонится в волне **одна** (обоснование — ниже, в шаге 4). параллельном она гонится в волне **одна** (обоснование — ниже, в шаге 4).
Помечается здесь, на планировании, а не во время прогона, и **независимо от Помечается здесь, на планировании, а не во время прогона, и **независимо от
режима**: состав волны определяется сейчас, режим может смениться просьбой уже режима**: состав волны определяется сейчас, режим может смениться просьбой уже
после плана, а ступень ревью выберет разметчик уже внутри прогона — ключевать после плана, а метка ревью назовёт разметчик уже внутри пайплайна задачи,
волну на ещё не сделанный выбор нельзя. Триггеры — по фактам о задаче, каждый после `propose`, — ключевать волну на ещё не сделанный выбор нельзя. Триггеры — по фактам о задаче, каждый
сам по себе достаточен: сам по себе достаточен:
- трогает схему хранилища, миграцию, формат на диске или объём хранимого; - трогает схему хранилища, миграцию, формат на диске или объём хранимого;
- трогает конкурентность: транзакции, блокировки, фоновые циклы, общее - трогает конкурентность: транзакции, блокировки, фоновые циклы, общее
состояние; состояние;
- трогает размер тела, буфер, память, сжатие, ретеншен, темп потока; - трогает размер тела, буфер, память, сжатие, ретеншен, темп потока;
- её тема названа в `docs/research/` или в журнале `docs/review.md` как место, - её тема названа в журнале `docs/review.md` как место, где уже ломалось.
где уже мерили или уже ломалось.
Ни один триггер не сработал — задача не замеряющая, даже если её ревью Ни один триггер не сработал — задача не замеряющая, даже если её ревью
окажется `wide`. Ступень про глубину проверки, замеряющая — про соревнование за окажется `large`. Метка про глубину проверки, замеряющая — про соревнование за
железо; это разные вопросы, и совпадают они не всегда. Обратное тоже бывает и железо; это разные вопросы, и совпадают они не всегда. Обратное тоже бывает и
тоже законно: помеченная задача, чьё ревью пошло профилем `quick` или тоже законно: помеченная задача, чьё ревью пошло меткой `small` или
`standard`, машину не займёт вовсе — меряющие проходы живут только в `wide`. `medium`, машину не займёт вовсе — меряющие проходы живут только в `large`.
Пометка от этого не снимается: она ставится **до** разметки, и перестраховка Пометка от этого не снимается: она ставится **до** разметки, и перестраховка
здесь стоит одной волны, а ошибка — испорченных чисел; здесь стоит одной волны, а ошибка — испорченных чисел;
- **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект - **нумерованные артефакты — номера раздаёт оркестратор заранее.** Если проект
@@ -238,8 +237,10 @@ flowchart TD
- прогони Skill **`av-dev-pipeline:task-pipeline`** ровно на этой задаче, - прогони Skill **`av-dev-pipeline:task-pipeline`** ровно на этой задаче,
полный цикл SDD с обоими чекпоинтами ревью; полный цикл SDD с обоими чекпоинтами ревью;
- если задаче назначен **номер артефакта** — используй строго его; - если задаче назначен **номер артефакта** — используй строго его;
- **ступень ревью выбирает разметчик конвейера, а не ты и не сабагент.** - **метка ревью выбирает разметчик конвейера, а не ты и не сабагент.** Он
Профиль в вызов не передаётся вовсе. Батч не повод её понижать: «нас много и идёт внутри пайплайна задачи, шагом 4, сразу после `propose`, и его план
обслуживает оба чекпоинта. Метка в вызов не передаётся вовсе. Батч не
повод её понижать: «нас много и
мы спешим» — ровно тот стимул, из-за которого проходы пропускают, и он снят мы спешим» — ровно тот стимул, из-за которого проходы пропускают, и он снят
тем, что регулятор не в руках у автора; тем, что регулятор не в руках у автора;
- **режим прогона проходов ревью — от режима батча**, и его называет charter, - **режим прогона проходов ревью — от режима батча**, и его называет charter,
@@ -249,15 +250,15 @@ flowchart TD
Внутренние рёбра графа — цепочку проходов, держащих машину — конвейер Внутренние рёбра графа — цепочку проходов, держащих машину — конвейер
соблюдает сам, в любом режиме; соблюдает сам, в любом режиме;
- **если вложенные сабагенты недоступны** (движок не даёт запускать агентов из - **если вложенные сабагенты недоступны** (движок не даёт запускать агентов из
агента) — не пропускай ревью и не понижай ступень: проведи его **инлайн** по агента) — не пропускай ревью и не понижай метка: проведи его **инлайн** по
тем же charter'ам `av-dev-pipeline`, сохранив обязательное — разметку первой тем же charter'ам `av-dev-pipeline`, сохранив обязательное — разметку первой
(план с темами и ступенью), гейт до опиниативных проходов, состав по плану, (план с темами и меткой), гейт до опиниативных проходов, состав по плану,
триаж последним. И **скажи в отчёте прямым текстом, что ревью шло инлайн**: триаж последним. И **скажи в отчёте прямым текстом, что ревью шло инлайн**:
инлайновый проход видит контекст автора и потому разведён с ним слабее — а инлайновый проход видит контекст автора и потому разведён с ним слабее — а
инлайновая разметка вдобавок означает, что ступень выбрал автор, и это инлайновая разметка вдобавок означает, что метка выбрал автор, и это
отдельная строка; отдельная строка;
- **вернуть отчёт**, в котором обязательно: исход задачи одним из трёх слов; - **вернуть отчёт**, в котором обязательно: исход задачи одним из трёх слов;
**план прогона: ступень с обоснованием, темы и их глубины**, и режим; что сделано; какие вопросы **план прогона: метка с обоснованием, темы и их глубины**, и режим; что сделано; какие вопросы
записаны и куда; изменённые файлы; добавлялся ли нумерованный артефакт и с записаны и куда; изменённые файлы; добавлялся ли нумерованный артефакт и с
каким номером; затронутые capability; состояние гейта; **перечень каким номером; затронутые capability; состояние гейта; **перечень
тем с исходом по каждой**; **путь к тем с исходом по каждой**; **путь к
@@ -284,9 +285,9 @@ flowchart TD
Сверка идёт в три шага, и порядок важен: Сверка идёт в три шага, и порядок важен:
1. **Возьми план прогона** из отчёта задачи — таблицу «тема → дом → глубина → кто 1. **Возьми план прогона** из отчёта задачи — таблицу «тема → дом → глубина → кто
закрывает» со ступенью и обоснованием; он затем и заказан в обязательных полях закрывает» с меткой и обоснованием; он затем и заказан в обязательных полях
шага 4. Плана в отчёте нет — сверять не с чем; это само по себе основание не шага 4. Плана в отчёте нет — сверять не с чем; это само по себе основание не
вливать, пока сабагент не покажет план разметчика. вливать, пока сабагент не покажет план разметки задачи.
2. **Сверяй с независимым артефактом, а не с прозой отчёта.** Перечень проходов 2. **Сверяй с независимым артефактом, а не с прозой отчёта.** Перечень проходов
бери из **сохранённого отчёта триажа** (`openspec/changes/<id>/review/` или бери из **сохранённого отчёта триажа** (`openspec/changes/<id>/review/` или
`openspec/changes/archive/<id>/review/` — задача доведена, change заархивирован) — `openspec/changes/archive/<id>/review/` — задача доведена, change заархивирован) —
@@ -294,13 +295,18 @@ flowchart TD
проход и пропустить: она подтверждает сама себя. Отчёта триажа на месте нет — проход и пропустить: она подтверждает сама себя. Отчёта триажа на месте нет —
считай, что состав неизвестен, и дозапускай ревью целиком. считай, что состав неизвестен, и дозапускай ревью целиком.
3. **Сверь план с исходом**: против каждой темы плана обязан стоять отчёт либо 3. **Сверь план с исходом**: против каждой темы плана обязан стоять отчёт либо
названная причина его отсутствия. Раскладка «тема → кто закрывает на этой названная причина его отсутствия. Раскладка «тема → кто закрывает с этой меткой» — в скилле `av-dev-pipeline:review-pipeline`.
ступени» — в скилле `av-dev-pipeline:review-pipeline`.
Расхождение — не повод отменять задачу: дозапусти недостающие проходы **на Расхождение — не повод отменять задачу: дозапусти недостающие проходы **на
ветке**, в её worktree, через `av-dev-pipeline:review-pipeline`, и только потом ветке**, в её worktree, через `av-dev-pipeline:review-pipeline`, и только потом
интегрируй. интегрируй.
**Передай в дозапуск тот же план.** Триаж требует его обязательным входом — без
плана он не может сверить, все ли размеченные темы вернули отчёт, а эта сверка и
есть то, ради чего дозапуск затевается. Плана не осталось (сабагент не сохранил
его в отчёте) — пусть повторит разметку задачи: это самый дешёвый проход
конвейера, и он дешевле, чем прогон, который нечем сверить.
**Находки дозапуска — такие же находки, и зелёный гейт их не отменяет.** Правило **Находки дозапуска — такие же находки, и зелёный гейт их не отменяет.** Правило
интеграции «вливаем только зелёные» смотрит на гейт, а дозапущенный `critical` интеграции «вливаем только зелёные» смотрит на гейт, а дозапущенный `critical`
гейт не красит: он был бы пропущен молча, если это не сказать прямо. Поэтому: гейт не красит: он был бы пропущен молча, если это не сказать прямо. Поэтому:
@@ -337,7 +343,7 @@ rebase делается **внутри worktree задачи**, а ff-слиян
(`git -C <path> rebase --abort`), оставь ветку и worktree как есть, вынеси это (`git -C <path> rebase --abort`), оставь ветку и worktree как есть, вынеси это
в доклад как нераспознанное пересечение; в доклад как нераспознанное пересечение;
- **дерево сабагента обязано быть чистым.** `git -C <path> status --porcelain` - **дерево сабагента обязано быть чистым.** `git -C <path> status --porcelain`
до `rebase`: непусто — значит сабагент не довёл шаг 11 до коммита учёта (или до `rebase`: непусто — значит сабагент не довёл шаг 12 до коммита учёта (или
оставил мусор). Не форсируй и не коммить за него: назови задачу в докладе оставил мусор). Не форсируй и не коммить за него: назови задачу в докладе
недоведённой и оставь ветку с worktree. Молчаливый `rebase` на грязном дереве недоведённой и оставь ветку с worktree. Молчаливый `rebase` на грязном дереве
всё равно откажет, но с сообщением про unstaged changes — а причина другая; всё равно откажет, но с сообщением про unstaged changes — а причина другая;
@@ -395,7 +401,26 @@ rebase в файле X», а не «нераспознанное пересеч
- **заверши триажем.** Он единственный, кто агрегирует, и без него у находок нет - **заверши триажем.** Он единственный, кто агрегирует, и без него у находок нет
ни оракула, ни пометки `инлайн`/`развилка` — а следующий абзац на неё ни оракула, ни пометки `инлайн`/`развилка` — а следующий абзац на неё
опирается. Прогон из двух проходов без триажа — это сырые находки, выданные за опирается. Прогон из двух проходов без триажа — это сырые находки, выданные за
разобранные. разобранные;
- **план триажу собери сам, здесь, — разметчика на этой сверке нет.** Триаж
требует план обязательным входом: он сверяет размеченное с пришедшим, и без
плана эта сверка не выполняется вовсе. Разметка задачи сюда не годится — она
описывала одну задачу, а сверка идёт по интегрированной ветке. План здесь
короткий и составляется по факту запуска:
```
метка: не применяется — сверка стыка, а не ревью изменения
тема дом глубина закрывает
requirements openspec/specs/<capability-1>/ разбор specs (стык)
requirements openspec/specs/<capability-2>/ разбор specs (стык)
architecture docs/architecture.md разбор architecture
+ источник docs/passport.md
```
Темы, которых в этом списке нет (`autotests`, `conventions`, `security`,
`operations`, свои темы проекта), назови строкой «не проверяется на сверке
стыка: закрыто прогонами отдельных задач». Это не формальность — без такой
строки отчёт сверки читается как полное ревью ветки.
Граф этой сверки — веер в один сток, и он такой же, как у обычного прогона: Граф этой сверки — веер в один сток, и он такой же, как у обычного прогона:
@@ -421,7 +446,7 @@ flowchart TD
`git worktree prune`. Worktree и ветки **провалившихся** не трогай — они нужны `git worktree prune`. Worktree и ветки **провалившихся** не трогай — они нужны
для ручного дожатия. для ручного дожатия.
- **Записей учёта батч не трогает** — их правит пайплайн внутри сабагента на - **Записей учёта батч не трогает** — их правит пайплайн внутри сабагента на
шаге 11. Батч сообщает исход по каждой задаче; если какой-то сабагент дошёл до шаге 12. Батч сообщает исход по каждой задаче; если какой-то сабагент дошёл до
коммита, но закрытия не сделал (плагина нет, вызов не разрешился), скажи это коммита, но закрытия не сделал (плагина нет, вызов не разрешился), скажи это
строкой — иначе задача останется открытой молча. строкой — иначе задача останется открытой молча.
- Доложи кратко: - Доложи кратко:
+119 -56
View File
@@ -1,6 +1,6 @@
--- ---
name: task-pipeline name: task-pipeline
description: Автономно проводит одну задачу через полный цикл Spec Driven Development — от постановки до коммита (opsx explore→propose→ревью спек профилем design→apply→ревью кода→archive→коммит), с обязательными чекпоинтами ревью и докладом об исходе. Использовать, когда просят взять/сделать задачу или довести идею до реализации. description: "Автономно проводит одну задачу через полный цикл Spec Driven Development — от постановки до коммита (opsx explore→propose→разметка задачи→ревью дизайна→apply→ревью кода→archive→коммит), с обязательными чекпоинтами ревью и докладом об исходе. Разметка идёт один раз, сразу после propose: она называет размер, сложность и метка, и её план определяет состав обеих стадий ревью. Использовать, когда просят взять/сделать задачу или довести идею до реализации."
--- ---
# Пайплайн задачи # Пайплайн задачи
@@ -11,12 +11,13 @@ description: Автономно проводит одну задачу чере
Это тонкая обёртка над каноническими скиллами `opsx:explore` / `opsx:propose` / Это тонкая обёртка над каноническими скиллами `opsx:explore` / `opsx:propose` /
`opsx:apply` / `opsx:archive` — вызывай их через Skill, не переизобретай их шаги. `opsx:apply` / `opsx:archive` — вызывай их через Skill, не переизобретай их шаги.
Ревью — скилл `av-dev-pipeline:review-pipeline`; он же держит правило выбора Ревью — скилл `av-dev-pipeline:review-pipeline`; он же держит правило выбора
ступени и **сам её выбирает**: ты профиль не передаёшь. метки, а называет её агент `review-scope` на шаге 4 — один раз на задачу, для
обеих стадий ревью.
## Предпосылки ## Предпосылки
- **OpenSpec и скиллы `opsx:*` — жёсткая предпосылка, а не опция.** На них стоят - **OpenSpec и скиллы `opsx:*` — жёсткая предпосылка, а не опция.** На них стоят
шаги 2, 3, 6 и 8, проход `review-specs` и профиль `design` (они завязаны на шаги 2, 3, 7 и 9, проход `review-specs` и ревью дизайна (они завязаны на
`openspec/changes/<id>/specs/*/spec.md` и на `openspec validate --strict`). `openspec/changes/<id>/specs/*/spec.md` и на `openspec validate --strict`).
**Проект без OpenSpec этим пайплайном не ведётся** — подключай OpenSpec, а не **Проект без OpenSpec этим пайплайном не ведётся** — подключай OpenSpec, а не
вырождай цикл: ветка деградации здесь не пишется, потому что непроверенная вырождай цикл: ветка деградации здесь не пишется, потому что непроверенная
@@ -47,14 +48,14 @@ description: Автономно проводит одну задачу чере
проекте есть свой процесс управления задачами — он и решает, что брать. проекте есть свой процесс управления задачами — он и решает, что брать.
- **Форматом задач.** Пайплайн **не правит индексы руками и не выдумывает путь - **Форматом задач.** Пайплайн **не правит индексы руками и не выдумывает путь
к скрипту учёта**: он зовёт Skill `av-dev-pm:tasks`, который этим владеет к скрипту учёта**: он зовёт Skill `av-dev-pm:tasks`, который этим владеет
(шаг 11). Закрытие как таковое — его работа, и это осознанное решение с (шаг 12). Закрытие как таковое — его работа, и это осознанное решение с
названной ценой: **приёмщик и исполнитель совпали**. Закрытие поэтому **не названной ценой: **приёмщик и исполнитель совпали**. Закрытие поэтому **не
окончательно** — человек на сессии возвращает задачу `reopen` с причиной, а окончательно** — человек на сессии возвращает задачу `reopen` с причиной, а
доклад по критериям приёмки становится единственным, по чему приёмка вообще доклад по критериям приёмки становится единственным, по чему приёмка вообще
возможна. Плагина `av-dev-pm` в проекте нет — вызов не разрешится, и тогда возможна. Плагина `av-dev-pm` в проекте нет — вызов не разрешится, и тогда
учёт остаётся владельцу, о чём говорится в докладе. учёт остаётся владельцу, о чём говорится в докладе.
- **Заведением задач из урожая ревью.** Отложенные находки отдаются **списком** - **Заведением задач из урожая ревью.** Отложенные находки отдаются **списком**
(см. шаг 7); превращать их в задачи — работа того, кто ведёт задачи проекта. (см. шаг 8); превращать их в задачи — работа того, кто ведёт задачи проекта.
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос - **Определением ценности.** «Нужна ли эта функциональность» — не вопрос
пайплайна ни на одном шаге. пайплайна ни на одном шаге.
@@ -148,7 +149,7 @@ description: Автономно проводит одну задачу чере
## Шаги ## Шаги
Одиннадцать шагов с одной развилкой и одним досрочным исходом: Двенадцать шагов с одной развилкой и одним досрочным исходом:
```mermaid ```mermaid
flowchart TD flowchart TD
@@ -157,26 +158,34 @@ flowchart TD
big["исход «оказалась крупнее задачи»<br/>объявляется ДО заведения change"] big["исход «оказалась крупнее задачи»<br/>объявляется ДО заведения change"]
s2["2. opsx:explore — груминг идеи"] s2["2. opsx:explore — груминг идеи"]
s3["3. opsx:propose — change, дельта-спеки, tasks.md"] s3["3. opsx:propose — change, дельта-спеки, tasks.md"]
s4["4. ревью предложения, профиль design"] s4["4. разметка задачи — review-scope:<br/>размер, сложность, метка, план тем"]
s5["5. отработать замечания + validate --strict"] s5["5. ревью дизайна, состав по метке"]
s6["6. opsx:apply — код, гейт, поведенческая верификация"] s6["6. отработать замечания + validate --strict"]
s7["7. ревью кода, ступень выбирает разметчик"] s7["7. opsx:apply — код, гейт, поведенческая верификация"]
s8["8. opsx:archive"] s8["8. ревью кода, состав по той же метки"]
s9["9. синк документации — av-dev-pm:docs"] s9["9. opsx:archive"]
s10["10. коммит работы — av-dev-git:commit"] s10["10. синк документации — av-dev-pm:docs"]
s11["11. закрыть задачу — av-dev-pm:tasks,<br/>вторым коммитом учёта"] s11["11. коммит работы — av-dev-git:commit"]
s12["12. закрыть задачу — av-dev-pm:tasks,<br/>вторым коммитом учёта"]
s1 --> triv s1 --> triv
s1 -.-> big s1 -.-> big
triv -->|"нет: идея или мутная постановка"| s2 triv -->|"нет: идея или мутная постановка"| s2
s2 --> s3 s2 --> s3
triv -->|"да: шаги 2 и 4 пропускаются"| s3 triv -->|"да: шаг 2 пропускается"| s3
s3 --> s4 --> s5 --> s6 --> s7 --> s8 --> s9 --> s10 --> s11 s3 --> s4 --> s5 --> s6 --> s7 --> s8 --> s9 --> s10 --> s11 --> s12
s4 -.->|"план задачи: та же метка"| s8
``` ```
Два чекпоинта ревью — шаги 4 и 7 — единственные места, где зовётся конвейер; **Разметка стоит одна и обслуживает обе стадии ревью** — шаги 5 и 8. Это и есть
пунктирное ребро на схеме: план, посчитанный на шаге 4, доезжает до ревью кода
без пересчёта. Раньше разметка была первым проходом внутри шага ревью кода, а
состав ревью дизайна называл сам пайплайн — то есть одна и та же величина
считалась дважды, и один из двух раз тем, кто только что написал предложение.
Два чекпоинта ревью — шаги 5 и 8 — единственные места, где зовётся конвейер;
порядок «сперва коммит работы, потом коммит учёта» на схеме тоже ребро, и оно порядок «сперва коммит работы, потом коммит учёта» на схеме тоже ребро, и оно
обязательное (шаг 11). обязательное (шаг 12).
Схема — **сводка**: содержание каждого шага в его разделе ниже, и при Схема — **сводка**: содержание каждого шага в его разделе ниже, и при
расхождении прав текст. расхождении прав текст.
@@ -191,13 +200,20 @@ flowchart TD
уезжают в `tasks.md` change. Файл задачи может быть удалён до коммита, а уезжают в `tasks.md` change. Файл задачи может быть удалён до коммита, а
критерии обязаны его пережить. критерии обязаны его пережить.
Оцени тривиальность (влияет на шаг 4): Оцени тривиальность **теперь она влияет ровно на один шаг, второй**:
- **тривиальная** — локальная правка без изменения поведения, спек и схемы, - **тривиальная** — локальная правка без изменения поведения, спек и схемы,
решение очевидно. Explore и ревью спек пропускаются; решение очевидно. Explore пропускается;
- **нетривиальная** — новое или изменённое поведение, дизайн-развилки, задеты - **нетривиальная** — новое или изменённое поведение, дизайн-развилки, задеты
инварианты, схема или несколько capability. Полный цикл. инварианты, схема или несколько capability. Полный цикл.
**На состав ревью тривиальность больше не влияет** — это работа шага 4. Раньше
она решала и то, звать ли ревью предложения вовсе; теперь глубину обеих стадий
называет метку, и тривиальная задача просто получает `small`. Разница
существенная: «пропустить ревью дизайна» и «пройти его одним самым дешёвым
проходом» — не одно и то же, а сверка дельта-спек стоит меньше, чем разбор того,
что она поймала бы.
Здесь же — проверка на «крупнее задачи»: если видно, что одним заходом это не Здесь же — проверка на «крупнее задачи»: если видно, что одним заходом это не
мерджится, объявляй исход **до** заведения change. мерджится, объявляй исход **до** заведения change.
@@ -216,31 +232,67 @@ flowchart TD
Критерии приёмки задачи, если они были, копируются в `tasks.md` отдельным блоком. Критерии приёмки задачи, если они были, копируются в `tasks.md` отдельным блоком.
### 4. (Нетривиальная) Ревью предложения — профиль `design`, ДО кода ### 4. Разметка задачи — агент `review-scope`
Первый чекпоинт. Вызови Skill **`av-dev-pipeline:review-pipeline`** с профилем **Один запуск на всю задачу, и он обслуживает оба чекпоинта ревью.** Запусти
`design` и ссылкой на change `<id>`. агента `review-scope`, дав ему корень проекта, идентификатор change, базу диффа и
запись задачи. Кода на этот момент нет, и это условие его работы, а не помеха.
**Состав чекпоинта решает конвейер, а не ты**: `review-specs` в режиме «дизайн ДО Он возвращает **план задачи**:
кода» идёт всегда, а `review-rubric` и `review-architecture` — только когда
изменение крупное или незнакомое (то же условие, что у ступени `wide`, и та же
доля — 5–10% задач). Причина в том, что чекпоинт стоит на **каждой** задаче: при
мелкой нарезке три прохода здесь умножаются на число задач и становятся самой
большой статьёй конвейера.
Смысл профиля: архитектурная находка на готовом коде стоит переписывания и - **размер** (малое / среднее / крупное) и **сложность** (знакомое /
незнакомое), каждое с обоснованием по факту;
- **метка** как максимум по двум осям: `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` как приёмочные `review-rubric` запускался, перенеси его рубрику в `tasks.md` как приёмочные
критерии; там же уже лежат критерии от постановки, если они были. критерии; там же уже лежат критерии от постановки, если они были.
### 5. Отработать замечания ревью предложения ### 6. Отработать замечания ревью предложения
- Мелочь и явные улучшения — правь сам в спеках и дизайне. - Мелочь и явные улучшения — правь сам в спеках и дизайне.
- Развилки (компромисс, scope, инвариант) — вопросом в запись, спеки урезаются на - Развилки (компромисс, scope, инвариант) — вопросом в запись, спеки урезаются на
остаток. остаток.
- После правок перепрогони `openspec validate --strict <id>`. - После правок перепрогони `openspec validate --strict <id>`.
### 6. Написать код — `opsx:apply` ### 7. Написать код — `opsx:apply`
Вызови Skill `opsx:apply` для реализации `tasks.md`. Код — по конвенциям проекта Вызови Skill `opsx:apply` для реализации `tasks.md`. Код — по конвенциям проекта
(каталог `docs/conventions/`). Меняешь схему — обнови её описание в (каталог `docs/conventions/`). Меняешь схему — обнови её описание в
@@ -256,21 +308,31 @@ flowchart TD
**Сервис не оставляем лежать.** Если запуск упал — почини или откати до конца **Сервис не оставляем лежать.** Если запуск упал — почини или откати до конца
шага. шага.
### 7. Ревью кода — Skill `av-dev-pipeline:review-pipeline` ### 8. Ревью кода — Skill `av-dev-pipeline:review-pipeline`
Второй чекпоинт. Вызови Skill **`av-dev-pipeline:review-pipeline`**, дав ссылку Второй чекпоинт. Вызови Skill **`av-dev-pipeline:review-pipeline`**, дав ссылку
на change `<id>`, базу диффа **и режим запуска**. на change `<id>`, базу диффа, **план разметки с шага 4** и режим запуска.
**Профиль ты не передаёшь, и это правило, а не упрощение.** Ступень выбирает **Метка ты не выбираешь, и это правило, а не упрощение.** Её назвал
разметчик конвейера (`review-scope`, стадия 0) — по объёму и незнакомости `review-scope` ещё на шаге 4 — по размеру и сложности, с обоснованием по каждой
изменения, с обоснованием строкой. Причина в разведённости: ты только что написал оси. Причина в разведённости: ты только что написал этот код, и решать, насколько
этот код, и решать, насколько глубоко его проверять, тебе нельзя — под давлением глубоко его проверять, тебе нельзя — под давлением «я почти закончил» решение
«я почти закончил» решение известно заранее. Правило выбора живёт в скилле известно заранее. Правило выбора живёт в скилле конвейера, проектные триггеры — в
конвейера, проектные триггеры — в `docs/review.*`. `docs/review.*`, подраздел «Триггеры метки».
**Считаешь ступень заниженной — скажи это в докладе строкой, а не переспорь.** **Метка не пересматривается по факту диффа.** Дифф может выйти крупнее, чем
ожидалось при разметке, — это не повод её поднимать: пересмотр означал бы второй
запуск разметчика, ровно то, ради устранения чего он и переехал на шаг 4.
**Считаешь метку заниженной — скажи это в докладе строкой, а не переспорь.**
Разметчик вправе и поднять, и понизить; твоё несогласие это факт для человека, а Разметчик вправе и поднять, и понизить; твоё несогласие это факт для человека, а
не команда конвейеру. не команда конвейеру. Место, где такое несогласие превращается в изменение
правил, — журнал дефектов `docs/review.md`, и только постфактум.
**Плана нет — ревью кода не запускается.** Триаж требует план обязательным
входом: без него он не может сверить, все ли размеченные темы вернули отчёт, а
эта сверка — единственная защита от молчащего пропуска. Потерял план (прервалась
сессия, ушёл контекст) — повтори шаг 4, а не гони прогон без него.
**Режим по умолчанию — `по графу`, и обосновывать его не надо.** Конвейер сам **Режим по умолчанию — `по графу`, и обосновывать его не надо.** Конвейер сам
знает свои рёбра: гейт открывает опиниативные проходы, проходы с пометкой «держит знает свои рёбра: гейт открывает опиниативные проходы, проходы с пометкой «держит
@@ -289,10 +351,10 @@ flowchart TD
разметчика — таблицей «тема → дом → глубина → кто закрывает», — и против каждой разметчика — таблицей «тема → дом → глубина → кто закрывает», — и против каждой
темы обязан стоять исход. Тема без отчёта и тема без дома — разные вещи, и обе темы обязан стоять исход. Тема без отчёта и тема без дома — разные вещи, и обе
должны быть названы. Реестр короткий (шесть тем ядра плюс свои) — сверка стоит должны быть названы. Реестр короткий (шесть тем ядра плюс свои) — сверка стоит
одного взгляда. Почему это правило существует, объясняет раздел «Профили» скилла одного взгляда. Почему это правило существует, объясняет раздел «Метки» скилла
конвейера; здесь — само требование. конвейера; здесь — само требование.
Отработай так же, как шаг 5: помеченное `инлайн` чини сам и не логируй, Отработай так же, как шаг 6: помеченное `инлайн` чини сам и не логируй,
`развилка` — вопросом в запись (он уже сформулирован триажем, его остаётся `развилка` — вопросом в запись (он уже сформулирован триажем, его остаётся
перенести). После правок — снова гейт. перенести). После правок — снова гейт.
@@ -306,19 +368,19 @@ flowchart TD
сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно», сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно»,
превращается в ложное ощущение проверенности. превращается в ложное ощущение проверенности.
**Отчёт триажа сохрани вместе с change (`openspec/changes/<id>/review/`; шаг 8 **Отчёт триажа сохрани вместе с change (`openspec/changes/<id>/review/`; шаг 9
унесёт его в `openspec/changes/archive/<id>/review/` вместе с change) — это унесёт его в `openspec/changes/archive/<id>/review/` вместе с change) — это
обязательно, а не «если удобно».** По нему потом видно, что было найдено и что из обязательно, а не «если удобно».** По нему потом видно, что было найдено и что из
этого осталось в урожае. И это единственный **независимый** артефакт о составе этого осталось в урожае. И это единственный **независимый** артефакт о составе
прогона: под оркестратором `task-batch` именно по нему сверяют полноту ревью прогона: под оркестратором `task-batch` именно по нему сверяют полноту ревью
ветки, а не по твоей прозе — она написана тем же, кто мог проход и пропустить. ветки, а не по твоей прозе — она написана тем же, кто мог проход и пропустить.
### 8. Архивировать — `opsx:archive` ### 9. Архивировать — `opsx:archive`
Вызови Skill `opsx:archive`: change уезжает в архив, дельты вливаются в Вызови Skill `opsx:archive`: change уезжает в архив, дельты вливаются в
актуальные спеки. актуальные спеки.
### 9. Синк документации ### 10. Синк документации
Ревью выполненного — до этого шага. Затем **вызови Skill `av-dev-pm:docs`**: он Ревью выполненного — до этого шага. Затем **вызови Skill `av-dev-pm:docs`**: он
владеет содержимым документов канона и ведёт чек-лист синка. Плагина нет — шаг владеет содержимым документов канона и ведёт чек-лист синка. Плагина нет — шаг
@@ -343,7 +405,7 @@ flowchart TD
сделан по перечню документов, без списка триггеров — плагина `av-dev-pm` нет». сделан по перечню документов, без списка триггеров — плагина `av-dev-pm` нет».
Канона в проекте тоже нет — назови это исходом и предложи `av-dev-pm:canon`. Канона в проекте тоже нет — назови это исходом и предложи `av-dev-pm:canon`.
### 10. Коммит ### 11. Коммит
Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не
создавай и не переключай, ничего не пушь. При ручном запуске HEAD обычно на создавай и не переключай, ничего не пушь. При ручном запуске HEAD обычно на
@@ -354,7 +416,7 @@ flowchart TD
сделано», тело списком 1–3 пункта, без трейлеров). Одна задача — один осмысленный сделано», тело списком 1–3 пункта, без трейлеров). Одна задача — один осмысленный
коммит. коммит.
### 11. Закрыть задачу — **после коммита, не раньше** ### 12. Закрыть задачу — **после коммита, не раньше**
**Вызови Skill `av-dev-pm:tasks`** и попроси закрыть задачу как реализованную — **Вызови Skill `av-dev-pm:tasks`** и попроси закрыть задачу как реализованную —
он владеет форматом и двигает строку из набора спринта сам. Путь к его скрипту не он владеет форматом и двигает строку из набора спринта сам. Путь к его скрипту не
@@ -362,7 +424,7 @@ flowchart TD
путь. путь.
**Порядок обязателен.** Закрытие удаляет файл задачи; сделанное до коммита оно **Порядок обязателен.** Закрытие удаляет файл задачи; сделанное до коммита оно
оставило бы задачу закрытой без единого следа работы, если шаг 10 упадёт. оставило бы задачу закрытой без единого следа работы, если шаг 11 упадёт.
**Закрытие тоже коммитится — вторым коммитом, тут же.** Удаление файла задачи и **Закрытие тоже коммитится — вторым коммитом, тут же.** Удаление файла задачи и
правка индексов (их имена знает `av-dev-pm`, не ты) — это правки в рабочем правка индексов (их имена знает `av-dev-pm`, не ты) — это правки в рабочем
@@ -390,7 +452,7 @@ flowchart TD
это доклад приёмщику, а не отметка «принято»; это доклад приёмщику, а не отметка «принято»;
- **`Урожай`** — отложенные находки списком (формулировка, оракул, провенанс). - **`Урожай`** — отложенные находки списком (формулировка, оракул, провенанс).
Задачи из него заводит тот, кто ведёт задачи проекта; Задачи из него заводит тот, кто ведёт задачи проекта;
- **одна строка границ покрытия**: какая ступень и режим гонялись, какие проходы - **одна строка границ покрытия**: какая метка и режим гонялись, какие проходы
не запускались и что проверить было невозможно. Доклад без неё сообщает не запускались и что проверить было невозможно. Доклад без неё сообщает
«проверено», не сообщая, что именно. «проверено», не сообщая, что именно.
@@ -400,15 +462,16 @@ flowchart TD
текущем worktree и на текущей ветке: не делай `git checkout`/`switch`, не текущем worktree и на текущей ветке: не делай `git checkout`/`switch`, не
создавай веток, не пушь. создавай веток, не пушь.
- Не пропускай `openspec validate --strict` перед архивацией. - Не пропускай `openspec validate --strict` перед архивацией.
- Тривиальная задача: шаги 2 и 4 пропускаются; ревью кода (шаг 7) остаётся - Тривиальная задача: пропускается только шаг 2. Обе стадии ревью остаются, но
всегда, но в профиле `quick`. с меткой `small` — один проход на дизайне и четыре на коде, а при своих темах
проекта пять: приёмник тем запускается, если ему есть что принимать.
- Гейт блокирует: пока он красный, опиниативные проходы не запускаются. Чинить и - Гейт блокирует: пока он красный, опиниативные проходы не запускаются. Чинить и
перезапускать, а не «посмотреть заодно». перезапускать, а не «посмотреть заодно».
- Если ревью предлагает крупную переработку — это развилка: не правь молча и не - Если ревью предлагает крупную переработку — это развилка: не правь молча и не
спрашивай, запиши вопросом и доведи остаток. спрашивай, запиши вопросом и доведи остаток.
- Держи вызывающего в цикле короткими репликами на переходах фаз, но не проси - Держи вызывающего в цикле короткими репликами на переходах фаз, но не проси
подтверждать механику. подтверждать механику.
- **Занизить ступень ревью или пропустить тему — самый дешёвый способ - **Занизить метка ревью или пропустить тему — самый дешёвый способ
«ускориться», и он же самый дорогой по последствиям.** Защита устроена так, «ускориться», и он же самый дорогой по последствиям.** Защита устроена так,
что регулятора у тебя нет: ступень выбирает разметчик, план сверяется по темам, что регулятора у тебя нет: метку выбирает разметчик **до того**, как ты
непокрытое называется в отчёте строкой. написал код, план сверяется по темам, непокрытое называется в отчёте строкой.
+1 -1
View File
@@ -103,7 +103,7 @@ color: green
| Термин | Что называет | | Термин | Что называет |
| --- | --- | | --- | --- |
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции | | интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
| триаж | ступень конвейера, сводящая находки в решение | | триаж | стадия конвейера, сводящая находки в решение |
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство | | провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
| дедуп, дедупликация | сверка нового против уже лежащего | | дедуп, дедупликация | сверка нового против уже лежащего |
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки | | чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
+1 -1
View File
@@ -100,7 +100,7 @@ color: green
одной партии»), — находка: слово стоит, проверки нет. Число критериев считает одной партии»), — находка: слово стоит, проверки нет. Число критериев считает
`tasks.py check`, тебе оно неинтересно. `tasks.py check`, тебе оно неинтересно.
6. **Предписания процесса в теле нет.** «Делать профилем standard», «взять 6. **Предписания процесса в теле нет.** «Делать с меткой medium», «взять
такой-то агент» — это выбор, который делают, увидев изменение, а не при такой-то агент» — это выбор, который делают, увидев изменение, а не при
постановке. Он же путь понизить требования решением, принятым до постановке. Он же путь понизить требования решением, принятым до
проектирования. проектирования.
+1 -1
View File
@@ -199,7 +199,7 @@ capability), `openspec/config.yaml`.
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.pm.json` с **Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.pm.json` с
версией скрипта — и только его. Применена ли запись журнала **по существу**, он версией скрипта — и только его. Применена ли запись журнала **по существу**, он
не знает: проект несёт `"canon": 4` и может не иметь того, чего требовала любая не знает: проект несёт `"canon": 6` и может не иметь того, чего требовала любая
из пройденных версий. Записи применяются руками (переименовать секцию, проставить из пройденных версий. Записи применяются руками (переименовать секцию, проставить
типы, дописать раздел каждому `fix`), а ручной проход по нескольким записям типы, дописать раздел каждому `fix`), а ручной проход по нескольким записям
подряд — ровно то место, где половина шага делается и забывается. Судьи и есть подряд — ровно то место, где половина шага делается и забывается. Судьи и есть
+107 -41
View File
@@ -1,6 +1,6 @@
# Канон документов проекта # Канон документов проекта
**Версия 5.** **Версия 6.**
Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs` Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs`
читают его, а не пересказывают: три описания одной раскладки разъедутся, и читают его, а не пересказывают: три описания одной раскладки разъедутся, и
@@ -47,10 +47,10 @@
## Раскладка ## Раскладка
**Документ канона — это тема ревью, а тема живёт файлом или каталогом.** **Документ канона живёт файлом или каталогом.** `docs/security.md` и
`docs/security.md` и `docs/security/` — одно и то же; форму выбирает проект по `docs/security/` — одно и то же; форму выбирает проект по объёму написанного, и
объёму написанного, и переход между формами не меняет ни канон, ни версию. Обе переход между формами не меняет ни канон, ни версию. Обе формы сразу — ошибка:
формы сразу — ошибка: два дома для одного факта расходятся молча. два дома для одного факта расходятся молча.
``` ```
CLAUDE.md памятка агенту: что это, стек, инварианты с CLAUDE.md памятка агенту: что это, стек, инварианты с
@@ -75,21 +75,76 @@ openspec/
changes/archive/ архив изменений с design.md — сырьё для ADR changes/archive/ архив изменений с design.md — сырьё для ADR
``` ```
**У темы-каталога обязателен `README.md`** — вход, по которому её читают агенты. **У документа-каталога обязателен `README.md`** — вход, по которому его читают агенты.
`adr/` в форме каталога держит ещё и `template.md`, а записи именуются `adr/` в форме каталога держит ещё и `template.md`, а записи именуются
`ADR-ГГГГ-ММ-ДД-slug.md`. `ADR-ГГГГ-ММ-ДД-slug.md`.
**Список тем открытый, и это не послабление, а механизм.** Всё, что проект ## Три категории документов
кладёт в `docs/`, становится темой ревью: у конвейера есть приёмник для темы, к
которой нет именной оптики, и заведён он ровно за этим. Завёл Раньше здесь стояло плоское правило «каждый документ `docs/` — тема ревью». Оно
`docs/accessibility.md` — появилась тема `accessibility`, и она попадает в план неверно ровно наполовину: паспорт и схема хранилища ревью нужны, но темами не
каждого прогона. Не темы ровно две: `docs/tasks/` (его ведёт скилл `tasks`) являются, а журнал решений и журнал наблюдений ревью изменения не нужны вовсе.
и `docs/review.*` — это настройка самого конвейера, слой над темами. Плоское правило заставляло разметчика либо плодить фантомные темы, либо терять
документы молча — а молчащая потеря и есть то, против чего канон написан.
**Разрез один и проверяемый: можно ли по документу сказать «в этом изменении
сделано не так»?**
| Категория | Ответ на разрез | Что с ней делает ревью |
| --- | --- | --- |
| **тема** | да, прямо | заводит направление проверки и требует исполнителя |
| **источник темы** | нет, но он задаёт границу, по которой судит чужая тема | читается как материал, своей темы не порождает |
| **процессный документ** | нет: он про то, как мы работаем, а не про изменение | не судит по нему изменение |
| Документ | Категория | Куда питает |
| --- | --- | --- |
| `conventions.*` | тема | `conventions` |
| `security.*` | тема | `security` |
| `architecture.*` | тема | `architecture`; раздел эксплуатации — `operations` |
| *свой документ проекта* | тема | своя тема, именем документа |
| `passport.*` | источник | `architecture` — граница домена, «чем **не** является» |
| `database.*` | источник | `operations` — схема и настройки с числами |
| `CLAUDE.md`, `AGENTS.md` | источник | `autotests` (семантика гейта); инварианты — сквозные |
| `openspec/specs/` | источник | `requirements` |
| `tasks/` | процессный | — |
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
| `adr.*` | процессный | — |
| `research.*` | процессный | — |
| `.pm.json` | процессный | — (служебный файл, не документ) |
**Список тем открытый, и это не послабление, а механизм.** Категории
`источник` и `процессный` **закрыты** — они перечислены здесь поимённо и
проектом не пополняются. Всё остальное, что проект кладёт в `docs/`, — тема: у
конвейера есть приёмник для темы, к которой нет именной оптики, и заведён он
ровно за этим. Завёл `docs/accessibility.md` — появилась тема `accessibility`, и
она попадает в план каждого прогона.
Отсюда следствие, ради которого правило и заведено: **`docs/` — это конфигурация Отсюда следствие, ради которого правило и заведено: **`docs/` — это конфигурация
ревью.** Проект настраивает проверку тем, что пишет о себе, а не отдельным файлом ревью.** Проект настраивает проверку тем, что пишет о себе, а не отдельным файлом
настроек, который разошёлся бы с документами. настроек, который разошёлся бы с документами.
**«Не судит по нему» и «не открывает» — не одно и то же, и разница существенна.**
`docs/review.*` проходы читают на каждом прогоне: там лежат вопросы по темам,
журнал дефектов, типовые узлы и типовые ложноположительные. Это чтение конвейером
**собственной настройки**, а не суждение об изменении, и потому оно законно.
`adr/`, `research/` и `tasks/` не открывает никто: по ним изменение не судят, и
настройкой конвейера они не являются.
**Процессный документ — не документ второго сорта.** `adr/` и `research/`
проверяются наравне с остальными, но **сверкой документации**, а не прогоном
ревью: ADR без ссылки на архивный `design.md`, замена без парного статуса, число
без провенанса — это работа агентов `doc-consistency` и `doc-code-drift`, и она
осталась там же, где была. Изменилось одно: прогон ревью не открывает их как
критерий и не судит по ним изменение.
Цена этого решения записана, а не подразумевается: **расхождение изменения с
записанным решением прогоном больше не ловится.** Раньше архитектурный проход
читал `adr/` и мог сказать «здесь отменено решение ADR-2026-03-11, а парного
статуса нет»; теперь это скажет только `doc-consistency` на сессии между
спринтами. Сделка сознательная — ADR объясняет прошлое, а не предъявляет
требование к изменению, и чтение всего каталога решений на каждой задаче
оплачивалось на каждой, а срабатывало на единицах.
### Имена файлов английские, текст русский ### Имена файлов английские, текст русский
**Текст документов русский; имена файлов, capability и задач — английские, **Текст документов русский; имена файлов, capability и задач — английские,
@@ -116,30 +171,35 @@ kebab-case.** Причина не эстетическая: имя файла с
## Роли документов и темы ревью ## Роли документов и темы ревью
Одна строка на каждый — на какой вопрос он отвечает и какую тему ревью питает. Одна строка на каждый — на какой вопрос он отвечает, в какой он категории и
**Кто именно закрывает тему, здесь не указано намеренно**: это зависит от ступени какую тему питает. **Кто именно закрывает тему, здесь не указано намеренно**: это
прогона и меняется вместе с конвейером, а документ живёт дольше. Раскладку зависит от метки прогона и меняется вместе с конвейером, а документ живёт
«тема → проход → глубина» держит скилл `av-dev-pipeline:review-pipeline`. дольше. Раскладку «тема → проход → глубина» держит скилл
`av-dev-pipeline:review-pipeline`.
**Общего словаря у канона с конвейером ровно два вида имён: имена тем и имена **Общего словаря у канона с конвейером ровно три вида имён: имена категорий,
ступеней.** Ими проект и настраивает ревью — вопросами по темам и триггерами имена тем и имена меток.** Категорий три — `тема`, `источник`, `процессный`;
профиля. **Имён проходов канон не называет нигде**, включая вывод `docs.py`: **меток тоже три, и они закрыты: `small`, `medium`, `large`.** Метка это итог
проход переименовывается и переезжает между ступенями, и канон, назвавший его, в классификации задачи, и по ней конвейер выбирает исполнителей на обеих стадиях
ревью; проект её не выдумывает, а только уточняет триггеры. Ими проект и
настраивает ревью — вопросами по темам и триггерами метки. **Имён проходов канон не называет нигде**, включая вывод `docs.py`:
проход переименовывается и переезжает между метками, и канон, назвавший его, в
этот день соврёт молча. Обратное направление законно — конвейер называет этот день соврёт молча. Обратное направление законно — конвейер называет
документы канона поимённо, потому что он их читатель. документы канона поимённо, потому что он их читатель.
| Документ | Вопрос | Тема ревью | | Документ | Вопрос | Категория и тема |
| --- | --- | --- | | --- | --- | --- |
| `CLAUDE.md`, `AGENTS.md` | что нельзя нарушать, чем краснеет гейт | `autotests`; инварианты — сквозные, во все темы | | `CLAUDE.md`, `AGENTS.md` | что нельзя нарушать, чем краснеет гейт | источник: `autotests`; инварианты — сквозные, во все темы |
| `passport.*` | зачем и для кого, чем это **не** является | `architecture` | | `passport.*` | зачем и для кого, чем это **не** является | источник: `architecture` |
| `architecture.*` | как сложено и где что работает | `architecture`; раздел эксплуатации — `operations` | | `architecture.*` | как сложено и где что работает | тема `architecture`; раздел эксплуатации — `operations` |
| `database.*` | что лежит в хранилище и какими настройками | `operations` | | `database.*` | что лежит в хранилище и какими настройками | источник: `operations` |
| `security.*` | против кого защищаемся и что вне модели | `security` | | `security.*` | против кого защищаемся и что вне модели | тема `security` |
| `conventions.*` | как мы пишем код | `conventions` | | `conventions.*` | как мы пишем код | тема `conventions` |
| `research/` | что показала реальность, а не документация | `operations`, `requirements` | | `openspec/specs/` | что система делает — нормативно | источник: `requirements` |
| `adr.*` | почему решено именно так | `architecture` | | `research.*` | что показала реальность, а не документация | процессный |
| `openspec/specs/` | что система делает — нормативно | `requirements` | | `adr.*` | почему решено именно так | процессный |
| `review.*` | как настроен конвейер и что уже проскакивало | **не тема**: слой над всеми | | `review.*` | как настроен конвейер и что уже проскакивало | процессный: слой **над** темами |
| `tasks/` | что делаем и в каком порядке | процессный |
| *свой документ проекта* | что проект счёл нужным проверять | **своя тема**, именем документа | | *свой документ проекта* | что проект счёл нужным проверять | **своя тема**, именем документа |
### `passport.md` ### `passport.md`
@@ -256,16 +316,22 @@ kebab-case.** Причина не эстетическая: имя файла с
- **Типовые ложноположительные** — находки, которые здесь выглядят убедительно и - **Типовые ложноположительные** — находки, которые здесь выглядят убедительно и
всегда неверны, каждая со строкой «почему здесь это не дефект»; всегда неверны, каждая со строкой «почему здесь это не дефект»;
- **Вопросы по темам** — в форме `<тема>: <вопрос> (<провенанс>)`. **Не по именам - **Вопросы по темам** — в форме `<тема>: <вопрос> (<провенанс>)`. **Не по именам
проходов**: проход уезжает между ступенями, а тема остаётся, и вопрос, проходов**: проход уезжает между метками, а тема остаётся, и вопрос,
адресованный проходу, перестал бы задаваться молча в тот день, когда тот уехал адресованный проходу, перестал бы задаваться молча в тот день, когда тот уехал
в верхнюю ступень. Задаёт вопрос тот, кто закрывает тему на этом прогоне; в старшую метку. Задаёт вопрос тот, кто закрывает тему на этом прогоне.
- **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью: Адресовать можно только теме: `passport`, `database`, `adr`, `research` и
что в этом проекте считается **крупным или незнакомым** изменением (поднимает `review` — не темы, и вопрос, адресованный им, не задаст никто;
прогон до `wide`, верхней ступени, — и она рассчитана на 5–10% задач) и что - **Триггеры метки** — проектная конкретизация правила выбора метки ревью,
считается **мелким** (опускает до `quick`). Перечнем мест, узлами или **тремя списками**. Два поднимают, по одному на ось: что в этом проекте считается
capability, а не вторым определением класса. Уточняет умолчания, а не отменяет **крупным** (объём: сколько узлов и слоёв трогает) и что считается
их. Рабочее умолчание — `standard`: миграция схемы и публичный контракт ступень **незнакомым** (форма решения: известна до начала или нащупывается по ходу).
**не** поднимают, их проверяют проходы, которые в `standard` и так есть; Любая из двух осей поднимает прогон до `large`, старшей метки, — а она
рассчитана на 5–10% задач. Третий список — что считается **мелким** (опускает
до `small`); он один, потому что вниз метку опускает только совпадение обеих
осей сразу. Перечнем мест, узлами или capability, а не вторым определением
класса. Уточняет умолчания, а не отменяет их. Рабочее умолчание — `medium`:
миграция схемы и публичный контракт метку **не** поднимают, их проверяют
проходы, которые в `medium` и так есть;
- **Недоступно проверке** — два подраздела, оба **по темам**: «не проверит ни - **Недоступно проверке** — два подраздела, оба **по темам**: «не проверит ни
один проход» (принципиальная граница, по факту промаха не пересматривается) и один проход» (принципиальная граница, по факту промаха не пересматривается) и
«перестали проверять сознательно» (пересматривается первым). Тема, у которой в «перестали проверять сознательно» (пересматривается первым). Тема, у которой в
@@ -441,7 +507,7 @@ kebab-case.** Причина не эстетическая: имя файла с
```json ```json
{ {
"canon": 4, "canon": 6,
"migrations": "internal/store/migrations", "migrations": "internal/store/migrations",
"tasks": { "tasks": {
"backlog": "INDEX.md" "backlog": "INDEX.md"
@@ -13,6 +13,92 @@ upgrade` идёт по записям снизу вверх от версии п
--- ---
## Версия 6 — 2026-08-07
Версия 5 объявила: **каждый документ `docs/` — тема ревью**. Правило оказалось
верным ровно наполовину и потому вредным целиком. Паспорт и схему хранилища
ревью читает, но темами они не являются — они задают границу, по которой судит
чужая тема. Журнал решений и журнал наблюдений ревью изменения не нужны вовсе:
ADR объясняет прошлое решение, а не предъявляет требование к изменению.
Разметчик, применявший плоское правило буквально, обязан был либо завести
фантомные темы `passport`, `adr`, `database`, `research` и продублировать ими
работу тем `architecture` и `operations`, либо потерять четыре документа молча —
а молчащая потеря и есть то, против чего канон написан.
**Что изменилось:**
1. **Три категории документов вместо одной.** Разрез проверяемый: можно ли по
документу сказать «в этом изменении сделано не так»? **Тема** — да, прямо
(`conventions`, `security`, `architecture`, свои документы проекта).
**Источник темы** — нет, но он задаёт границу для чужой темы (`passport.*`
`architecture`, `database.*``operations`, `CLAUDE.md``autotests`,
`openspec/specs/``requirements`). **Процессный документ** — нет, он про то,
как мы работаем (`tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json`).
2. **Категории `источник` и `процессный` закрыты, категория `тема` открыта.**
Прежде открытым был весь список, и «не темы ровно две» противоречило
собственной раскладке канона. Теперь пополняется только одно множество, и
документ, которого нет в раскладке, — однозначно своя тема проекта.
3. **`adr/` и `research/` уходят из входа ревью изменения.** Прогон их больше не
открывает. Проверяться они не перестали: ADR без ссылки на архивный
`design.md`, замена без парного статуса, число без провенанса — это по-прежнему
работа `doc-consistency` и `doc-code-drift`, на сессии между спринтами.
4. **`docs.py` печатает категорию в отказе.** «Нет источника passport» читается
иначе, чем «нет темы security». Обязательность при этом не изменилась:
заводятся все документы одинаково и с первого дня.
5. **У задачи появилась метка — `small`, `medium` или `large`.** Это итог
классификации и **единственный вход, по которому конвейер выбирает
исполнителей** на обеих стадиях ревью. Прежние имена `quick`, `standard` и
`wide` описывали глубину прогона, то есть свойство ревью; метка описывает
**задачу** — а выбирают по ней одно и то же. Слово «ступень» уходит:
у одной вещи одно имя.
6. **Метка выводится из двух осей и не равна ни одной из них.** Размер (малое,
среднее, крупное) и сложность (знакомое, незнакомое); метка — максимум по
ним. Малое **незнакомое** изменение получает `large`, трогая один узел, —
поэтому размер и метка пишутся отдельными строками, и выводить одно из
другого нельзя.
**Цена, записанная явно:** расхождение изменения с записанным решением прогоном
больше не ловится. Раньше архитектурный проход мог сказать «здесь отменено
решение ADR-2026-03-11, парного статуса нет»; теперь это скажет только сверка
документации. Сделка сознательная: чтение всего каталога решений оплачивалось на
каждой задаче, а срабатывало на единицах.
**Что переехало:** ничего в раскладке. Ни один файл не переименовывается и не
перемещается.
**Что сделать проекту:**
1. `docs/review.*`, подраздел «Вопросы по темам»: убрать вопросы, адресованные
`passport`, `database`, `adr`, `research` и `review` — **ни одно из этих имён
больше не тема**. Под каноном 5 темой был каждый документ `docs/`, поэтому
такие вопросы там законны и почти наверняка есть. Переадресовать:
про границу домена и про решение → `architecture`; про хранилище, настройку и
измеренное число → `operations`. Вопрос, который никуда не переадресовывается,
удалить, а не оставить висеть: адресованный несуществующей теме, он не
задаётся никем и молча.
2. Там же, «Недоступно проверке»: те же пять имён убрать из разнесения по темам,
переразнеся содержимое по оставшимся.
3. Там же: подраздел **«Триггеры профиля» → «Триггеры метки»**, и разнести его
на **три** списка вместо двух — «крупное здесь» (про объём), «незнакомое
здесь» (про форму решения) и «мелкое здесь» (опускает до `small`). Раньше
первые две оси были склеены в один список, и потому объём в правило по факту
не входил.
4. **Переименовать метки прогона везде, где проект их называет** — в «Триггерах
метки», в «Недоступно проверке», в журнале дефектов: `quick`**`small`**,
`standard`**`medium`**, `wide`**`large`**. Метка это итог классификации
задачи, и три её значения — часть общего словаря канона и конвейера. Слово
«ступень» из документов уходит: у одной вещи одно имя.
5. Проверить, что свои темы проекта не совпадают именем с закрытыми категориями:
`docs/passport/`, `docs/adr/`, `docs/research/`, `docs/database/`,
`docs/review/` — это слоты канона, а не свои темы, и своим смыслом их
наполнять нельзя.
6. Ничего не заводить и не удалять: раскладка канона 6 совпадает с раскладкой
канона 5 файл в файл.
7. `docs/.pm.json`: `"canon": 6`.
---
## Версия 5 — 2026-08-06 ## Версия 5 — 2026-08-06
Канон перестал быть списком файлов и стал **списком тем ревью**. Раскладка та же, Канон перестал быть списком файлов и стал **списком тем ревью**. Раскладка та же,
@@ -134,7 +134,7 @@
| Термин | Что называет | | Термин | Что называет |
| --- | --- | | --- | --- |
| интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции | | интейк | заведение записи с фильтром и дедупом: «заведение» называет создание файла, слить их — смешать две операции |
| триаж | ступень конвейера, сводящая находки в решение | | триаж | стадия конвейера, сводящая находки в решение |
| провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство | | провенанс | обязательное свойство числа: чем и при каких условиях получено. «Источник» рядом называет саму запись, а не свойство |
| дедуп, дедупликация | сверка нового против уже лежащего | | дедуп, дедупликация | сверка нового против уже лежащего |
| чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки | | чек-лист | перечень, по которому идут сверху вниз, называя исход каждой строки |
+25 -15
View File
@@ -282,30 +282,40 @@
задаёт тот проход, который закрывает эту тему на текущем прогоне, дополнительно задаёт тот проход, который закрывает эту тему на текущем прогоне, дополнительно
к обязательным. к обязательным.
**Адресуй теме, а не имени прохода.** Проходы переезжают между ступенями и **Адресуй теме, а не имени прохода.** Проходы переезжают между метками и
упраздняются; вопрос, адресованный проходу, перестанет задаваться в тот день, упраздняются; вопрос, адресованный проходу, перестанет задаваться в тот день,
когда тот уедет в верхнюю ступень, — и заметить это будет нечем. Тема переезд когда тот уедет в старшую метку, — и заметить это будет нечем. Тема переезд
переживает. переживает.
Темы ядра: `requirements`, `autotests`, `conventions`, `architecture`, Темы ядра: `requirements`, `autotests`, `conventions`, `architecture`,
`security`, `operations`. Плюс любая своя — та, под которую в `docs/` лежит `security`, `operations`. Плюс любая своя — та, под которую проект завёл в
документ. `docs/` **свой** документ. Документы категорий `источник` и `процессный` тем не
порождают, и адресовать вопрос `passport`, `database`, `adr`, `research` или
`review` нельзя — таких тем нет. Вопрос про границу домена адресуй
`architecture`, вопрос про хранилище и числа — `operations`.
### Триггеры профиля ### Триггеры метки
Проектная конкретизация правила выбора профиля, двумя списками и поимённо — Проектная конкретизация правила выбора метки. **Списка три: по одному на
узлами или capability. каждую ось вверх и один вниз** — поимённо, узлами или capability.
**Крупное или незнакомое здесь** — поднимает прогон до `wide`, верхней ступени: **Крупное здесь** — про объём: что трогает несколько узлов или слоёв, переносит
там `security`, `operations` и `architecture` проверяют запуском, и там же ответственность между ними, перекладывает существующий код в новую форму.
единственные замеры. Ступень рассчитана на **510% задач**; если сюда попадает
каждая третья, список написан слишком широко.
**Мелкое здесь** — опускает до `quick`. Помни отрицательный тест конвейера: что **Незнакомое здесь** — про форму решения: то, чего в проекте ещё не было и чью
форму предстоит нащупать по ходу. Признак простой: перед работой нельзя назвать,
какие узлы будут тронуты.
Любая из двух осей поднимает прогон до `large`, старшей метки: там `security`,
`operations` и `architecture` проверяют запуском, и там же единственные замеры.
Метка рассчитана на **510% задач**; если сюда попадает каждая третья, списки
написаны слишком широко.
**Мелкое здесь** — опускает до `small`. Помни отрицательный тест конвейера: что
после мерджа не откатывается обратной правкой (миграция, формат на диске, после мерджа не откатывается обратной правкой (миграция, формат на диске,
публичный контракт, имя), — не `quick`, каким бы маленьким ни был дифф. публичный контракт, имя), — не `small`, каким бы маленьким ни был дифф.
Уточняет умолчания конвейера, не отменяет их; рабочее умолчание — `standard`. Уточняет умолчания конвейера, не отменяет их; рабочее умолчание — `medium`.
### Недоступно проверке ### Недоступно проверке
@@ -409,7 +419,7 @@ severity стоит здесь, а не выводится каждым прох
```json ```json
{ {
"canon": 4 "canon": 6
} }
``` ```
+76 -56
View File
@@ -25,55 +25,69 @@ from dataclasses import dataclass, field
from pathlib import Path from pathlib import Path
from typing import NoReturn from typing import NoReturn
CANON_VERSION = 5 CANON_VERSION = 6
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4 OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
# --- Раскладка канона ------------------------------------------------------- # --- Раскладка канона -------------------------------------------------------
# Тема канона: имя → на какой вопрос отвечает (для внятного отказа). # Документ канона: имя → (категория, на какой вопрос отвечает).
# #
# **Тема живёт файлом `docs/<имя>.md` либо каталогом `docs/<имя>/` с README.md # Категории — из canon.md, раздел «Три категории документов». Разрез один: можно
# внутри.** Форму выбирает проект: тема разрослась — стала каталогом, и это не # ли по документу сказать «в этом изменении сделано не так»?
# смена канона и не повод править скрипт. Обе формы сразу — ошибка: это два дома # тема — да, прямо: документ заводит направление проверки изменения;
# для одного факта, ровно то, от чего канон и защищает. # источник — нет, но он задаёт границу, по которой судит чужая тема;
THEMES = { # процессный — нет: он про то, как мы работаем, а не про изменение.
"passport": "зачем и для кого, чем НЕ является", #
"architecture": "как сложено — обзор, окружение, эксплуатация", # **Категория не меняет обязательности документа** — заводятся все три
"security": "периметр, недоверенный вход, что вне модели", # одинаково и с первого дня. Она меняет только то, что с документом делает
"conventions": "как мы пишем код; индекс, промоут, что механизировано", # конвейер ревью, и потому печатается в отказе: «нет источника passport»
"research": "что показала реальность: наблюдения и числа с провенансом", # читается иначе, чем «нет темы security», и чинится теми же руками, но с
"adr": "почему решено так; индекс, статусы, правило замены", # другим приоритетом.
"review": "настройка конвейера + журнал дефектов", #
# **Документ живёт файлом `docs/<имя>.md` либо каталогом `docs/<имя>/` с
# README.md внутри.** Форму выбирает проект: документ разросся — стал каталогом,
# и это не смена канона и не повод править скрипт. Обе формы сразу — ошибка: это
# два дома для одного факта, ровно то, от чего канон и защищает.
DOCS = {
"passport": ("источник", "зачем и для кого, чем НЕ является"),
"architecture": ("тема", "как сложено — обзор, окружение, эксплуатация"),
"security": ("тема", "периметр, недоверенный вход, что вне модели"),
"conventions": ("тема", "как мы пишем код; индекс, промоут, что механизировано"),
"research": ("процессный", "что показала реальность: наблюдения и числа"),
"adr": ("процессный", "почему решено так; индекс, статусы, правило замены"),
"review": ("процессный", "настройка конвейера + журнал дефектов"),
} }
# Тема, обязательная только при условии: имя → (ключ .pm.json, пояснение). # Документ, обязательный только при условии: имя → (ключ .pm.json, категория,
CONDITIONAL_THEMES = { # пояснение).
"database": ("migrations", "схема хранилища и настройки"), CONDITIONAL_DOCS = {
"database": ("migrations", "источник", "схема хранилища и настройки"),
} }
# Обязательные файлы вне тем. # Обязательные файлы вне раскладки docs/.
REQUIRED = { REQUIRED = {
"CLAUDE.md": "памятка агенту: инварианты с severity, команды, семантика гейта", "CLAUDE.md": "памятка агенту: инварианты с severity, команды, семантика гейта",
"docs/.pm.json": "версия канона и пути, нужные проверкам", "docs/.pm.json": "версия канона и пути, нужные проверкам",
} }
# Файлы, которые тема-каталог обязана держать сверх README.md. # Файлы, которые документ-каталог обязан держать сверх README.md.
THEME_EXTRA = { DOC_EXTRA = {
"adr": {"template.md": "шаблон записи ADR"}, "adr": {"template.md": "шаблон записи ADR"},
} }
# Служебное в docs/ и каталог, который ведёт tasks.py. # Служебное в docs/ и каталог, который ведёт tasks.py. Оба процессные, но
NOT_THEMES = {".pm.json", "tasks"} # проверок формы у них нет: .pm.json не markdown, tasks/ ведёт другой скрипт.
NOT_DOCS = {".pm.json", "tasks"}
# Слоты, которых в каноне нет, — с адресом, куда уезжает содержимое. Имена, # Слоты, которых в каноне нет, — с адресом, куда уезжает содержимое. Имена,
# совпадающие с темой, отсюда убраны намеренно: `docs/conventions.md` и # совпадающие с темой, отсюда убраны намеренно: `docs/conventions.md` и
# `docs/review/` теперь законные формы своих тем. # `docs/review/` теперь законные формы своих тем.
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": "→ docs/tasks/ROADMAP.md",
"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": "→ docs/tasks/",
@@ -122,11 +136,11 @@ def check_slugs(root: Path, rep: Report) -> None:
if not docs.is_dir(): if not docs.is_dir():
return return
# Имена, выбранные каноном, а не проектом: их форма задана здесь же. # Имена, выбранные каноном, а не проектом: их форма задана здесь же.
fixed = {"README.md", "template.md"} | {f"{name}.md" for name in THEMES} fixed = {"README.md", "template.md"} | {f"{name}.md" for name in DOCS}
# Все темы-каталоги, включая свои темы проекта: правило имён общее, а # Все документы-каталоги, включая свои темы проекта: правило имён общее, а
# перечислять их поимённо значило бы закрыть открытый список. # перечислять их поимённо значило бы закрыть открытый список.
for folder in sorted(docs.iterdir()): for folder in sorted(docs.iterdir()):
if not folder.is_dir() or folder.name in NOT_THEMES: if not folder.is_dir() or folder.name in NOT_DOCS:
continue continue
sub = folder.name sub = folder.name
for path in sorted(folder.rglob("*.md")): for path in sorted(folder.rglob("*.md")):
@@ -267,8 +281,8 @@ def check_version(root: Path, cfg: dict, rep: Report) -> None:
) )
def theme_home(root: Path, name: str) -> tuple[Path | None, str | None]: def doc_home(root: Path, name: str) -> tuple[Path | None, str | None]:
"""Дом темы: файл `docs/<имя>.md` или каталог `docs/<имя>/`. """Дом документа: файл `docs/<имя>.md` или каталог `docs/<имя>/`.
Возвращает путь и жалобу. Обе формы сразу это два дома для одного факта, и Возвращает путь и жалобу. Обе формы сразу это два дома для одного факта, и
расходятся они молча: правят одну, читают другую. расходятся они молча: правят одну, читают другую.
@@ -278,7 +292,7 @@ def theme_home(root: Path, name: str) -> tuple[Path | None, str | None]:
as_dir = docs / name as_dir = docs / name
if as_file.is_file() and as_dir.is_dir(): if as_file.is_file() and as_dir.is_dir():
return as_file, ( return as_file, (
f"тема {name} живёт сразу двумя домами — docs/{name}.md и docs/{name}/:" f"{name} живёт сразу двумя домами — docs/{name}.md и docs/{name}/:"
f" оставить один, иначе правят один, а читают другой" f" оставить один, иначе правят один, а читают другой"
) )
if as_file.is_file(): if as_file.is_file():
@@ -286,8 +300,8 @@ def theme_home(root: Path, name: str) -> tuple[Path | None, str | None]:
if as_dir.is_dir(): if as_dir.is_dir():
if not (as_dir / "README.md").is_file(): if not (as_dir / "README.md").is_file():
return as_dir, ( return as_dir, (
f"docs/{name}/ без README.md — у темы-каталога вход обязателен:" f"docs/{name}/ без README.md — у документа-каталога вход"
f" по нему её читают агенты" f" обязателен: по нему его читают агенты"
) )
return as_dir, None return as_dir, None
return None, None return None, None
@@ -298,54 +312,60 @@ def check_required(root: Path, cfg: dict, rep: Report) -> None:
if not (root / rel).exists(): if not (root / rel).exists():
rep.error(f"нет {rel}{what}") rep.error(f"нет {rel}{what}")
for name, what in THEMES.items(): for name, (kind, what) in DOCS.items():
home, complaint = theme_home(root, name) home, complaint = doc_home(root, name)
if home is None: if home is None:
rep.error(f"нет темы {name} (docs/{name}.md или docs/{name}/) — {what}") rep.error(
f"нет документа {name} (docs/{name}.md или docs/{name}/),"
f" категория «{kind}» — {what}"
)
continue continue
if complaint: if complaint:
rep.error(complaint) rep.error(complaint)
if home.is_dir(): if home.is_dir():
for extra, why in THEME_EXTRA.get(name, {}).items(): for extra, why in DOC_EXTRA.get(name, {}).items():
if not (home / extra).is_file(): if not (home / extra).is_file():
rep.error(f"нет docs/{name}/{extra}{why}") rep.error(f"нет docs/{name}/{extra}{why}")
for name, (key, what) in CONDITIONAL_THEMES.items(): for name, (key, kind, what) in CONDITIONAL_DOCS.items():
home, complaint = theme_home(root, name) home, complaint = doc_home(root, name)
if complaint: if complaint:
rep.error(complaint) rep.error(complaint)
if key in cfg and home is None: if key in cfg and home is None:
rep.error( rep.error(
f"нет темы {name} (docs/{name}.md или docs/{name}/){what}" f"нет документа {name} (docs/{name}.md или docs/{name}/),"
f" (обязательна: в .pm.json объявлен {key})" f" категория «{kind}» — {what}"
f" (обязателен: в .pm.json объявлен {key})"
) )
elif key not in cfg and home is None: elif key not in cfg and home is None:
rep.skip(f"тема {name} — в .pm.json нет ключа {key}, проверка неприменима") rep.skip(f"{name} — в .pm.json нет ключа {key}, проверка неприменима")
def check_stray(root: Path, rep: Report) -> None: def check_stray(root: Path, rep: Report) -> None:
"""Лишнего в docs/ больше нет — есть темы проекта. """Лишнего в docs/ больше нет — есть свои темы проекта.
Список тем **открытый**: каждый документ в docs/ и есть заявка на тему Категории `источник` и `процессный` **закрыты**: они перечислены в каноне
ревью, и запретить проекту завести свою нельзя. Проверяются только слоты, поимённо и проектом не пополняются. Открыта только категория `тема`
у которых дом в другом месте, иначе переехавшее содержимое вернулось бы поэтому любой документ в docs/, которого нет в раскладке, и есть заявка на
темой и выглядело законным. свою тему, и запретить её нельзя. Проверяются только слоты, у которых дом в
другом месте, иначе переехавшее содержимое вернулось бы темой и выглядело
законным.
""" """
docs = root / "docs" docs = root / "docs"
if not docs.is_dir(): if not docs.is_dir():
rep.error("нет каталога docs/") rep.error("нет каталога docs/")
return return
known = set(THEMES) | set(CONDITIONAL_THEMES) known = set(DOCS) | set(CONDITIONAL_DOCS)
own: list[str] = [] own: list[str] = []
for entry in sorted(docs.iterdir()): for entry in sorted(docs.iterdir()):
name = entry.name name = entry.name
if name in RETIRED: if name in RETIRED:
rep.error(f"docs/{name} — слота нет в каноне: {RETIRED[name]}") rep.error(f"docs/{name} — слота нет в каноне: {RETIRED[name]}")
continue continue
if name in NOT_THEMES: if name in NOT_DOCS:
continue continue
theme = name[:-3] if entry.is_file() and name.endswith(".md") else name topic = name[:-3] if entry.is_file() and name.endswith(".md") else name
if theme in known: if topic in known:
continue continue
if entry.is_file() and not name.endswith(".md"): if entry.is_file() and not name.endswith(".md"):
rep.error(f"docs/{name} — не markdown: тема ревью читается как текст") rep.error(f"docs/{name} — не markdown: тема ревью читается как текст")
@@ -356,7 +376,7 @@ def check_stray(root: Path, rep: Report) -> None:
f" по нему её читают агенты" f" по нему её читают агенты"
) )
continue continue
own.append(theme) own.append(topic)
if own: if own:
rep.note( rep.note(
f"свои темы проекта: {', '.join(own)} — именной оптики у них нет," f"свои темы проекта: {', '.join(own)} — именной оптики у них нет,"
@@ -418,13 +438,13 @@ def check_placeholders_and_debt(root: Path, rep: Report) -> None:
rep.debt(f"{rel}: {what}") rep.debt(f"{rel}: {what}")
def theme_text(root: Path, name: str) -> str | None: def doc_text(root: Path, name: str) -> str | None:
"""Текст темы целиком: файл или все markdown каталога, склеенные. """Текст документа целиком: файл или все markdown каталога, склеенные.
Проверке всё равно, одним файлом написана тема или десятью: она ищет Проверке всё равно, одним файлом написан документ или десятью: она ищет
упоминание, а упоминание живёт в любом из них. упоминание, а упоминание живёт в любом из них.
""" """
home, _ = theme_home(root, name) home, _ = doc_home(root, name)
if home is None: if home is None:
return None return None
if home.is_file(): if home.is_file():
@@ -437,7 +457,7 @@ def theme_text(root: Path, name: str) -> str | None:
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 = theme_text(root, "architecture") text = doc_text(root, "architecture")
if not specs.is_dir(): if not specs.is_dir():
rep.skip("openspec/specs/ нет — сверка capability с архитектурой неприменима") rep.skip("openspec/specs/ нет — сверка capability с архитектурой неприменима")
return return
+1 -1
View File
@@ -130,7 +130,7 @@ description: Вести содержимое документов канона
Твоя часть на синке: **дефект пишется сразу**, а не «потом, когда починим». Твоя часть на синке: **дефект пишется сразу**, а не «потом, когда починим».
Со временем теряется не факт, а то, почему дефект не поймали, — единственное, Со временем теряется не факт, а то, почему дефект не поймали, — единственное,
ради чего журнал есть. И решение сузить проверки (перестали звать проход, понизили ради чего журнал есть. И решение сузить проверки (перестали звать проход, понизили
профиль) обязано попасть в раздел настройки, а не остаться в отчёте ревью. метка) обязано попасть в раздел настройки, а не остаться в отчёте ревью.
## Промоут в конвенции ## Промоут в конвенции
@@ -250,7 +250,7 @@
Здесь же последний дешёвый момент заметить **разнородную задачу**: раздел Здесь же последний дешёвый момент заметить **разнородную задачу**: раздел
«Затрагивает» показывает границы до того, как заведено предложение об «Затрагивает» показывает границы до того, как заведено предложение об
изменении. Строка, которая одна тянет задачу на ступень выше остального изменении. Строка, которая одна тянет задачу на метку выше остального
перечня, — кандидат на разрез (шов — в `tasks`, `references/split.md`). перечня, — кандидат на разрез (шов — в `tasks`, `references/split.md`).
Замеченная здесь, она стоит одного `edit`; замеченная на ревью — выброшенного Замеченная здесь, она стоит одного `edit`; замеченная на ревью — выброшенного
предложения. предложения.
+4 -4
View File
@@ -279,7 +279,7 @@ stateDiagram-v2
**напоминает** — беклог, заведённый до появления типа, законен, и переоформлять **напоминает** — беклог, заведённый до появления типа, законен, и переоформлять
его «заодно» здесь не просят. его «заодно» здесь не просят.
**Тип не выбирает профиль ревью и вообще ничего не предписывает пайплайну.** **Тип не выбирает метку ревью и вообще ничего не предписывает пайплайну.**
Профиль выбирается по факту изменения, а не по типу задачи: `chore` бывает Профиль выбирается по факту изменения, а не по типу задачи: `chore` бывает
миграцией схемы, `fix` — правкой публичного контракта. Правило «предписание миграцией схемы, `fix` — правкой публичного контракта. Правило «предписание
процесса в теле задачи снимается» типом не отменяется, а подтверждается: он процесса в теле задачи снимается» типом не отменяется, а подтверждается: он
@@ -523,8 +523,8 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
**каждая давать видимую пользу**, а у штурма исход «выкинуть» — полноправный. **каждая давать видимую пользу**, а у штурма исход «выкинуть» — полноправный.
Там же **шов**: где резать, когда допустимых мест несколько. Коротко — по Там же **шов**: где резать, когда допустимых мест несколько. Коротко — по
границе, которая одна поднимает ступень ревью выше остальных; и не резать, когда границе, которая одна поднимает метку ревью выше остальных; и не резать, когда
обе половины остаются в одной ступени, потому что несокращаемый костяк проверок обе половины остаются в одной метке, потому что несокращаемый костяк проверок
платится за каждую задачу отдельно. платится за каждую задачу отдельно.
### Вычитка: два прохода, а не один ### Вычитка: два прохода, а не один
@@ -589,7 +589,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
- **свойство репозитория в рамках** — номер миграции, хеш, версия зависимости: - **свойство репозитория в рамках** — номер миграции, хеш, версия зависимости:
в лежалой задаче протухает молча и становится ложной рамкой. Снимается; в лежалой задаче протухает молча и становится ложной рамкой. Снимается;
снимок берётся при постановке, а не при заведении; снимок берётся при постановке, а не при заведении;
- **предписание процесса в теле** — «делать таким-то профилем ревью», «взять - **предписание процесса в теле** — «делать с такой-то меткой ревью», «взять
такой-то агент»: это второй дом для правила выбора и путь понизить требования такой-то агент»: это второй дом для правила выбора и путь понизить требования
решением, принятым до проектирования. Снимается; решением, принятым до проектирования. Снимается;
- **тип, разошедшийся с задачей** — задача заводилась починкой, а после разбора - **тип, разошедшийся с задачей** — задача заводилась починкой, а после разбора
+9 -9
View File
@@ -25,25 +25,25 @@
Тест выше говорит, **допустим** ли разрез. Где его провести из нескольких Тест выше говорит, **допустим** ли разрез. Где его провести из нескольких
допустимых мест — отвечает шов. допустимых мест — отвечает шов.
**Шов — там, где падает ступень ревью.** Раздел «Затрагивает» перечисляет **Шов — там, где падает метка ревью.** Раздел «Затрагивает» перечисляет
границы; если одна строка перечня поднимает ступень выше остальных, эта часть и границы; если одна строка перечня поднимает метку выше остальных, эта часть и
режется отдельно. Пример: задача перекладывает несколько узлов разом и заодно режется отдельно. Пример: задача перекладывает несколько узлов разом и заодно
добавляет два поля в существующий ответ. Целиком это `wide` — семь проходов по добавляет два поля в существующий ответ. Целиком это `large` — семь проходов по
всему диффу, включая два, что держат машину и идут цепочкой. Разрезанная по шву, всему диффу, включая два, что держат машину и идут цепочкой. Разрезанная по шву,
она даёт `wide` на маленькой переложенной части и `standard` на остатке. она даёт `large` на маленькой переложенной части и `medium` на остатке.
**Считай костяк, а не файлы.** У каждой задачи есть несокращаемые четыре прохода **Считай костяк, а не файлы.** У каждой задачи есть несокращаемые четыре прохода
(гейт, спеки, код, триаж), и они платятся за каждую. Разрез, после которого обе (гейт, спеки, код, триаж), и они платятся за каждую. Разрез, после которого обе
половины остаются в одной ступени, делает ревью **дороже**: тот же объём половины остаются в одной метке, делает ревью **дороже**: тот же объём
проверяется тем же составом, но костяк оплачен дважды. Отсюда правило: **резать, проверяется тем же составом, но костяк оплачен дважды. Отсюда правило: **резать,
когда разрез снимает дорогой проход с большей части диффа**, и не резать, когда когда разрез снимает дорогой проход с большей части диффа**, и не резать, когда
он просто делает файлы мельче. он просто делает файлы мельче.
**Это планирование, а не предписание процесса.** Ступень ревью выбирается по **Это планирование, а не предписание процесса.** Метка ревью выбирается по
факту изменения — тем, кто его видит, — и в тело задачи не пишется: строка факту изменения — тем, кто его видит, — и в тело задачи не пишется: строка
«делать профилем standard» это ровно тот второй дом правила выбора, который «делать с меткой medium» это ровно тот второй дом правила выбора, который
гигиена полей снимает. Шов пользуется ступенью как **признаком**, что в задаче гигиена полей снимает. Шов пользуется меткой как **признаком**, что в задаче
две разнородные работы; решение о профиле остаётся за конвейером. две разнородные работы; решение о метке остаётся за конвейером.
**Цель наследуется.** Все части несут `goal:` родителя: декомпозиция не меняет **Цель наследуется.** Все части несут `goal:` родителя: декомпозиция не меняет
того, чему работа служит. Если у части цель другая — это признак, что дробили не того, чему работа служит. Если у части цель другая — это признак, что дробили не