добавлены плагины av-dev-tasks и av-dev-pipeline
Пара плагинов с намеренно проведённой границей: av-dev-tasks отвечает за то, что делаем и в каком порядке, av-dev-pipeline — за то, как ведём одну задачу. Зависимости между ними нет: управление задачами работает и с ручным исполнением, пайплайн — на проекте с любым учётом задач. - av-dev-tasks — преемник av-dev-backlog: цели вместо приоритетов, спринт под одну цель с заморозкой набора, различение вопроса и блокера, каденция «вопросы — разбор — переоценка — набор». Раскладка docs/tasks с items/, PLAN.md, BACKLOG.md, SPRINT.md, REJECTED.md; проверенное из av-dev-backlog перенесено, не переписано. - av-dev-pipeline — вынос того, что лежало копиями в healthlog и jellybit (3628 строк) и уже разошлось: цикл SDD, конвейер ревью с обязательным триажем, прогон нескольких задач разом. Проектная специфика вынесена в файл-бриф, charter'ы несут метод. Коммит фиксирует состояние на момент ревью: три прохода нашли блокирующие дефекты (нет шага, заводящего бриф; git rebase на занятой worktree ветке; sprint drop пишет наполовину) — они чинятся следующими коммитами. Сохранено как база, от которой видно правки.
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
# Шаблон брифа проекта
|
||||
|
||||
Скопируй в `docs/review-brief.md` и заполни. Контракт разделов — в
|
||||
[project-brief.md](project-brief.md); здесь только образец заполнения.
|
||||
|
||||
Курсивом даны пояснения — их из готового брифа убирают. Примеры взяты из двух
|
||||
разных проектов (коллектор данных с непрерывным потоком и связующий сервис вокруг
|
||||
внешних демонов), чтобы было видно, как один и тот же раздел выглядит при разной
|
||||
природе проекта.
|
||||
|
||||
---
|
||||
|
||||
## Проект
|
||||
|
||||
*Абзац: что делает — и чего не делает.*
|
||||
|
||||
> Коллектор выгрузок с телефона. Принимает доставки, хранит их и отдаёт другим
|
||||
> сервисам. Это **хранилище, а не аналитика**: принять, дедуплицировать,
|
||||
> сохранить, отдать. Не переименовывать поля источника, не интерпретировать
|
||||
> значения; свёртка считается только в ответе на запрос.
|
||||
|
||||
> Связующий сервис между качалкой и медиасервером: принимает задание, качает,
|
||||
> распознаёт содержимое, раскладывает файлы ссылками. **Не медиатека и не
|
||||
> плеер** — ничего не хранит сверх метаданных о раскладке.
|
||||
|
||||
## Инварианты
|
||||
|
||||
*Проверяемое свойство + последствие + severity по умолчанию. Цитируются
|
||||
формулировкой.*
|
||||
|
||||
- **Точка сохраняется дословно.** Незнакомое поле не отбрасывается, число не
|
||||
округляется при записи. Нарушение — необратимая потеря: сырой архив живёт
|
||||
14 дней, дальше истина только в свёртке. По умолчанию `critical`.
|
||||
- **Источник неприкосновенен.** Только `mkdir`/`link(2)`/`unlink` собственных
|
||||
ссылок; файлы под каталогом загрузок не трогаются никогда. Нарушение —
|
||||
повреждение чужих данных, необратимое. По умолчанию `critical`.
|
||||
- **Сохранили — значит приняли.** Код ответа отражает доставку, а не разбор:
|
||||
непонятое содержимое — `200`, тело уже на диске. Нарушение стоит доставки,
|
||||
которую отправитель не повторит. По умолчанию `critical`.
|
||||
- **Секреты и данные пользователя не в логах.** Тело запроса — только на `DEBUG`
|
||||
и с обрезкой. По умолчанию `critical`.
|
||||
- **Агрегации при записи нет.** Нарушение искажает историю молча и
|
||||
диагностируется только сверкой с внешним источником, то есть месяцами позже.
|
||||
По умолчанию `major`, `critical` — если испорченное невосстановимо.
|
||||
|
||||
## Гейт
|
||||
|
||||
- **Команда:** `task gate BASE=<база>`; база по умолчанию —
|
||||
`git merge-base HEAD master`, на `master` — `HEAD~1`.
|
||||
- **Логи шагов:** `tmp/gate/<шаг>.log`. Сводка печатает `OK`/`FAIL`/`WARN`/`SKIP`;
|
||||
краснит гейт только `FAIL`.
|
||||
- **Шаги:** сборка, `vet`, линтеры, форматирование, тесты, повторный прогон на
|
||||
флаки, `-race`, покрытие изменённых строк, накат миграций с нуля, поиск
|
||||
секретов, `govulncheck`.
|
||||
- **Красят безусловно** *(перечислить с причиной — это главная часть раздела)*:
|
||||
- `no-user-data` — файл из каталога данных попал под контроль версий: убрать
|
||||
обычным коммитом уже нельзя;
|
||||
- `config-samples` — структура конфига изменилась, а образец нет: забытое поле
|
||||
обнаруживается не тестом, а тем, что через полгода о нём никто не знает;
|
||||
- `migrations` — миграции не накатываются с нуля: восстановление перестаёт
|
||||
работать ровно тогда, когда оно нужно;
|
||||
- `er-schema` — миграция тронута, а схема в документации не обновлена.
|
||||
- **Чего в гейте намеренно нет:** прогон на живом корпусе (`task verify:archive`)
|
||||
— минута работы и данные, которых нет ни на какой другой машине. У этой
|
||||
проверки краснота не видна никому до следующей задачи, которая до неё
|
||||
дотянется, — говори об этом в границах покрытия.
|
||||
|
||||
## Команды
|
||||
|
||||
- Карта проекта для архитектурного прохода: `task review:context > tmp/review-context.md`
|
||||
- Поднять изменение вживую: `task restart`, логи — `task logs`
|
||||
- Тесты и линт: `task test`, `task lint`
|
||||
- Дорогое, вручную: `task verify:archive` (минута, живые данные)
|
||||
- **Запускать запрещено:** ничего, что пишет в `./data`, в рабочую БД и в боевой
|
||||
каталог архива. Замеры — только на копиях в `./tmp`.
|
||||
|
||||
## Прод и поток
|
||||
|
||||
- **Где:** один статический бинарь в контейнере на домашнем сервере, перед ним
|
||||
обратный прокси с TLS, SQLite на диске. Ни оркестратора, ни реплик, ни дежурной
|
||||
смены.
|
||||
- **Внешние зависимости и как каждая отказывает:** прокси — рвёт соединение на
|
||||
длинном теле; диск — заполняется и тормозит; СУБД — отдаёт «занято» под
|
||||
параллельной записью; приложение-источник на телефоне — молча перестаёт слать.
|
||||
*(В другом проекте здесь были бы качалка, медиасервер, LLM и база метаданных, и
|
||||
каждая — со своим «отвечает медленно», а не только «упала».)*
|
||||
- **Кто заметит отказ:** один пользователь-владелец, в лучшем случае вечером, а
|
||||
скорее не заметит вовсе.
|
||||
- **Характер потока:** телефон шлёт непрерывно и молча; обратной связи у
|
||||
отправителя нет, об отказах он не сообщает, расписание плавает. Тихо
|
||||
сломавшаяся доставка — главный эксплуатационный риск.
|
||||
- **Числа (с провенансом):** нижний слой — порядка 135 тыс. точек в сутки
|
||||
(замер, `docs/local-research.md`); тела доходили до 42 МБ (там же); запись —
|
||||
read-modify-write под конкурентными доставками (`docs/architecture.md`).
|
||||
- **Обратимость:** падение сервиса обратимо — отправитель дошлёт широким
|
||||
проходом. Потеря или порча точки необратима. Поэтому тихая порча весит больше,
|
||||
чем «сервис вернул 500».
|
||||
|
||||
## Модель угроз
|
||||
|
||||
- **Недоверенное:** тело доставки целиком (имена метрик, единицы, формы точек,
|
||||
метки времени, глубина вложенности, размер); заголовки доставки, часть которых
|
||||
участвует в решениях; содержимое архива внешнего экспорта (имена файлов внутри
|
||||
zip мы не формировали); параметры читающего API.
|
||||
- **Из чего строятся пути и ключи:** файл сырого архива —
|
||||
`raw/ГГГГ/ММ/ДД/<ulid>.json.gz`, дата берётся из времени приёма, имя — из
|
||||
генератора идентификаторов; ключ записи — `метрика + слой + начало + конец`,
|
||||
источник в ключ не входит.
|
||||
- **Разграничение:** статические токены в `Authorization: Bearer`, раздельные на
|
||||
запись и на чтение; конфиг под `0600`.
|
||||
- **Что дороже:** данные пользователя дороже токена. Путь, по которому значение
|
||||
доезжает до лога выше `DEBUG`, до ответа с ошибкой или до `testdata` в git, —
|
||||
полноценная находка, а не замечание по гигиене.
|
||||
- **Вне модели:** злоумышленник в локальной сети; вредоносный оператор;
|
||||
компрометация поставщика данных; мультиарендность. Находки этих классов не
|
||||
выводятся — они никогда не будут исправлены.
|
||||
|
||||
## Карта
|
||||
|
||||
- Актуальные спеки: `openspec/specs/<capability>/spec.md`
|
||||
- Дельта-спеки изменения: `openspec/changes/<id>/specs/*/spec.md`
|
||||
- Конвенции прозой: `docs/conventions.md`. Механизировано и потому **не
|
||||
проверяется проходом по конвенциям**: форма логов, `fmt.Print*`/`os.Getenv`/
|
||||
`time.Now` мимо единых точек, сравнение ошибок, сторонние пакеты ошибок —
|
||||
всё это правила в `.golangci.yml`.
|
||||
- Архитектура и решения: `docs/architecture.md`
|
||||
- Журнал проскочивших дефектов: `docs/review-journal.md`
|
||||
- **Единые точки:** идентификаторы — `internal/ident`; время — `store.Now()`;
|
||||
разбор дат входного формата — один парсер в `internal/parse`; маппинг доменной
|
||||
ошибки в код ответа — одна точка в `internal/httpapi`; путь приёма — `ingest`,
|
||||
общий для HTTP и CLI. Инвентарь целиком выгружает `task review:context`.
|
||||
- **Нумерованные артефакты:** миграции — `internal/store/migrations/NNNN_*.sql`,
|
||||
номер монотонный, следующий свободный смотреть там же.
|
||||
- Задачи: `docs/backlog/` *(пайплайн только читает и сообщает исход)*
|
||||
- Реальные пакеты для тестов разбора: `internal/parse/testdata` — там данные
|
||||
пользователя с вычищенными токенами, наружу не копировать
|
||||
- Временное: `./tmp` (не системный `/tmp`)
|
||||
- **Не трогать:** `./data` — боевой архив и БД
|
||||
|
||||
## Типовые узлы
|
||||
|
||||
*Род узла + 3–5 специфичных проверяемых свойств.*
|
||||
|
||||
- **Разбор входного формата** — поведение на усечённом и враждебном входе,
|
||||
границы размера, отсутствие паники, детерминизм, судьба незнакомых полей.
|
||||
- **HTTP-обработчик приёма** — валидация формы конверта до записи, лимит тела и
|
||||
архивная бомба, что попадает в ответ, а что в лог, отсутствие доменной логики
|
||||
в транспорте.
|
||||
- **Обработчик читающего API** — предсказуемость размера ответа, поведение при
|
||||
пустом диапазоне, коды ответа на невозможный запрос.
|
||||
- **Репозиторий** — границы транзакции, конкурентная запись того же ключа,
|
||||
откуда берутся время и id, что возвращается при отсутствии записи,
|
||||
идемпотентность повторной записи.
|
||||
- **Файловое хранилище с ретеншеном** — атомарность записи, поведение при
|
||||
неполной записи и нехватке места, что удаляется и по какому критерию, можно ли
|
||||
удалить лишнее.
|
||||
- **CLI-команда пересборки** — идемпотентность повторного прогона, поведение при
|
||||
отмене на середине, что остаётся после падения, отчёт для человека.
|
||||
- **Клиент внешнего сервиса** — таймаут, протяжка `context`, поведение при
|
||||
«медленно» против «упало», ретраи и их граница.
|
||||
|
||||
## Триггеры
|
||||
|
||||
- `deep`: миграция в `internal/store/migrations/`, новый пакет `internal/*`,
|
||||
изменение контракта читающего API, правило слияния или вывод слоя.
|
||||
- «Видимое снаружи» (то есть `standard`): эндпоинт, форма ответа, код ответа
|
||||
приёма, формат лога.
|
||||
- `reimpl` запускается, когда изменение вводит **новое правило слияния,
|
||||
идентичности или разбора**.
|
||||
|
||||
## Недоступно проверке
|
||||
|
||||
- Поведение внешнего приложения-источника на следующем его обновлении.
|
||||
- Что реально лежит в системе-источнике: сверить можно только ручным экспортом,
|
||||
а он делается раз в 2–3 месяца.
|
||||
- Поведение таблицы под объёмом нескольких лет истории и реальный профиль
|
||||
нагрузки.
|
||||
- Завязка внешних потребителей на текущую форму ответа.
|
||||
- Суждение «этой функциональности не должно существовать».
|
||||
@@ -0,0 +1,85 @@
|
||||
# Калибровка проходов
|
||||
|
||||
Без измерения набор проходов растёт монотонно и вырождается в театр: каждый
|
||||
кажется полезным, потому что иногда что-то говорит. Калибровка отвечает на
|
||||
единственный вопрос — **ловит ли проход дефект своего класса**.
|
||||
|
||||
## Процедура (инъекция дефекта)
|
||||
|
||||
1. Взять **реальный коммит** из истории (`git log --oneline`), лучше
|
||||
архивированный change с непустым диффом.
|
||||
2. Внести в него **один** дефект того класса, который проход обязан ловить по
|
||||
своему charter'у. Дефект должен быть правдоподобным — таким, какой реально
|
||||
пишет модель, а не карикатурой (`panic("TODO")` не считается).
|
||||
3. Прогнать **только этот проход** на подготовленном диффе — **три раза**,
|
||||
каждый в чистом контексте.
|
||||
4. Зафиксировать: нашёл `n/3`, число находок всего, число ложных.
|
||||
5. Вердикт:
|
||||
|
||||
| Результат | Вердикт | Что делаем |
|
||||
|---|---|---|
|
||||
| нашёл 3/3 или 2/3, ложных немного | `keep` | ничего |
|
||||
| нашёл 1/3 или 0/3 | `retune` | правим charter — сужаем вход, убираем чек-лист, добавляем оракул |
|
||||
| `retune` уже был дважды подряд | `drop` | удаляем проход |
|
||||
| находит, но ложных больше трети от всех находок | `retune` | триаж съедает больше, чем экономит проход |
|
||||
|
||||
**`retune` не более двух раз подряд.** Проход, не находящий дефект своего класса
|
||||
в 2 из 3 прогонов после двух правок промпта, — это театр. Удалять, а не
|
||||
бесконечно править формулировки: каждая итерация правки промпта стоит дороже,
|
||||
чем отсутствие прохода.
|
||||
|
||||
**Существующий проход не удаляется без замера.** Сначала калибровка, потом
|
||||
решение — иначе удаляется то, что работало, а остаётся то, что громче. Обратный
|
||||
пример уже был: проход про идиоматичность стоял в списке на удаление как
|
||||
«вкусовщина», а замер показал, что он зарабатывает **экспериментами против
|
||||
поведения библиотеки и драйвера**, — и находка, воспроизведённая числом, отменила
|
||||
решение, принятое по ощущению.
|
||||
|
||||
## Состав проходов принадлежит плагину, а не проекту
|
||||
|
||||
Проходы общие. Проект не может удалить проход — он может **не звать** его, и
|
||||
тогда это идёт строкой «не запускался» в границы покрытия, как любой другой
|
||||
пропуск. Молча сузить состав нельзя: пропуск прохода не отличим от прохода без
|
||||
находок.
|
||||
|
||||
Отсюда два следствия:
|
||||
|
||||
- **правка charter'а — правка для всех проектов.** Прежде чем сужать
|
||||
формулировку под свою боль, проверь, не место ли ей в брифе: предмет проверки
|
||||
живёт там, метод — в charter'е;
|
||||
- **удаление прохода из плагина требует замера на двух проектах**, а не на одном:
|
||||
класс, не всплывший здесь, мог быть единственным работающим там.
|
||||
|
||||
## Пробы дефектов по проходам
|
||||
|
||||
Проба — заготовка инъекции. Список пополняется из журнала проскочивших дефектов
|
||||
(см. [review-journal.md](review-journal.md)): реальный проскочивший дефект —
|
||||
лучшая проба, какая вообще возможна, потому что синтетические смещены в сторону
|
||||
тех, которые уже умеешь придумывать.
|
||||
|
||||
| Проход | Класс дефекта для инъекции | Заготовка пробы |
|
||||
|---|---|---|
|
||||
| `review-gate` | отсутствующая верификация | убрать тест на изменённую ветку, оставить код рабочим |
|
||||
| `review-specs` | поведение вне спеки | добавить незаказанный фолбэк-дефолт на пустом входе |
|
||||
| `review-code` | нарушение прозаической конвенции | увести штатный отказ мимо единой точки трансляции ошибки |
|
||||
| `review-rubric` | нарушенное свойство узла | у клиента внешнего сервиса убрать таймаут и протяжку `context` |
|
||||
| `review-reimpl` | форма решения | размазать решение по трём слоям там, где хватало одной функции |
|
||||
| `review-architecture` | второй способ | завести вторую точку генерации id мимо единой |
|
||||
| `review-adversary` | построенный путь | принять внешний идентификатор без разбора до запроса в хранилище |
|
||||
| `review-ops` | деградация окружения | убрать обработку недоступности внешней зависимости в фоновом цикле |
|
||||
| `review-triage` | шум | подать 20 находок, из них 15 вкусовщина и 3 дубля — проверить потолок и дедуп |
|
||||
|
||||
Метрик сверх этого не заводим. Precision, корреляция между проходами, стоимость
|
||||
прогона в токенах — всё это красиво звучит и никем не считается вручную; набор
|
||||
показателей, который не собирают, создаёт впечатление измеряемости и тем вреден.
|
||||
Работает ровно один механизм: инъекция дефекта и вердикт. Если корреляция двух
|
||||
проходов действительно бросается в глаза — это видно по полю `Найдено проходом`
|
||||
в триажированных отчётах и без отдельной метрики.
|
||||
|
||||
## Когда калибровать
|
||||
|
||||
- при заведении нового прохода — **до** включения в профиль по умолчанию;
|
||||
- при правке charter'а существующего — иначе непонятно, правка помогла или нет;
|
||||
- при появлении записи в журнале проскочивших дефектов — калибруем тот проход,
|
||||
который должен был поймать;
|
||||
- планово — нет. Календарная калибровка ради галочки сама превращается в театр.
|
||||
@@ -0,0 +1,97 @@
|
||||
# Контракт находок
|
||||
|
||||
Единый формат для всех проходов конвейера ревью. Проход, нарушивший контракт,
|
||||
считается сломанным — триаж вправе выбросить его вывод целиком.
|
||||
|
||||
## Форма находки
|
||||
|
||||
```
|
||||
### <краткая формулировка ПОСЛЕДСТВИЯ, не симптома>
|
||||
- Файл: internal/<пакет>/<файл>.go:120-134
|
||||
- Severity: critical | major | minor | nit
|
||||
- Confidence: high | medium | low
|
||||
- Оракул: <падающий тест / команда с выводом / положение гайда / нет>
|
||||
- Последствие: <что произойдёт и при каких условиях>
|
||||
- Предложение: <конкретное изменение>
|
||||
- Найдено проходом: <имя агента>
|
||||
```
|
||||
|
||||
## Правила
|
||||
|
||||
- **Заголовок через последствие.** Не «нет проверки токена», а «читатель без
|
||||
токена выгрузит всю историю». Не «слияние перезаписывает запись», а «повторная
|
||||
доставка сотрёт поля у уже сохранённой записи, и восстановить их нечем».
|
||||
Симптом в заголовке — это заявка на то, что читатель сам достроит последствие;
|
||||
он не достроит, он просто починит симптом.
|
||||
- **`critical` без оракула или построенного пути не существует.** Оракул — это
|
||||
падающий тест, вывод выполненной команды или поимённое положение гайда. Не
|
||||
«вероятно, здесь гонка», а прогон детектора гонок с его выводом.
|
||||
- **`confidence: low` — это «так обычно пишут».** Такие находки допустимы, но не
|
||||
поднимаются выше `minor`. Частотность конструкции в публичном коде — не
|
||||
аргумент.
|
||||
- **Находка без поля «Последствие» не выводится вовсе.** Пустое «Последствие:
|
||||
ухудшает читаемость» равносильно отсутствию поля.
|
||||
- **`nit` допустим только при нарушении записанной конвенции** — со ссылкой на
|
||||
файл и раздел конвенций проекта (путь — из раздела `## Карта` брифа) либо на
|
||||
правило линтера. Если правило механизируемо, но не механизировано — это не
|
||||
находка ревью, это `Promote candidate` (см. [promote.md](promote.md)).
|
||||
- **`critical` по основанию «нарушен инвариант проекта» требует брифа.** Ссылка
|
||||
идёт на пункт раздела `## Инварианты` дословно. Без брифа такое основание
|
||||
недоступно — см. [project-brief.md](project-brief.md), деградированный режим.
|
||||
- **Расхождение — не дефект, пока не названо последствие.** Особенно для прохода
|
||||
независимой реализации: «я бы сделал иначе» без последствия не выводится.
|
||||
|
||||
## Шкала severity
|
||||
|
||||
| Severity | Что это | Пример |
|
||||
|---|---|---|
|
||||
| `critical` | нарушение инварианта проекта, потеря или порча данных, утечка секрета, построенный путь к отказу | запись потеряна при слиянии; тело пользовательской выгрузки в поле лога |
|
||||
| `major` | сломанное требование дельта-спеки, необрабатываемый отказ штатного сценария, флаки-тест, поведение вне спеки, меняющее исход | приём отвечает 200, не записав тело: доставка считается принятой, а данных нет |
|
||||
| `minor` | отступление от конвенции с реальной ценой, отсутствующая наблюдаемость, дублирование, которое разойдётся | ни одного чекпоинта на пути разбора: молчащая автоматизация неотличима от пустого потока |
|
||||
| `nit` | нарушение записанной конвенции без последствий за пределами чтения | `msg` с интерполяцией вместо константы |
|
||||
|
||||
Шкала привязана к обратимости, а не к громкости: класс «необратимо и молча»
|
||||
всегда весит больше класса «шумно и лечится повтором». Что здесь необратимо,
|
||||
говорит раздел `## Прод и поток` брифа.
|
||||
|
||||
## Блок границ покрытия
|
||||
|
||||
Каждый проход завершает вывод этим блоком. Он не сокращается и не заменяется
|
||||
фразой «всё проверено».
|
||||
|
||||
```
|
||||
## Coverage of this pass
|
||||
- проверено: <что реально прочитано/запущено, с путями и командами>
|
||||
- не проверялось и почему: <бюджет, недоступный инструмент, вне входа>
|
||||
- принципиально недоступно этому проходу: <из charter'а агента>
|
||||
```
|
||||
|
||||
## Финальный отчёт триажа
|
||||
|
||||
Секции строго в этом порядке, потолок — 7 пунктов в первых двух:
|
||||
|
||||
1. `Блокирует мердж` (≤3, каждая с оракулом);
|
||||
2. `Стоит исправить сейчас` (≤4);
|
||||
3. `Гипотезы без доказательства` — что понижено и почему;
|
||||
4. `Promote candidates` — кандидаты в конвенцию или правило линтера;
|
||||
5. `Границы покрытия` — сводная, обязательная.
|
||||
|
||||
Перед секциями — сводка для человека: профиль и режим прогона, состояние гейта,
|
||||
**перечень запущенных проходов поимённо с исходом каждого**, сколько находок
|
||||
пришло на вход и сколько осталось. Перечень обязателен: пропуск прохода не
|
||||
отличим от прохода без находок, и назвать его больше некому.
|
||||
|
||||
Каждая находка в секциях 1–2 несёт дополнительное поле:
|
||||
|
||||
```
|
||||
- Действие: инлайн | развилка
|
||||
```
|
||||
|
||||
`инлайн` — оркестратор чинит сам, не спрашивая и не логируя. `развилка` — цена
|
||||
исправления сопоставима с переработкой, либо выбор меняет scope, либо решение
|
||||
трогает инвариант: уезжает вопросом с вариантами и ценой каждого туда, где
|
||||
проект держит вопросы, а работа продолжается на остатке.
|
||||
|
||||
Потребитель отчёта — оркестратор, который **реализует прочитанное**. Поэтому
|
||||
потолок в 7 пунктов — не забота о внимании читателя, а защита кодовой базы от
|
||||
правок, которых никто не заказывал.
|
||||
@@ -0,0 +1,208 @@
|
||||
# Бриф проекта — контракт
|
||||
|
||||
Конвейер общий, а находки — проектные. Проход, не знающий, что в этом проекте
|
||||
нельзя нарушать, чем краснеет гейт и сколько данных реально проходит через узел,
|
||||
выдаёт правдоподобные общие места: их дорого опровергать и нечем подтверждать.
|
||||
|
||||
Поэтому проектная специфика живёт **в одном файле проекта**, а не в charter'ах
|
||||
агентов. Charter описывает **метод** прохода (что он делает и почему именно так),
|
||||
бриф — **предмет** (что здесь дорого, чем это меряется, где лежит).
|
||||
|
||||
Шаблон для заполнения — [brief-template.md](brief-template.md).
|
||||
|
||||
## Где лежит и как находится
|
||||
|
||||
Порядок разрешения пути, одинаковый для скилла и для каждого агента:
|
||||
|
||||
1. путь, названный в задании конвейера (`бриф: <путь>`) — конвейер обязан его
|
||||
передавать каждому проходу;
|
||||
2. `docs/review-brief.md`;
|
||||
3. `.claude/review-brief.md`;
|
||||
4. брифа нет — **деградированный режим** (см. ниже).
|
||||
|
||||
Разрешает путь конвейер, один раз, и дальше передаёт готовым. Агент, получивший
|
||||
путь в задании, сам ничего не ищет.
|
||||
|
||||
## Деградированный режим
|
||||
|
||||
Брифа нет — проходы работают, но их recall падает предсказуемым образом, и это
|
||||
**обязано быть названо**, а не сглажено. Каждый проход без брифа:
|
||||
|
||||
- не присваивает `critical` по основанию «нарушен инвариант проекта» — инвариантов
|
||||
он не знает;
|
||||
- не оперирует числами объёма и потока — формулирует условиями;
|
||||
- пишет в границы покрытия строку: «брифа проекта нет: инварианты, модель угроз и
|
||||
профиль нагрузки неизвестны; находки этих классов не искались».
|
||||
|
||||
Триаж сводит эти строки в одну и выносит в финальный отчёт. Отсутствие брифа —
|
||||
дыра покрытия, а не нейтральное умолчание.
|
||||
|
||||
## Форма
|
||||
|
||||
Markdown. Разделы — заголовки второго уровня с **точными именами** из списка
|
||||
ниже: по ним агенты находят свой кусок. Порядок разделов свободен, лишние разделы
|
||||
допустимы и игнорируются, отсутствующий раздел работает как деградированный режим
|
||||
для тех проходов, которые его читают.
|
||||
|
||||
## Разделы
|
||||
|
||||
### `## Проект` — обязателен
|
||||
|
||||
Абзац: что система делает — и, что важнее, **чего она не делает**. Граница домена
|
||||
нужна архитектурному проходу как критерий: «хранилище, а не аналитика», «единое
|
||||
ядро, тонкие транспорты», «связующий сервис, а не медиатека». Без неё перенос
|
||||
понятия через границу выглядит просто новым кодом.
|
||||
|
||||
Читают: `architecture`, `rubric`, `reimpl`, `specs`.
|
||||
|
||||
### `## Инварианты` — обязателен
|
||||
|
||||
Список того, что нарушать нельзя. Каждый пункт — три вещи:
|
||||
|
||||
- формулировка **как проверяемое свойство**, а не как лозунг: «точка сохраняется
|
||||
дословно: незнакомое поле не отбрасывается», а не «бережно относимся к данным»;
|
||||
- **последствие нарушения** и его обратимость;
|
||||
- **severity по умолчанию** — если это не `critical`, скажи прямо.
|
||||
|
||||
Это единственный раздел, который **цитируется формулировкой**, а не пересказывается
|
||||
ссылкой: по нему присваивается severity, и пересказ здесь стоит неверной оценки.
|
||||
|
||||
Читают: `specs` (режим 1 — отражены ли задетые инварианты в спеке), `code`,
|
||||
`adversary`, `architecture`, `triage` (ранжирование и разметка «развилка»).
|
||||
|
||||
### `## Гейт` — обязателен
|
||||
|
||||
- **Команда** целиком, включая передачу базы диффа (`task gate BASE=<база>`), и
|
||||
как база определяется по умолчанию.
|
||||
- **Где логи** отдельных шагов.
|
||||
- **Что означает каждый исход**: чем гейт краснеет, что предупреждает, что
|
||||
пропускается по составу диффа.
|
||||
- **Шаги, которые красят безусловно, и почему.** Это самая ценная часть раздела:
|
||||
«данные под контролем версий», «структура конфига изменилась, а образец нет»,
|
||||
«миграции не накатываются с нуля» — проход обязан знать, что здесь не бывает
|
||||
«ну это мелочь».
|
||||
- **Чего в гейте намеренно нет** и почему — прогон на живом корпусе, длинный
|
||||
интеграционный тест. У проверки, которую гейт не гоняет, краснота никому не
|
||||
видна; это уезжает в границы покрытия.
|
||||
|
||||
Читает: `gate`.
|
||||
|
||||
### `## Команды` — обязателен
|
||||
|
||||
Что проход имеет право выполнить и чем:
|
||||
|
||||
- **карта проекта для архитектуры** — команда, отдающая пакеты, граф зависимостей
|
||||
и инвентарь концепций (`task review:context`);
|
||||
- **запуск изменения вживую** — чем поднять и как проверить поведение (нужно
|
||||
пайплайну задачи на шаге поведенческой верификации);
|
||||
- **тесты, линт, дополнительные проверки** — и какие из них дорогие;
|
||||
- **что запускать запрещено**: рабочая БД, боевой каталог данных, внешние
|
||||
сервисы. Формулируй запретом с путями, а не «будь осторожен».
|
||||
|
||||
Читают: `architecture`, `gate`, `ops`, `triage`, пайплайн задачи.
|
||||
|
||||
### `## Прод и поток` — обязателен
|
||||
|
||||
Материал для эксплуатационного прохода, и он же — половина ранжирования триажа:
|
||||
|
||||
- где это работает: машина, окружение, что рядом, кто перезапускает;
|
||||
- **внешние зависимости поимённо** и чем каждая отказывает: не только «падает», но
|
||||
и «отвечает медленно», «молчит», «отдаёт мусор». Эксплуатационный проход
|
||||
спрашивает про каждую отдельно, и список зависимостей он взять больше неоткуда;
|
||||
- **кто заметит отказ и когда** — есть ли вообще наблюдатель;
|
||||
- **характер потока**: непрерывный и молчаливый, по запросу, по расписанию; есть
|
||||
ли обратная связь у отправителя;
|
||||
- **измеренные числа с провенансом**: объёмы, размеры тел, темп, размеры таблиц.
|
||||
Число без источника проход обязан превратить в условие — так и напиши, откуда
|
||||
оно;
|
||||
- **что обратимо, а что нет.** Падение, которое лечится повтором, и тихая потеря,
|
||||
которую нечем восстановить, — разные классы, и порядок находок в отчёте зависит
|
||||
от того, какой из них здесь главный.
|
||||
|
||||
Читают: `ops`, `adversary`, `triage`, `reimpl`.
|
||||
|
||||
### `## Модель угроз` — обязателен
|
||||
|
||||
- **что недоверенное** и каким каналом приходит: тело запроса, файл, аргумент
|
||||
команды, ответ внешней системы, содержимое архива;
|
||||
- **из чего строятся пути и ключи** — раскладка файлов на диске, состав
|
||||
координатного ключа записи, имя каталога. Враждебный проход выводит запись за
|
||||
пределы песочницы именно отсюда, и без этого пункта он ищет вслепую;
|
||||
- **что разграничивает доступ** — токены, контуры, права файлов;
|
||||
- **что чувствительнее чего**: если данные дороже секретов, скажи это прямо;
|
||||
- **что вне модели** — перечислить явно. Пустой пункт «вне модели» означает, что
|
||||
враждебный проход выдумает угрозу сам, и находка никогда не будет исправлена.
|
||||
|
||||
Читает: `adversary`.
|
||||
|
||||
### `## Карта` — обязателен
|
||||
|
||||
Где что лежит, путями:
|
||||
|
||||
- актуальные спеки и дельта-спеки предлагаемого изменения;
|
||||
- конвенции прозой — и **какая их часть уже механизирована** правилом (её проход
|
||||
по конвенциям не проверяет);
|
||||
- архитектура и решения; журнал проскочивших дефектов;
|
||||
- **единые точки проекта** — где генерируются идентификаторы и время, где
|
||||
единственный парсер входного формата, где маппинг доменной ошибки в код ответа,
|
||||
где общий путь приёма. Это материал для вопроса «не появился ли второй способ»;
|
||||
если команда карты проекта их выгружает, здесь хватит ссылки на неё;
|
||||
- **нумерованные артефакты** — путь миграций и правило нумерации: батч раздаёт
|
||||
номера заранее, чтобы параллельные задачи не столкнулись файлами;
|
||||
- где ведутся задачи (пайплайн только читает и сообщает исход);
|
||||
- `testdata` и что в них лежит; куда можно писать временное;
|
||||
- **каталоги, которые не трогают вовсе**.
|
||||
|
||||
Читают: все проходы.
|
||||
|
||||
### `## Типовые узлы` — необязателен, но без него рубрика беднеет
|
||||
|
||||
Роды узлов, из которых состоит проект (парсер входного формата, HTTP-обработчик,
|
||||
репозиторий, воркер, клиент внешнего API, CLI-команда, файловое хранилище), и по
|
||||
3–5 **специфичных для рода** проверяемых свойств к каждому.
|
||||
|
||||
Читает: `rubric`. Без раздела рубрика выродится в общие слова и повторит
|
||||
конвенции — то есть станет applicative-проходом, ради отсутствия которого она и
|
||||
существует.
|
||||
|
||||
### `## Триггеры` — необязателен
|
||||
|
||||
Проектная конкретизация правила выбора профиля: какие пути и контракты означают
|
||||
`deep`; что считается «поведением, видимым снаружи»; при каком изменении
|
||||
запускается `reimpl`. Умолчания записаны в самом скилле и работают без этого
|
||||
раздела — но общее правило говорит «изменение публичного контракта», а какой
|
||||
контракт публичный, знает только проект.
|
||||
|
||||
Читают: скилл конвейера, пайплайн задачи.
|
||||
|
||||
### `## Недоступно проверке` — обязателен
|
||||
|
||||
Что не проверит ни один проход и почему: поведение внешних систем и их будущих
|
||||
версий, реальный профиль нагрузки, соответствие сохранённого действительности,
|
||||
завязка внешних потребителей на текущую форму, суждение «а нужна ли эта
|
||||
функциональность».
|
||||
|
||||
Этот раздел целиком уезжает в границы покрытия финального отчёта. Он существует
|
||||
ровно затем, чтобы «критичных проблем не обнаружено» никогда не читалось как
|
||||
«проверено всё».
|
||||
|
||||
Читает: `triage`; каждый проход — свою часть.
|
||||
|
||||
## Правила ведения
|
||||
|
||||
- **Бриф не пересказывает документацию проекта.** Факт, записанный в `CLAUDE.md`
|
||||
или в архитектуре, попадает сюда ссылкой и одной строкой сути. Два дома для
|
||||
одного факта разъезжаются, и разошедшийся бриф хуже отсутствующего: он выглядит
|
||||
актуальным. Исключение одно — раздел инвариантов, он цитируется.
|
||||
- **Числа — с провенансом.** «Тела доходили до 42 МБ (замер, ссылка)». Число без
|
||||
источника проход не имеет права использовать как утверждение.
|
||||
- **Что вне модели — называется явно.** Это относится и к угрозам, и к нагрузке,
|
||||
и к классам находок, которые проект сознательно перестал проверять.
|
||||
- **Бриф подчиняется промоуту.** Свойство, ставшее правилом линтера, из брифа
|
||||
вычёркивается — как и из конвенций, и из charter'ов (см.
|
||||
[promote.md](promote.md), шаг 3).
|
||||
- **Когда обновлять:** сменился гейт; появился новый контур, зависимость или
|
||||
источник входа; журнал ревью получил запись вида «проход не мог этого знать».
|
||||
Планового пересмотра нет.
|
||||
- **Бриф ведёт проект**, а не плагин. Плагин его только читает и никогда не
|
||||
правит.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Промоут: находка → конвенция → правило → удаление
|
||||
|
||||
Механизм храповика. Без него конвейер выдаёт одни и те же находки бесконечно, а
|
||||
конвенции не растут — то есть внимание тратится повторно на уже решённое.
|
||||
|
||||
Роли уровней:
|
||||
|
||||
- **generative-проходы** — механизм *открытия* неявного (дорого, шумно, но
|
||||
только они достают то, чего нет в списках);
|
||||
- **конвенции** — дешёвая *регрессионная сетка* на уже открытое;
|
||||
- **правила линтера** — то же с детерминированным оракулом и нулевой ценой
|
||||
внимания.
|
||||
|
||||
## Шаг 1. Находка → конвенция
|
||||
|
||||
Условия: находка **принята** при ревью (не отвергнута, не понижена в гипотезу) и
|
||||
**не специфична для одного места**.
|
||||
|
||||
- Формулируется как **проверяемое свойство**, а не как совет: «уровень доменного
|
||||
отказа выбирает единственный логирующий чекпоинт», а не «внимательнее с
|
||||
уровнями логов».
|
||||
- Записывается источник — какой проход нашёл. Это единственные данные для
|
||||
калибровки: проход, чьи находки регулярно доезжают до конвенции, оправдан;
|
||||
проход, чьи находки не доезжают никогда, — кандидат на `drop` (см.
|
||||
[calibration.md](calibration.md)).
|
||||
- Место записи — файл конвенций проекта (путь — в разделе `## Карта` брифа). Если
|
||||
тема относится к поведению системы, а не к тому, как мы пишем код, — это не
|
||||
конвенция, а требование: заводится дельта-спека обычным путём.
|
||||
|
||||
Промоут идёт **тем же путём, что change → spec**: правка попадает в тот же
|
||||
коммит, что и исправление кода, с пометкой в сообщении — история промоутов
|
||||
остаётся видна в `git log` по файлу конвенций.
|
||||
|
||||
## Шаг 2. Конвенция → правило
|
||||
|
||||
Как только свойство выражается детерминированно, оно переезжает в инструмент.
|
||||
Порядок предпочтения — от дешёвого к дорогому:
|
||||
|
||||
1. **готовое правило существующего линтера** — включить в конфиг;
|
||||
2. **запрет идентификатора или импорта** правилом-«запретителем» с собственным
|
||||
паттерном;
|
||||
3. **правило с настройкой формы** — когда важно не имя, а конструкция;
|
||||
4. **тест-сканер исходников** — когда правило про структуру проекта или про
|
||||
схему: направление зависимостей, форма миграций, матчинг ошибки по тексту,
|
||||
бизнес-логика в транспорте;
|
||||
5. **собственный анализатор** — последний рубеж, заводим только если 1–4 не
|
||||
выражают правило.
|
||||
|
||||
Правило обязано быть **зелёным на текущем коде в момент включения**: иначе
|
||||
хук блокирует любой коммит, и правило снимут первым же раздражённым движением.
|
||||
Приводить код в соответствие — часть шага 2, отдельным коммитом.
|
||||
|
||||
## Шаг 3. Удаление из конвенций, из брифа и из промптов
|
||||
|
||||
**Шаг, который пропускают чаще всего, и единственный, ради которого затевались
|
||||
первые два.**
|
||||
|
||||
Как только правило работает:
|
||||
|
||||
- из файла конвенций убирается формулировка правила; остаётся, если нужно, одна
|
||||
строка «проверяется линтером `<имя>`» — но только там, где без неё раздел
|
||||
теряет связность;
|
||||
- **из брифа проекта** убирается соответствующий пункт, а в разделе `## Карта`
|
||||
правило переезжает в перечень «механизировано и потому проходом по конвенциям
|
||||
не проверяется»;
|
||||
- из контекста инструмента спек убирается дубль, если он там был.
|
||||
|
||||
Charter'ы проходов при этом **не правятся**: они общие и живут в плагине, а
|
||||
предмет проверки приходит из брифа. Именно поэтому шаг 3 стал дешевле, чем был:
|
||||
вычеркнуть строку в одном файле проекта, а не в девяти промптах.
|
||||
|
||||
Практический критерий: **в прозаических конвенциях остаётся только то, что
|
||||
принципиально не выражается правилом.** Файл конвенций на несколько сотен строк
|
||||
размазывает внимание модели по тривиальному — она добросовестно проверит
|
||||
именование полей лога и не дойдёт до формы решения. Каждая строка конвенций,
|
||||
которую можно было бы проверить машиной, оплачивается непойманным дефектом
|
||||
где-то ещё.
|
||||
|
||||
## Обратное движение
|
||||
|
||||
Правило, которое даёт ложные срабатывания чаще, чем ловит (порядка трети от
|
||||
общего числа), снимается и возвращается в прозу — или удаляется совсем, если
|
||||
свойство перестало быть важным. Снятие фиксируется там же, где включалось, с
|
||||
одной строкой «почему».
|
||||
|
||||
## Что промоуту не подлежит
|
||||
|
||||
- Находка, специфичная для одного места (её лечит комментарий в коде).
|
||||
- Вкусовщина: не меняет поведения, не влияет на стоимость следующего изменения,
|
||||
не нарушает записанного. Такое выбрасывается на триаже и не хранится.
|
||||
- Свойство, требующее знания рантайма (профиль нагрузки, история инцидентов) —
|
||||
его нельзя проверить ни промптом, ни линтером; место такому — в журнале ревью
|
||||
как «признано неавтоматизируемым» (см. [review-journal.md](review-journal.md)).
|
||||
@@ -0,0 +1,62 @@
|
||||
# Журнал проскочивших дефектов
|
||||
|
||||
Артефакт проекта, а не плагина: файл живёт в репозитории (путь — в разделе
|
||||
`## Карта` брифа, по умолчанию `docs/review-journal.md`). Здесь описано, зачем он
|
||||
и какой формы, потому что без него конвейер не учится: находки закрываются,
|
||||
причины непоймания теряются, и один и тот же класс проскакивает второй раз.
|
||||
|
||||
## Что туда попадает
|
||||
|
||||
Дефект, который **прошёл ревью и всплыл позже**. Записывается **сразу**, а не
|
||||
ретроспективно: со временем теряется не сам факт, а причина непоймания —
|
||||
единственное, ради чего журнал существует.
|
||||
|
||||
Реализованные задачи, находки ревью и принятые решения сюда не пишутся: у них
|
||||
есть коммит, спека и задача. Здесь только промахи конвейера.
|
||||
|
||||
Отдельно сюда попадают **решения о составе прогонов**: перестали звать проход,
|
||||
понизили профиль правилом, сузили класс проверяемого. Не потому, что это промах,
|
||||
а потому, что здесь лежит цена: если что-то теперь проскочит, первый вопрос —
|
||||
«не тот ли это класс, который мы перестали проверять».
|
||||
|
||||
## Форма записи
|
||||
|
||||
```
|
||||
## ГГГГ-ММ-ДД — <краткое последствие>
|
||||
|
||||
- **Где:** путь:строка либо «конвейер, а не код»
|
||||
- **Симптом:** как обнаружилось, кем и когда
|
||||
- **Причина:** что на самом деле было не так
|
||||
- **Почему не поймали:** какой проход обязан был найти и что ему помешало
|
||||
- **Что меняем:** правило прохода, шаг гейта, конвенция, пункт брифа — либо
|
||||
«ничего, цена поимки выше цены дефекта»
|
||||
```
|
||||
|
||||
Последний пункт важнее остальных. Вывод «ничего не меняем» — законный исход: не
|
||||
всякий дефект стоит того, чтобы усложнять ради него ревью каждой задачи.
|
||||
|
||||
## Куда ведёт запись
|
||||
|
||||
Три адреса, и выбор между ними — половина ценности журнала:
|
||||
|
||||
- **в бриф проекта** — если проход не мог знать факта: объём, характер потока,
|
||||
что здесь необратимо, какой шаг гейта красит безусловно. Самый частый адрес и
|
||||
самый дешёвый.
|
||||
- **в конвенции или в правило линтера** — если свойство выражается
|
||||
детерминированно (процедура — [promote.md](promote.md)).
|
||||
- **в charter прохода** — если сломан **метод**, а не знание. Правка charter'а
|
||||
меняет поведение во всех проектах, поэтому она требует калибровки
|
||||
([calibration.md](calibration.md)) и обоснования, почему это не лечится
|
||||
брифом.
|
||||
|
||||
## Что журнал даёт конвейеру
|
||||
|
||||
- **пробы для калибровки** — реальный проскочивший дефект сильнее синтетического:
|
||||
синтетические смещены в сторону тех, которые уже умеешь придумывать;
|
||||
- **основание для правил конвейера** — требование называть запущенные проходы
|
||||
поимённо, отказ от чисел, производных от размера корпуса, и правило
|
||||
последовательного прогона выведены из конкретных записей, а не из общих
|
||||
соображений;
|
||||
- **счётчик обратимости решений** — сузили состав проходов и через месяц поймали
|
||||
дефект ровно того класса, который перестали проверять: решение пересматривается
|
||||
фактом, а не спором.
|
||||
Reference in New Issue
Block a user