добавлен конвейер ревью и пайплайн задачи

- одиннадцать проходов ревью перенесены из jellybit и переписаны под домен:
  приём пакетов, слои, координатная идентичность, чувствительность данных
- скиллы task-pipeline и review-pipeline, контракт находок, журнал промахов
This commit is contained in:
av
2026-08-01 14:11:41 +03:00
parent 505664acf1
commit 36908b774c
16 changed files with 2063 additions and 0 deletions
+234
View File
@@ -0,0 +1,234 @@
---
name: review-pipeline
description: Конвейер ревью изменений healthlog — детерминированный гейт, сверка с дельта-спеками OpenSpec в обе стороны, generative-проходы (рубрика, независимая реализация, stdlib grounding, negative space), архитектура, враждебные постановки и обязательный триаж. Вызывается из task-pipeline (чекпоинты ревью) и отдельно — профилем design на OpenSpec-предложении ДО кода.
---
# Конвейер ревью (healthlog)
Готовит ревью — **не заменяет его**. Потребитель отчёта — оркестратор, который
чинит код; человек читает только сводку, развилки и границы покрытия.
## Три правила, из которых всё следует
Если ситуация не покрыта инструкцией — решай по ним.
1. **Recall чек-листа равен длине чек-листа.** Проход, устроенный как «проверь
пункты 1..N», найдёт ровно перечисленное. Всё неявное — идиомы, форма
решения, «так не делают» — неперечислимо по определению: перечислимое уже
стало бы конвенцией. Отсюда деление проходов на **applicative** (применяют
заданный критерий) и **generative** (сперва порождают критерий или
альтернативу, потом сравнивают). Расширять чек-листы бесполезно; неявный слой
достают только generative-проходы.
2. **Ценность верификатора = наличие внешнего оракула × декорреляция с
автором**, а не число ролей. Под всеми ролями одна модель с одними
априорными, вход у всех общий: седьмая роль почти не добавляет recall, но
линейно удорожает триаж. Иерархия надёжности: детерминированный инструмент >
агент, который его **запускает** и интерпретирует вывод > агент с чистым
мнением. Максимум работы переносим вниз.
3. **Отчёт без границ покрытия хуже отсутствия отчёта.** «Критичных проблем не
обнаружено» потребляет ощущение проверенности, ничего не гарантируя. Секция
границ покрытия обязательна и не сокращается — в том числе в докладе человеку.
## Что этот конвейер защищает в healthlog
Инварианты, нарушение которых — по умолчанию `critical` (подробно —
`CLAUDE.md`, `docs/architecture.md`):
- **Точка хранится дословно.** Всё, что теряет поле внутри точки, необратимо:
сырой архив живёт 14 дней, дальше истина — сами точки.
- **Идентичность по координатам** (`метрика + слой + метка`). `source` в ключ
не входит. Неверное правило слияния портит историю молча — заметить это
можно только сверкой с родным экспортом Apple, то есть месяцами позже.
- **Агрегации при записи нет.** Свёртка живёт только в ответе и только с
измеренным родом метрики. Нижний слой HAE не суммируется никогда.
- **Данные о здоровье чувствительнее токенов.** Тело запроса в логе на уровне
выше `DEBUG`, файл выгрузки под контролем версий — это утечка, а не
неаккуратность.
- **Приём не теряет доставку.** Код ответа отражает доставку, а не разбор;
тело ложится на диск до разбора.
## Профили
| Профиль | Когда | Стадии |
|---|---|---|
| `quick` | багфикс, локальная правка, доки | 0, 1, 5 |
| `standard` | новая функциональность в существующем пакете | 0, 1, 2, 5 |
| `deep` | новый пакет, изменение публичного контракта, миграция БД, трогает инварианты выше | 0, 1, 2, 3, 4, 5 |
| `design` | **до кода**, на OpenSpec-предложении | rubric + idiom + architecture (см. ниже) |
Правило выбора — по факту изменения, не по ощущению важности:
- есть миграция в `internal/store/migrations/`, новый пакет `internal/*`,
изменение контракта Read API или MCP, трогается правило слияния точек или
вывод слоя → `deep`;
- иначе меняется поведение, видимое снаружи (эндпоинт, форма ответа, код
ответа приёма, формат лога) → `standard`;
- иначе → `quick`.
Профиль объявляется в отчёте. Понижение профиля — решение оркестратора, и оно
попадает в границы покрытия строкой «профиль понижен до X, потому что …».
## Стадия 0 — Gate (обязательна во всех профилях)
Агент `healthlog-review-gate`. Запускает `task gate` и интерпретирует вывод.
**Пока гейт красный — опиниативные проходы не запускаются.** Оркестратор чинит и
перезапускает гейт. Исключение одно: отказ, унаследованный от базовой ветки
(гейт проверяет это прогоном на базе) — тогда он фиксируется находкой и не
блокирует.
Гейт возвращает не только «зелено/красно», но и находки класса **отсутствующая
верификация**: изменённые строки без покрытия, конкурентность без теста с
параллельным доступом, флаки-тест (не ниже `major`), недоступный инструмент.
Шаги выбираются по изменённым файлам: правка документации не гоняет тесты,
линтеры и `-race`. Пропуск при этом не молчит — он виден в сводке с причиной и
уезжает в границы покрытия, как и любой другой `SKIP`.
Два шага гейта специфичны для healthlog и красят его безусловно:
`no-health-data` (файл из `data/` попал под контроль версий) и `config-samples`
(структура конфига изменилась, а `config.example.toml`/`config.docker.toml`
нет).
## Стадия 1 — Conformance (обязательна во всех профилях)
Два applicative-прохода: оба применяют **записанный** критерий, оба дешёвые,
запускаются **одним сообщением параллельно**.
- `healthlog-review-specs` — критерий взят из **дельта-спек change в
`openspec/changes/<id>/specs/`**, а не из proposal, сообщения коммита или
описания задачи. Сверка двунаправленная; направление `code → spec` важнее.
- `healthlog-review-code` — критерий взят из `docs/conventions.md`, и только та
его часть, которая **не выражается правилом**: механизируемое уже проверила
стадия 0 (`sloglint`, `forbidigo`, `errorlint`, `depguard`). Уровень лога по
адресату, единственный логирующий чекпоинт на доменной границе, трансляция
ошибки на внешней границе, `ident.Parse` на входной границе, время в UTC
через `store.Now()`.
Recall обоих равен длине их источника — это и есть предел applicative-проходов,
ради которого существует стадия 2.
## Стадия 2 — Tacit layer (generative; `standard`, `deep`)
Четыре прохода, каждый в своём контексте, запускаются **одним сообщением
параллельно**:
- `healthlog-review-rubric` — порождает рубрику до чтения кода, потом судит по ней;
- `healthlog-review-reimpl` — пишет свою реализацию, не открывая существующую,
затем диффит по решениям (в профиле `standard` включается только если
изменение содержит новый файл или функцию длиннее ~60 строк — иначе дорог и
бесполезен);
- `healthlog-review-idiom` — заземляет «идиоматичность» на stdlib и поимённые
положения гайдов;
- `healthlog-review-negative` — чего нет и что лишнее.
## Стадия 3 — Global (`deep`, `design`)
Агент `healthlog-review-architecture`. Получает **вход шире диффа**: дерево
пакетов с назначением, граф внутренних зависимостей, инвентарь существующих
концепций проекта. Готовит вход команда:
```
task review:context > tmp/review-context.md
```
Главный вопрос — концептуальная целостность и **второй способ** делать то, что
уже делается. Потолок — 3 находки плюс секция «дешевле переделать до мерджа».
## Стадия 4 — Adversarial и operational (`deep`)
`healthlog-review-adversary` (находка = построенный путь, не свойство) и
`healthlog-review-ops` (постмортем от симптома у владельца сервиса к строке).
Запускаются параллельно со стадией 2, если профиль `deep`.
Для healthlog эксплуатационный проход обязан держать в голове: телефон шлёт
непрерывно и молча, тела доходили до 42 МБ, запись в часовой объект —
read-modify-write под конкурентными доставками, а тихо сломавшаяся
автоматизация обнаруживается не сразу.
## Стадия 5 — Triage (обязательна)
Агент `healthlog-review-triage`. Единственный, кто агрегирует. Получает сырые
выводы всех проходов и `git diff`; возвращает финальный отчёт.
Без триажа шесть проходов дают порядка сорока замечаний при единицах
существенных. Потребитель здесь — оркестратор, который **молча реализует** всё,
что прочитал: цена нетриажированного отчёта — не потерянное время человека, а
разросшийся от вкусовщины код.
Порядок: дедупликация по причине → оракул для всего `critical`/`major`
понижение неподтверждённого до гипотезы → отсев вкусовщины → ранжирование по
ущербу × вероятности → потолок 7 пунктов в основном списке.
## Профиль `design` — до кода
Запускается на шаге ревью спек (`task-pipeline` шаг 4), когда change уже имеет
`proposal.md` + дельта-спеки, но кода ещё нет. Состав:
1. `healthlog-review-specs` в режиме «дизайн ДО кода»;
2. `healthlog-review-rubric`, фаза 1 без фазы 2: рубрика на задуманный узел
становится приёмочными критериями и уезжает в `tasks.md`;
3. `healthlog-review-idiom` по описанию решения (какие конструкции stdlib
закрывают задачу; не изобретаем ли то, что уже есть);
4. `healthlog-review-architecture` на предложении: вводит ли change новое
понятие, можно ли выразить существующими, не появляется ли второй способ;
5. вопрос автору дизайна: **«предложи три формы решения и назови компромисс
каждой»** — если ответ показывает, что рассматривалась одна, это находка.
Смысл профиля: архитектурная находка на готовом коде стоит переписывания и
поэтому игнорируется; та же находка на предложении стоит абзаца обсуждения.
## Контракт находок
Единый для всех проходов — [references/finding-contract.md](references/finding-contract.md).
Коротко: заголовок через **последствие**, обязательные поля `Файл`, `Severity`,
`Confidence`, `Оракул`, `Последствие`, `Предложение`, `Найдено проходом`.
`critical` без оракула или построенного пути не существует. Находка без поля
«Последствие» не выводится вовсе.
Каждый проход завершает вывод блоком `## Coverage of this pass`.
## Что происходит с находками дальше
- Оркестратор чинит помеченное `Действие: инлайн` и **не логирует мелочь**.
- `Действие: развилка` — на человека через `AskUserQuestion`, вопросом с
вариантами.
- Находка не для этого мерджа, но реальная (отложенный `major`, развилка,
решённая «потом») — не теряется: заводится задачей через скилл `backlog`
(интейк из ревью), с оракулом и провенансом в теле. Мелочь класса `nit` — в
пакетный файл, а не файлом на находку.
- `Promote candidates` — по процедуре
[references/promote.md](references/promote.md): находка → конвенция → правило
линтера → **удаление из конвенций и из промптов**. Третий шаг обязателен.
- Дефект, проскочивший ревью и всплывший позже, идёт в
[docs/review-journal.md](../../../docs/review-journal.md) — сразу, не
ретроспективно: теряется именно причина непоймания.
## Честный предел
Модель воспроизводит медиану публичного Go, смещённую к популярному и
туториальному: отсюда тяга к интерфейсам ради интерфейсов, лишним мокам и
конфигурируемости, которую никто не просил. **«Идиоматично» и «распространено» —
разные вещи**; проходы обязаны различать их и опираться на поимённое положение
гайда, а не на ощущение частотности.
Согласие нескольких проходов — **не подтверждение**: это один источник,
высказавшийся несколько раз. Совпадение повышает приоритет, но не `confidence`.
Ни одному проходу принципиально недоступно:
- поведение Health Auto Export на следующем обновлении приложения;
- то, что реально лежит в Apple Health, — сверить можно только с ручным
экспортом, а он делается раз в 2–3 месяца;
- поведение таблицы SQLite под объёмом нескольких лет истории;
- завязка внешних потребителей (агент-медик, трекер, игра) на текущую форму
ответа;
- суждение «этой метрики не должно существовать».
Это и есть причина, по которой конвейер готовит ревью, а не заменяет его.
## Ссылки
- [references/finding-contract.md](references/finding-contract.md) — контракт находок.
- [references/promote.md](references/promote.md) — промоут находка → конвенция → правило → удаление.
- [docs/review-journal.md](../../../docs/review-journal.md) — журнал проскочивших дефектов.
@@ -0,0 +1,84 @@
# Контракт находок
Единый формат для всех проходов конвейера ревью. Проход, нарушивший контракт,
считается сломанным — триаж вправе выбросить его вывод целиком.
## Форма находки
```
### <краткая формулировка ПОСЛЕДСТВИЯ, не симптома>
- Файл: internal/store/bucket.go:120-134
- Severity: critical | major | minor | nit
- Confidence: high | medium | low
- Оракул: <падающий тест / команда с выводом / положение гайда / нет>
- Последствие: <что произойдёт и при каких условиях>
- Предложение: <конкретное изменение>
- Найдено проходом: <имя агента>
```
## Правила
- **Заголовок через последствие.** Не «нет проверки токена», а «читатель без
токена выгрузит всю историю пульса». Не «слияние перезаписывает точку», а
«повторная доставка сотрёт `start`/`end` у уже сохранённой точки, и восстановить
их можно только из экспорта Apple». Симптом в
заголовке — это заявка на то, что читатель сам достроит последствие; он не
достроит, он просто починит симптом.
- **`critical` без оракула или построенного пути не существует.** Оракул — это
падающий тест, вывод выполненной команды или поимённое положение гайда. Не
«вероятно, здесь гонка», а `CGO_ENABLED=1 go test -race` с выводом детектора.
- **`confidence: low` — это «так обычно пишут».** Такие находки допустимы, но не
поднимаются выше `minor`. Частотность конструкции в публичном Go — не аргумент.
- **Находка без поля «Последствие» не выводится вовсе.** Пустое «Последствие:
ухудшает читаемость» равносильно отсутствию поля.
- **`nit` допустим только при нарушении записанной конвенции** — со ссылкой на
файл и раздел `docs/conventions.md` либо на правило `.golangci.yml`. Если
правило механизируемо, но не механизировано — это не находка ревью, это
`Promote candidate` (см. [promote.md](promote.md)).
- **Расхождение — не дефект, пока не названо последствие.** Особенно для
`healthlog-review-reimpl`: «я бы сделал иначе» без последствия не выводится.
## Шкала severity
| Severity | Что это | Пример |
|---|---|---|
| `critical` | нарушение инварианта безопасности данных, потеря/порча данных, утечка секрета, построенный путь к отказу | точка потеряна при слиянии часового объекта, тело выгрузки Apple Health в поле лога |
| `major` | сломанное требование дельта-спеки, необрабатываемый отказ штатного сценария, флаки-тест, поведение вне спеки, меняющее исход | приём отвечает 200, не записав тело в архив: доставка считается принятой, а данных нет |
| `minor` | отступление от конвенции с реальной ценой, отсутствующая наблюдаемость, дублирование, которое разойдётся | разбор пакета не пишет ни одного чекпоинта, и молчащая автоматизация неотличима от пустого потока |
| `nit` | нарушение записанной конвенции без последствий за пределами чтения | `msg` с интерполяцией вместо константы |
## Блок границ покрытия
Каждый проход завершает вывод этим блоком. Он не сокращается и не заменяется
фразой «всё проверено».
```
## Coverage of this pass
- проверено: <что реально прочитано/запущено, с путями и командами>
- не проверялось и почему: <бюджет, недоступный инструмент, вне входа>
- принципиально недоступно этому проходу: <из charter'а агента>
```
## Финальный отчёт триажа
Секции строго в этом порядке, потолок — 7 пунктов в первых двух:
1. `Блокирует мердж` (≤3, каждая с оракулом);
2. `Стоит исправить сейчас` (≤4);
3. `Гипотезы без доказательства` — что понижено и почему;
4. `Promote candidates` — кандидаты в конвенцию или правило линтера;
5. `Границы покрытия` — сводная, обязательная.
Каждая находка в секциях 1–2 несёт дополнительное поле:
```
- Действие: инлайн | развилка
```
`инлайн` — оркестратор чинит сам, не спрашивая и не логируя. `развилка` — цена
исправления сопоставима с переработкой, либо выбор меняет scope, либо решение
трогает инвариант: идёт человеку через `AskUserQuestion` вопросом с вариантами.
Потребитель отчёта — оркестратор, который **реализует прочитанное**. Поэтому
потолок в 7 пунктов — не забота о внимании читателя, а защита кодовой базы от
правок, которых никто не заказывал.
@@ -0,0 +1,89 @@
# Промоут: находка → конвенция → правило → удаление
Механизм храповика. Без него конвейер выдаёт одни и те же находки бесконечно, а
конвенции не растут — то есть внимание тратится повторно на уже решённое.
Роли уровней:
- **generative-проходы** — механизм *открытия* неявного (дорого, шумно, но
только они достают то, чего нет в списках);
- **конвенции** — дешёвая *регрессионная сетка* на уже открытое;
- **правила линтера** — то же с детерминированным оракулом и нулевой ценой
внимания.
## Шаг 1. Находка → конвенция
Условия: находка **принята** при ревью (не отвергнута, не понижена в гипотезу) и
**не специфична для одного места**.
- Формулируется как **проверяемое свойство**, а не как совет: «уровень доменного
отказа выбирает единственный логирующий чокпоинт», а не «внимательнее с
уровнями логов».
- Записывается источник — какой проход нашёл. Это единственные данные для
калибровки: проход, чьи находки регулярно доезжают до конвенции, оправдан;
проход, чьи находки не доезжают никогда, — кандидат на `drop`.
- Место записи — соответствующий файл `docs/conventions.md`. Если тема
относится к поведению системы, а не к тому, как мы пишем код, — это не
конвенция, а требование: заводится дельта-спека OpenSpec обычным путём.
Промоут идёт **тем же путём, что change → spec**: правка попадает в тот же
коммит, что и исправление кода, с пометкой в сообщении — история промоутов
видна в `git log docs/conventions/`.
## Шаг 2. Конвенция → правило
Как только свойство выражается детерминированно, оно переезжает в инструмент.
Порядок предпочтения — от дешёвого к дорогому:
1. **готовый линтер** в `.golangci.yml` (`sloglint`, `errorlint`, `depguard`,
`forbidigo`, `misspell`, стандартный набор v2);
2. **`forbidigo`/`depguard` с собственным паттерном** — запрет идентификатора или
импорта;
3. **`revive`/`gocritic` с настройкой** — когда нужна форма, а не имя;
4. **тест-сканер исходников** `internal/arch_test.go` — когда правило про
структуру проекта или SQL: направление зависимостей, `AUTOINCREMENT` в
миграциях, матчинг ошибки по тексту, бизнес-логика в транспорте;
5. **`go/analysis`-анализатор** — последний рубеж, заводим только если 1–4 не
выражают правило.
Правило обязано быть **зелёным на текущем коде в момент включения**: иначе
lefthook блокирует любой коммит, и правило снимут первым же раздражённым
движением. Приводить код в соответствие — часть шага 2, отдельным коммитом.
## Шаг 3. Удаление из конвенций и из промптов
**Шаг, который пропускают чаще всего, и единственный, ради которого затевались
первые два.**
Как только правило работает:
- из `docs/conventions.md` убирается формулировка правила; остаётся, если
нужно, одна строка «проверяется линтером `<имя>`» — но только там, где без неё
раздел теряет связность;
- из charter'ов агентов (`.claude/agents/healthlog-review-*.md`) убирается
соответствующий пункт;
- из `openspec/config.yaml``context` убирается дубль, если он там был.
Практический критерий: **в прозаических конвенциях остаётся только то, что
принципиально не выражается правилом.** Файл конвенций на несколько сотен строк
размазывает внимание модели по тривиальному — она добросовестно проверит
именование полей лога и не дойдёт до формы решения. Каждая строка конвенций,
которую можно было бы проверить машиной, оплачивается непойманным дефектом
где-то ещё.
## Обратное движение
Правило, которое даёт ложные срабатывания чаще, чем ловит (порядка трети от
общего числа), снимается и возвращается в прозу — или удаляется совсем, если
свойство перестало быть важным. Снятие фиксируется там же, где включалось, с
одной строкой «почему».
## Что промоуту не подлежит
- Находка, специфичная для одного места (её лечит комментарий в коде).
- Вкусовщина: не меняет поведения, не влияет на стоимость следующего изменения,
не нарушает записанного. Такое выбрасывается на триаже и не хранится.
- Свойство, требующее знания рантайма (профиль нагрузки, история инцидентов) —
его нельзя проверить ни промптом, ни линтером; место такому — в
[journal.md](../../../../docs/review/journal.md) как «признано
неавтоматизируемым».
+192
View File
@@ -0,0 +1,192 @@
---
name: task-pipeline
description: Автономно проводит задачу healthlog через полный цикл SDD — от выбора в беклоге до коммита (opsx explore→propose→ревью спек→apply→ревью кода→archive→чистка беклога). Использовать, когда пользователь просит взять/сделать задачу из беклога или довести идею до реализации.
---
# Пайплайн задачи (healthlog)
Оркестратор одной задачи по Spec Driven Development: проводит её от беклога до
коммита максимально автономно, привлекая пользователя **только на реальных
развилках** (компромиссы, изменение scope, угроза инвариантам). Механику не
согласовываем — делаем.
Перед стартом прочитай `CLAUDE.md`, а также `README.md`, `docs/architecture.md`,
`docs/conventions.md`, если ещё не в контексте. Это тонкая обёртка над
каноническими скиллами `opsx:explore` / `opsx:propose` / `opsx:apply` /
`opsx:archive` — вызывай их через Skill, не переизобретай их шаги.
## Что нельзя сломать
healthlog — хранилище данных о здоровье, у которого источник (телефон) шлёт
непрерывно и молча. Отсюда особенности, которых нет в обычном сервисе:
- **Поток не останавливается на время задачи.** Сервис поднят в контейнере
(`task up` / `task restart`), данные в `./data`. Перезапуск на пару секунд
безопасен — дыру закроют средний и глубокий проходы синхронизации; а вот
сломанный приём, оставленный работать, теряет данные необратимо.
- **Потерянная точка не восстанавливается.** Сырой архив живёт 14 дней. Любая
правка разбора, слияния или вывода слоя — это `deep`-профиль ревью, без
исключений.
- **Данные чувствительны.** Ничего из `./data` не попадает ни в git, ни в
логи выше `DEBUG`, ни в вывод агента. Гейт проверяет первое механически
(`no-health-data`), остальное — на тебе.
- **Разведка уже проведена.** `docs/local-research.md` — 41 находка на живом
потоке, половина расходится с документацией HAE. Проверь там, прежде чем
строить догадку о формате: скорее всего вопрос уже закрыт измерением.
## Принцип автономности
Зови пользователя (через **AskUserQuestion**) только когда решение реально его:
- **Выбор задачи**, если он не задан явно.
- **Развилки грумминга** на explore: несколько равнозначных направлений,
спорный scope, продуктовый компромисс.
- **Замечания ревью спек**, требующие выбора: смена подхода, урезание/расширение
scope, риск инварианту хранения данных.
- Всё остальное — механика: делаем без спроса. Мелкие замечания ревью чиним
инлайн, не логируем.
Стиль правок — заточка под проект и конвенции, right-size, без золочения.
## Шаги
### 1. Выбрать / прочитать задачу
- Если задача задана (slug, файл в `docs/backlog/` или описание) — прочитай её
файл и связанные спеки/черновики.
- Если не задана — покажи топ-кандидатов из `docs/backlog/README.md` (высокий
приоритет, не `[idea]`) через **AskUserQuestion** и дай выбрать.
- Задача с префиксом `[idea]` (ещё без решения «делаем») — сперва обязательно
через explore (шаг 2), там она либо становится задачей, либо остаётся идеей.
Формат файла задачи и индекса держит скилл `backlog` — здесь мы беклог только
читаем. Если по ходу выбора вскрылось, что задача устарела, дублируется или
разрослась в эпик, это работа для скилла `backlog`, а не для пайплайна.
Оцени тривиальность (влияет на шаг 4):
- **Тривиальная** — локальная правка без изменения поведения/спек/схемы БД,
очевидное решение. Explore и ревью спек пропускаем.
- **Нетривиальная** — новое/изменённое поведение, дизайн-развилки, затрагивает
инварианты, схему БД или несколько capability. Полный цикл.
### 2. (Опц.) Груммить идею — `opsx:explore`
Только для `[idea]`-задач или когда постановка мутная. Вызови Skill
`opsx:explore`. Развилки грумминга — на пользователя (AskUserQuestion). Выход:
ясная постановка, готовая к propose. **В explore не пишем код.**
### 3. Завести change — `opsx:propose`
Вызови Skill `opsx:propose`. Получаем `proposal.md`, дизайн (для нетривиальных),
дельта-спеки (`ADDED`/`MODIFIED`/`REMOVED Requirements`), `tasks.md`. Каждое
`### Requirement` содержит `SHALL`/`MUST`; структурные заголовки английские,
сценарии `GIVEN/WHEN/THEN`. Прогони `openspec validate --strict <id>`.
### 4. (Нетривиальная) Ревью предложения — профиль `design`, ДО кода
Первый чекпоинт ревью-процесса. Вызови Skill **`review-pipeline`** с профилем
`design` и ссылкой на change `<id>`. Он запустит `healthlog-review-specs` (режим
«дизайн/спеки ДО кода»), `healthlog-review-rubric` (фаза 1: приёмочные критерии
для задуманного узла), `healthlog-review-idiom` и `healthlog-review-architecture`
по предложению.
Смысл профиля: архитектурная находка на готовом коде стоит переписывания и
потому игнорируется — та же находка здесь стоит абзаца обсуждения. Рубрику из
`healthlog-review-rubric` перенеси в `tasks.md` как приёмочные критерии.
### 5. Отработать замечания ревью предложения
- Мелочь и явные улучшения — правь сам в спеках/дизайне.
- Развилки (компромисс, scope, инвариант) — на пользователя (AskUserQuestion).
- После правок перепрогони `openspec validate --strict <id>`.
### 6. Написать код — `opsx:apply`
Вызови Skill `opsx:apply` для реализации `tasks.md`. Код по конвенциям
`docs/conventions.md`: ошибки stdlib с `%w`/`errors.Is`, логи только `slog` без
секретов и тел запросов, время в UTC через `store.Now()`, ULID через
`internal/ident`, миграции goose. Меняешь схему — обнови ER-схему
`docs/database.md` в том же change (гейт это проверяет).
Прогони `task gate` и добейся зелёного — он же гейт следующего шага.
**Поведенческая верификация.** Если задача меняет реальное поведение (новый
эндпоинт, разбор входа, схема БД, форма ответа) — зелёных юнит-тестов мало.
Подними изменение вживую: `task restart`, затем прогони сценарий по настоящим
данным из `./data` (89+ доставок реального потока) или скриптом из
`tmp/research/`. Пропусти только для чисто внутренних правок без наблюдаемого
рантайма.
**Сервис не оставляем лежать.** Если `task restart` упал — почини или откати
до конца шага: телефон продолжает слать всё это время.
### 7. Ревью кода — Skill `review-pipeline`
Второй чекпоинт. Вызови Skill **`review-pipeline`**, дав ссылку на change
`<id>`, базу диффа и профиль. Профиль выбирается по факту изменения, а не по
ощущению важности (правило — в самом скилле):
- миграция, новый пакет, контракт Read API или MCP, правило слияния точек или
вывод слоя → `deep`;
- иначе меняется поведение, видимое снаружи → `standard`;
- иначе (багфикс, локальная правка, доки) → `quick`.
Скилл сам гоняет гейт, нужные проходы и обязательный триаж. Возвращает отчёт с
потолком 7 пунктов, разметкой `Действие: инлайн | развилка` и секцией границ
покрытия.
Отработай так же, как шаг 5: помеченное `инлайн` чини сам и не логируй,
`развилка` — на пользователя через AskUserQuestion (вопрос уже сформулирован
триажем). После правок — снова `task gate`.
**Границы покрытия из отчёта не выбрасывай** — они уезжают в финальный доклад
(шаг 10) сжатой строкой. Отчёт, из которого исчезло «что проверить было
невозможно», превращается в ложное ощущение проверенности.
### 8. Архивировать — `opsx:archive`
Вызови Skill `opsx:archive`: change уезжает в `openspec/changes/archive/`,
дельты вливаются в `openspec/specs/`.
### 9. Закрыть беклог и синк доков
Ревью выполненного — **до** чистки. Затем:
- Удали файл задачи `docs/backlog/<slug>.md` и строку в `docs/backlog/README.md`.
Реализованное не держим в беклоге — у него есть коммит и спека.
- Суть переехавшего решения — в `docs/architecture.md`, если ещё не там.
- Менялась структура БД — убедись, что `docs/database.md` обновлён в этом же
change.
- Новое, узнанное о формате HAE или о данных, — в `docs/local-research.md`
очередной находкой. Это источник истины по формату, и он ценнее кода.
- Проверь согласованность индекса командой `check` скилла `backlog`.
### 10. Коммит
Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не
создавай и не переключай, ничего не пушь. При ручном запуске HEAD обычно на
`master` — коммит идёт прямо в него, без feature-веток.
Сообщение — по-русски, по скиллу `commit` (первая строка отвечает «что
сделано», тело списком 1–3 пункта, без трейлеров). Одна задача — один
осмысленный коммит.
Готово — доложи пользователю кратко: что сделано, какие развилки решались,
ссылка на архивный change. **Плюс одна строка границ покрытия** из отчёта
ревью: какой профиль гонялся и что проверить было невозможно. Доклад без неё
сообщает «проверено», не сообщая, что именно.
## Тонкости
- **Не завязывайся на master и корень репо.** Скилл работает в текущем worktree
и на текущей ветке: не делай `git checkout`/`switch`, не создавай веток, не
пушь.
- Не пропускай `openspec validate --strict` перед архивацией.
- Тривиальная задача: шаги 2 и 4 пропускаются; ревью кода (шаг 7) остаётся
всегда, но в профиле `quick`.
- Гейт блокирует: пока `task gate` красный, опиниативные проходы не
запускаются. Чинить и перезапускать, а не «посмотреть заодно».
- Если ревью предлагает крупную переработку — это развилка, не правь молча,
вынеси пользователю.
- Держи пользователя в цикле короткими репликами на переходах фаз, но не проси
подтверждать механику.