Пара плагинов с намеренно проведённой границей: 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 пишет наполовину) — они чинятся следующими коммитами. Сохранено как база, от которой видно правки.
15 KiB
Шаблон брифа проекта
Скопируй в docs/review-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 месяца.
- Поведение таблицы под объёмом нескольких лет истории и реальный профиль нагрузки.
- Завязка внешних потребителей на текущую форму ответа.
- Суждение «этой функциональности не должно существовать».