Сабагент в роли продакт-менеджера оценил покрытие жизненного цикла личного проекта скиллами и агентами av-dev-pm. Скоуп сужен по ходу разбора: деплой и разбор инцидентов делаются вручную, скиллов под них не заводим — три находки из восьми сняты этим сразу. Шаг 2 сессии требовал чисел, которых процесс отказался собирать решением. cadence.md делал обязанностью пересмотр «ориентира по размеру спринта, прироста беклога на закрытую задачу, времени на задачу» и «сколько заняли задачи против ожидания». Данных нет: у записи нет дат заведения, взятия и закрытия, close удаляет файл, sprint close очищает SPRINT.md. Хуже, «против ожидания» и «время на задачу» требуют оценки и тайм-бокса, а session/SKILL.md в «Почему не Scrum» их прямо не берёт — пункт противоречил решению через файл от себя. Числа не пересматривались ни разу, поэтому выкинуты, а не подперты учётом дат. Осталось качественное; рядом записано, что замеров нет намеренно, иначе следующий читатель заведёт их обратно как недостающие. Шаг 3 пункт 9 переименован из «переоценки по измеренному» в «по пройденному». doc-consistency переехал с каждого синка на сессию, к doc-code-drift. Агент на opus звался шагом 9 пайплайна, то есть 5-8 opus-проходов за спринт по документам, меняющимся на несколько абзацев. Довод сильнее денег: расхождение между двумя документами по определению требует двух, а на большинстве задач синк правит один. И пачка, отбираемая работой, не видит того, чего работа не касалась, — а расхождение живёт ровно там. Это снимает открытый вопрос REMAINING про охват парного статуса ADR. Цена — потеря привязки находки к задаче, принято сознательно. Отмена цели получила порядок, но не флаг. close запрещал закрыть цель с живыми задачами и не говорил, что с ними делать. Теперь: сперва задачи поштучно (close --reason своей причиной либо edit --goal на другую), потом цель в REJECTED.md, а не в Готово. Флаг --cascade отвергнут: поштучный разбор — не церемония, а единственный момент, когда видно, что переживёт цель. Место процедуры — переоценка на сессии, отмена цели и есть разбор её задач. У брошенного спринта появился второй законный исход. --dissolve везде был привязан к блокеру, и вернувшийся к месячному набору не имел законного хода: двигать нельзя, распускать не по чему. Теперь роспуск объясняется блокером или тем, что набор протух. Порога в неделях нет — тот же класс, что выкинутые числа: счётчик простоя пришлось бы вести руками. Признак не срок, а что набор перестал быть твоим. Плюс точка входа «вернулся, а спринт открыт» и триггер в description скилла. Журнал канона прогоняется как есть, схлопывать 3 и 4 не стали. Взамен появилась проверка исхода: шагом 6 adopt и шагом 6 upgrade зовутся оба судьи документов. Это ответ на открытый вопрос «как проверять, что канон не разошёлся с проектами после upgrade»: check сверяет число в .pm.json с версией скрипта и про существо записи не знает ничего, а записи применяются руками. Износ обязательных «границ покрытия» не правится: это гипотеза, а не находка. Записана наблюдением к первой обкатке. Предложение агента поднять обкатку выше калибровки снято — TODO уже так устроен, агент спутал «главный риск» с «первое в очереди»; в REMAINING добавлена оговорка против того же прочтения. Тема 31 в DECISIONS.md, следствия 117-123. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2208 lines
199 KiB
Markdown
2208 lines
199 KiB
Markdown
# Решения по устройству процесса
|
||
|
||
Журнал согласований: что решено, почему и что из этого следует. Пишется по ходу
|
||
разбора тем, одна тема — один раздел. Причина обязательна: через месяц она
|
||
забывается раньше факта.
|
||
|
||
Незакрытые остатки прошлого захода — [REMAINING.md](REMAINING.md).
|
||
|
||
## Требования, зафиксированные по ходу
|
||
|
||
Не решения — вход, который обязан быть удовлетворён и разбирается в названной
|
||
теме.
|
||
|
||
**Т1. Адаптация и проверка проекта под канон — обязательный скилл.** Нужно уметь
|
||
прийти в **любой** старый проект и перевести его на текущие рельсы. Канон при
|
||
этом сам будет меняться, поэтому уже приведённые проекты тоже должны повышаться
|
||
до новых версий. *Разбирается в теме 5 (старт и жизненный цикл проекта).*
|
||
|
||
Следствия, которые из этого уже видны:
|
||
|
||
- **У канона обязана быть версия, а у проекта — отметка, под какую он
|
||
приведён.** Иначе «соответствует канону» не имеет определённого ответа:
|
||
сравнение идёт с тем, что модель помнит сейчас, а это и есть дрейф.
|
||
- **Журнал изменений канона — как миграции.** Каждое повышение версии несёт
|
||
запись «что добавилось, что переехало, что удалено, что сделать проекту». Без
|
||
него адаптация переизобретается на каждом проекте.
|
||
- **Отметка версии машиночитаема.** `.docs.json` отвергнут как *указатель
|
||
путей* (решение F), но отметка версии — другое: её читает скрипт, и разбирать
|
||
прозу `CLAUDE.md` для этого не нужно. Прецедент — `.tasks.json`.
|
||
- **Операций три:** `check` (соответствие текущему канону), `adopt` (перевод
|
||
чужой раскладки), `upgrade` (повышение с версии N до M по журналу). Первая и
|
||
третья — одно сравнение с разными исходами.
|
||
- **Механизируемое и суждение не смешивать.** Скрипт проверяет пути, лишние
|
||
файлы, битые ссылки, версию. Агент судит о смысловых дублях (`docs/specs/
|
||
recognition.md` против capability `recognition`) и об оставшемся поведении в
|
||
`architecture.md`. Скрипт, отчитавшийся «канон соблюдён» на проекте с тремя
|
||
лишними файлами, хуже отсутствующего.
|
||
- **Границы плагинов:** `docs/tasks/` — часть канона документов, но владеет им
|
||
`av-dev-tasks` со своим `tasks.py adopt`. Два плагина сходятся на одном
|
||
каталоге. *Тема 7.*
|
||
|
||
## 1. Статус OpenSpec (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
OpenSpec несёт оба проекта: healthlog — 5 capability, 3530 строк спек, 9
|
||
архивных change за две недели; jellybit — 11 capability, 3895 строк, 43 архивных
|
||
change. При этом в трёх местах плагина написана ветка «проект без OpenSpec»
|
||
(`task-pipeline` предпосылки, `review-pipeline` предпосылки, `task-batch`
|
||
предпосылки) — и **не исполнялась ни разу**.
|
||
|
||
Проектные факты живут в пяти домах: `CLAUDE.md`, `docs/architecture.md`,
|
||
`openspec/specs/`, `openspec/config.yaml` → `context`, и планируется шестой —
|
||
`docs/review-brief.md`.
|
||
|
||
Расхождение измерено: у healthlog раздел «Хранилище» в `docs/architecture.md` —
|
||
950 строк (377–1328) против `openspec/specs/storage/spec.md` на 1337 строк. Два
|
||
описания одного поведения, никем не сверяемые. У jellybit того же нет:
|
||
`docs/specs/architecture.md` — 300 строк обзора, детали в 11 спеках. **Проект с
|
||
43 изменениями держит архитектуру втрое короче проекта с 9.**
|
||
|
||
### Решено
|
||
|
||
**A. OpenSpec — жёсткая предпосылка `av-dev-pipeline`.** Ветки деградации
|
||
удаляются, вместо них объявленная зависимость и проверка на старте. Зависимость
|
||
на уровне **плагина, а не процесса**: `av-dev-tasks`, `av-dev-git` и будущий
|
||
плагин документов от OpenSpec не зависят и работают на python/ansible-проектах.
|
||
|
||
*Причина:* непроверенная ветка деградации хуже честной строки «требуется
|
||
OpenSpec» — она даёт ложную уверенность, что проект без спек поедет.
|
||
|
||
**B. Нормативный дом поведения — `openspec/specs/`.** `architecture.md`
|
||
переопределяется как **обзор**: принципы, компоненты со ссылками на capability,
|
||
внешние форматы данных, раскладка, деплой, открытые вопросы. Поведения он не
|
||
описывает.
|
||
|
||
*Причина:* `opsx:archive` вливает дельты именно в `openspec/specs/` — любой
|
||
другой нормативный дом обязан синхронизироваться руками и разойдётся. Форма
|
||
jellybit это уже подтвердила на 43 изменениях.
|
||
|
||
**C. `openspec/config.yaml` → `context` держит только нужды генерации.** Язык,
|
||
правила именования capability, придирки валидатора RFC 2119 — и ссылки. Правило
|
||
ревью, пересказ конвенций и инварианты оттуда вычищаются: у них есть свои дома.
|
||
|
||
*Причина:* блок «Ревью (процесс, не артефакт)» в обоих `config.yaml` дословно
|
||
повторяет шаги 4 и 7 `task-pipeline`. Это второй дом для правила, которым владеет
|
||
плагин, и он разойдётся на первой же правке.
|
||
|
||
### Что из этого следует
|
||
|
||
Из A:
|
||
|
||
1. Три места с веткой деградации переписываются на объявленную предпосылку плюс
|
||
проверку на старте (есть `openspec/`, разрешаются `opsx:*`) и внятный отказ:
|
||
`task-pipeline` предпосылки, `review-pipeline` предпосылки, `task-batch`
|
||
предпосылки.
|
||
2. Описание `av-dev-pipeline` в маркетплейсе получает строку «требует OpenSpec».
|
||
3. **Факт для темы «объединять ли tasks и pipeline»:** объединение потянуло бы
|
||
зависимость от OpenSpec на управление задачами, которой там сейчас нет.
|
||
|
||
Из B:
|
||
|
||
4. Правило «поведение — в спеку, устройство и границы — в архитектуру» становится
|
||
контрактом плагина документов и правилом шага «синк документации» в
|
||
`task-pipeline`.
|
||
5. healthlog чистится **не разом**: раздел вычищается той задачей, которая его
|
||
касается. Нужен способ не потерять остаток — иначе 950 строк «Хранилища»
|
||
останутся навсегда.
|
||
6. **Дыра, которую решение открывает:** «почему» после архивации. Сегодня
|
||
`CLAUDE.md` healthlog велит писать причину решения в `architecture.md` — а мы
|
||
её оттуда выселяем. Спеки нормативны и «почему» не держат; `design.md` живёт
|
||
внутри change и уезжает в архив. Либо ADR (как у jellybit), либо явное
|
||
правило «почему живёт в архивных change». **Первый вопрос следующей темы.**
|
||
|
||
Из C:
|
||
|
||
7. `av-dev-pipeline` даёт образец `openspec/config.yaml` отдельным reference —
|
||
он владеет связью с OpenSpec. Заполняется при старте проекта и при `adopt`.
|
||
8. У обоих проектов из `config.yaml` вычищается блок «Ревью (процесс, не
|
||
артефакт)», пересказ конвенций и инвариантов.
|
||
|
||
## 2. Канон документов проекта (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Измерено по обоим проектам:
|
||
|
||
- **«Почему» не теряется — оно не находится.** `design.md` пишется почти всегда
|
||
(jellybit 39 из 43 архивных change, healthlog 9 из 9 — ≈285 КБ за две недели)
|
||
и имеет секции `Context` / `Goals / Non-Goals` / `Decisions` /
|
||
`Risks / Trade-offs`, то есть является ADR по структуре. Против этого ADR
|
||
руками: **6 записей у jellybit, четыре из них 13 июня — в день старта**; между
|
||
15 июня и 23 июля прошло ~40 изменений и ноль ADR. У healthlog ADR нет вовсе,
|
||
а настоящее ADR-рассуждение (отказ от DuckDB) лежит в разделе «Открытые
|
||
вопросы» файла `architecture.md`, потому что больше некуда.
|
||
- **Два плана.** `docs/plan.md` healthlog («порядок и его обоснование», 11 шагов)
|
||
и `<tasks>/PLAN.md` из `av-dev-tasks` («цели с обоснованием очереди прозой»)
|
||
— один артефакт под двумя именами.
|
||
- **Дубли спек у jellybit.** Из шести файлов `docs/specs/` три (`recognition`,
|
||
`review-ux`, `workflow`) описывают поведение, уже покрытое capability в
|
||
`openspec/specs/`.
|
||
- **`docs/drafts/` раскладывается без остатка:** `roadmap.md` → цели в «порядок»,
|
||
`conventions-backlog.md` → задачи `[idea]`, `logical-title-model.md` (293
|
||
строки, итог «сущность `title` не вводим») → намеренный отказ, то есть ADR.
|
||
|
||
### Решено
|
||
|
||
**D. «Почему» — ADR как промоут поверх архива.** Обоснование по-прежнему пишет
|
||
`design.md`; ADR — короткая запись, цитирующая решение и ссылающаяся на архивный
|
||
`design.md`. Заводит её **шаг «синк документации» пайплайна по названному
|
||
триггеру** (дорогой откат / намеренный отказ от очевидного / пересмотр прежнего
|
||
решения), а не человек по вдохновению.
|
||
|
||
*Причина:* ручной ритуал эмпирически не выжил — 6 записей на 52 изменения.
|
||
Автоматический (`opsx:propose` пишет `design.md` всегда) работает и производит на
|
||
порядок больше. Чинить надо не дом, а индекс и критерий промоута.
|
||
|
||
**E. `docs/plan.md` растворяется в `<tasks>/PLAN.md`.** Файл удаляется, 11 шагов
|
||
становятся целями в «порядке», ссылки в `CLAUDE.md` и паспорте переводятся.
|
||
|
||
**F. Пути жёсткие, оба проекта приводятся к одному виду.** Плагин знает раскладку
|
||
поимённо; указателя вида `.docs.json` нет.
|
||
|
||
*Причина (словами владельца):* «так проще ориентироваться во множестве проектов,
|
||
а не видеть слегка похожую, но разную структуру в каждом. Все проекты малого и
|
||
среднего размера, проще подогнать их под одну структуру. Кроме того, у OpenSpec
|
||
тоже структура строгая». Цена принята сознательно: плагин перестаёт быть
|
||
переносимым на чужой репозиторий, а `adopt` из «поправь указатели» превращается в
|
||
«перенеси файлы».
|
||
|
||
**G. Конвенции и разведка — каталогами с README-индексом.** `docs/conventions/`
|
||
и `docs/research/`: путь жёсткий, нарезка внутри свободна. Схема хранилища —
|
||
**отдельный** `docs/database.md` (своя каденция: меняется миграцией, а не
|
||
архитектурным решением; гейт healthlog уже сверяет миграции с документацией).
|
||
Конвенции идентификаторов и именования — не схема, они в `conventions/`.
|
||
|
||
**H. Слота для черновиков нет.** Идея → задача `[idea]`; намеренный отказ → ADR;
|
||
порядок работ → `PLAN.md`; незрелое размышление → `opsx:explore` внутри change.
|
||
|
||
### Канон
|
||
|
||
```
|
||
CLAUDE.md памятка агенту: что это, стек, инварианты, команды, слоты
|
||
docs/
|
||
passport.md зачем и для кого; чем НЕ является; сценарии; референсы
|
||
architecture.md как сложено — обзор: принципы, компоненты со ссылками
|
||
на capability, внешние границы, раскладка, деплой
|
||
database.md схема хранилища (там, где есть БД)
|
||
conventions/README.md + <тема>.md как пишем код; README держит правило промоута
|
||
research/README.md + <тема>.md что показала реальность: чужие форматы, живые данные
|
||
adr/README.md + template.md + ADR-*.md почему — промоут поверх архивных design.md
|
||
review-journal.md промахи конвейера ревью ← уточнено в теме 3
|
||
review-brief.md предмет ревью — см. тему 3 ← отменено в теме 3
|
||
tasks/ av-dev-tasks: items/, PLAN.md, BACKLOG.md, SPRINT.md, REJECTED.md
|
||
openspec/
|
||
config.yaml только нужды генерации + ссылки
|
||
specs/<capability>/spec.md что система делает — нормативно
|
||
changes/archive/ журнал изменений с design.md — сырьё для ADR
|
||
```
|
||
|
||
Слотов **нет** у: `docs/drafts/`, `docs/specs/`, `docs/plan.md`, `BRIEF.md`,
|
||
`docs/backlog/`, `docs/review/journal.md`.
|
||
|
||
### Что из этого следует
|
||
|
||
9. **Переезд healthlog:** `architecture.md` 1611 → обзор (поведение уезжает в
|
||
`openspec/specs` по разделу за задачу); `conventions.md` →
|
||
`conventions/README.md`; `local-research.md` 1829 → `research/`; `plan.md` →
|
||
`docs/tasks/PLAN.md`; `backlog/` → `docs/tasks/`; завести `docs/adr/`.
|
||
10. **Переезд jellybit:** `BRIEF.md` → `docs/passport.md` (заодно обновить — не
|
||
трогался с 13 июня); `docs/specs/architecture.md` → `docs/architecture.md`;
|
||
`docs/specs/database.md` → `docs/database.md`; `docs/specs/jellyfin-layout.md`
|
||
→ `docs/research/`; `docs/specs/{recognition,review-ux,workflow}.md` сверить с
|
||
capability и удалить как дубли; `docs/review/journal.md` →
|
||
`docs/review-journal.md`; `drafts/` растворить по H; `docs/backlog/` →
|
||
`docs/tasks/`.
|
||
11. **`adopt` меняет природу** — теперь он переносит файлы, а не правит
|
||
указатели. Разбирается в теме про старт проекта.
|
||
12. **Открыто до темы 6 (поддержание):** точная формулировка триггера промоута в
|
||
ADR; нужен ли механический `check` раскладки документов, раз пути жёсткие;
|
||
как не потерять остаток при постепенной чистке `architecture.md`.
|
||
|
||
## 3. Брифа ревью нет — бриф это и есть канон (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Контракт брифа — 413 строк, 13 разделов, отдельный файл `docs/review-brief.md`,
|
||
который каждый проход читает как истину. Заполнение на обоих проектах дало 841 и
|
||
734 строки, и `REMAINING.md` уже отметил, что часть разделов вырождается в
|
||
пересказ.
|
||
|
||
Разбор по разделам после решения F (жёсткие пути) показал: **посредник между
|
||
агентом и файлом не нужен, когда путь известен**. Восемь из тринадцати разделов
|
||
дублируют канон или снимаются жёсткими путями.
|
||
|
||
### Решено
|
||
|
||
**I. Отдельного файла-брифа нет.** Проектную конкретику проходам дают документы
|
||
канона напрямую, по жёстким путям. Формулировка владельца: «артефакты в `docs` и
|
||
должны стать частями брифа, а для ревью достаточно дать ссылки на эти артефакты».
|
||
|
||
*Причина:* один факт — один дом. Бриф был вторым домом для паспорта, инвариантов
|
||
и карты, а разошедшийся бриф хуже отсутствующего: он выглядит актуальным.
|
||
|
||
**J. Заводится `docs/security.md`.** Периметр **первой строкой** (целевой и
|
||
сегодняшний, если контур не развёрнут), недоверенный вход и его каналы, из чего
|
||
строятся пути и ключи, что разграничивает доступ, что чувствительнее чего, что
|
||
вне модели. Материал уже есть, но рассыпан: у healthlog — раздел
|
||
«Аутентификация» в `architecture.md` и строка про секреты в `CLAUDE.md`, у
|
||
jellybit — секреты в `conventions/config.md`. **Периметра нет ни у одного**, а
|
||
без него враждебный проход не выбирает между «открыт наружу» и «контур
|
||
доверенный».
|
||
|
||
**K. `review-journal.md` → `docs/review.md`:** журнал дефектов плюс настройка
|
||
конвейера под проект. Туда садится остаток брифа, который фактом о проекте не
|
||
является — типовые узлы, типовые ложноположительные, вопросы к проходам,
|
||
недоступно проверке.
|
||
|
||
*Причина:* все четыре — производные калибровки, и журнал им источник. `##
|
||
Вопросы к проходам` сам называет журнал главным источником; `### Перестали
|
||
проверять сознательно` требует ссылки на его запись.
|
||
|
||
**L. Журнал расширяется до всех воспроизведённых дефектов** с пометкой
|
||
«проскочил / пойман ревью». Эвал-сет для калибровки — выборка по пометке.
|
||
|
||
*Причина:* пойманные дефекты с оракулом (768 МиБ пика, 5.019 с удержания
|
||
блокировки) сегодня не сохраняются нигде, кроме отчётов триажа в архиве change, а
|
||
они и есть лучшая опора для прохода — проектные, воспроизводимые, однажды
|
||
оказавшиеся правдой.
|
||
|
||
**M. Семантика гейта — в `CLAUDE.md`, расширением раздела «Команды».** Чем
|
||
краснеет безусловно и почему, где логи, что означает исход, чего в гейте
|
||
намеренно нет, **кто и когда обязан гонять дорогое вне гейта**, что запускать
|
||
запрещено (с путями). Гейт краснеет не только в ревью — это факт о проекте.
|
||
|
||
**N. Severity инвариантов дописывается в `CLAUDE.md`** рядом с формулировкой.
|
||
Контракт брифа сам называл это лучшим исходом; жёсткие пути делают возможным.
|
||
Оговорка «выведена по обратимости» исчезает вместе с пересказом.
|
||
|
||
### Канон после темы 3
|
||
|
||
```
|
||
CLAUDE.md что это, стек, инварианты с severity, команды,
|
||
семантика гейта, запреты, слоты
|
||
docs/
|
||
passport.md зачем и для кого; чем НЕ является; сценарии; референсы
|
||
architecture.md как сложено — обзор; окружение, внешние зависимости,
|
||
наблюдатель, характер потока
|
||
database.md схема хранилища; представление данных и настройки
|
||
с числовым значением (таймаут занятости, лимит тела,
|
||
режим журналирования, ретеншен)
|
||
security.md периметр первой строкой; недоверенный вход; из чего
|
||
строятся пути и ключи; разграничение; что вне модели
|
||
conventions/README.md + <тема>.md
|
||
research/README.md + <тема>.md наблюдения и измеренные числа с провенансом
|
||
adr/README.md + template.md + ADR-*.md
|
||
review.md настройка конвейера под проект + журнал дефектов
|
||
tasks/ av-dev-tasks
|
||
openspec/
|
||
config.yaml, specs/<capability>/spec.md, changes/archive/
|
||
```
|
||
|
||
Слотов **нет** у: `docs/review-brief.md`, `docs/drafts/`, `docs/specs/`,
|
||
`docs/plan.md`, `BRIEF.md`, `docs/backlog/`, `docs/review-journal.md`.
|
||
|
||
### Что из этого следует
|
||
|
||
13. **Скилл `project-brief` растворяется.** Заведение недостающих документов
|
||
канона — часть скилла старта/адаптации (тема 5, требование Т1).
|
||
14. **Девять charter'ов переписываются второй раз.** Сейчас каждый читает «из
|
||
раздела `## X` брифа»; станет — из файла канона. **Цена названа вслух:**
|
||
первая переписка (вынос в плагин) осталась незамеренной — `REMAINING.md`,
|
||
пункт 1. Вторая делает замер по четырём реальным находкам healthlog
|
||
**обязательным, а не желательным**: два неизмеренных изменения подряд в том
|
||
самом месте, где присваивается severity.
|
||
15. **Теряется соседство фактов, и charter обязан сшивать.** Контракт настаивал,
|
||
что замер становится находкой только рядом с настройкой: «768 МиБ пика» —
|
||
аномалия, лишь если известно, что запись лежит сжатой и распаковывается
|
||
целиком; «5.019 с удержания блокировки» — отказ соседа, лишь если известен
|
||
таймаут занятости. Теперь это `research/` и `database.md`, и charter'ы `ops`,
|
||
`adversary`, `reimpl` обязаны прямо говорить «собери из этих двух», иначе
|
||
проход снимет верное число и честно понизит находку до гипотезы.
|
||
16. **Деградация становится поразрядной** — и это лучше прежнего «нет брифа →
|
||
деградирует всё». Нет `security.md` — деградирует `adversary`; нет
|
||
`research/` — числа неизвестны `ops`, `adversary` и `reimpl`; нет
|
||
`passport.md` — архитектурный проход теряет границу домена. Каждый проход
|
||
пишет свою строку в границы покрытия.
|
||
17. **Открытый вопрос из `REMAINING.md` закрыт:** раздел `## Триггеры`
|
||
удаляется вместе с брифом. Правило выбора профиля остаётся в скилле
|
||
конвейера; проектная конкретизация, если понадобится, — в `docs/review.md`.
|
||
|
||
## 4. Границы плагинов (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Связь `tasks` ↔ `pipeline` уже сделана **ролями, а не именами**: скиллы говорят
|
||
«пайплайн проекта», «владелец спринта», «тот, кто ведёт задачи». Жёсткая ссылка
|
||
по имени ровно одна — `task-pipeline:112` на канонический текст правила про
|
||
остаток внутри `session`, и рядом обработан случай «плагин не подключён».
|
||
|
||
Слоты `CLAUDE.md` при этом дублировались уже внутри одного плагина: шесть у
|
||
`tasks`, семь у `session`, три пары — одно и то же. Темы 2–3 растворили ещё
|
||
часть: «куда переезжает суть» отвечает канон, «оракулы» — семантика гейта
|
||
(решение M), «где живёт разбор процесса» — `docs/review.md` (решение K). Из
|
||
тринадцати остаётся около четырёх.
|
||
|
||
### Решено
|
||
|
||
**O. Три плагина: `av-dev-pm`, `av-dev-pipeline`, `av-dev-git`.**
|
||
|
||
- **`av-dev-pm`** (бывший `av-dev-tasks`) — управление продуктом: канон
|
||
документов, задачи, цели, спринты, старт и адаптация проекта. Владеет всем
|
||
`docs/`, включая `docs/tasks/`.
|
||
- **`av-dev-pipeline`** — исполнение: SDD-цикл, конвейер ревью, девять агентов.
|
||
- **`av-dev-git`** — стиль коммитов; работает в любом репозитории.
|
||
|
||
*Причина (словами владельца):* «пайплайн можно и переиспользовать в других
|
||
проектах с более простым подходом к управлению». Это подтверждается разбором:
|
||
пайплайн зависит от **файлов канона и от OpenSpec, а не от плагина** `av-dev-pm`.
|
||
В чужом проекте нужных файлов нет — включается поразрядная деградация (следствие
|
||
16), и это штатный режим, а не поломка.
|
||
|
||
*Имя:* `pm` = product management, «объединение всех операций по управлению
|
||
продуктом», и согласуется с `av-dev-git`.
|
||
|
||
**P. Граница «пайплайн не закрывает задачу» снимается.** Закрывает задачу и
|
||
двигает строки между `SPRINT.md` / `BACKLOG.md` / `REJECTED.md` **агент-
|
||
оркестратор** — `task-pipeline` и `task-batch`, а не сабагенты внутри них. Зовёт
|
||
он `tasks.py` через слот «Команда учёта задач» в `CLAUDE.md`.
|
||
|
||
Слот, следовательно, **не исчезает, а становится мостом между плагинами** — и
|
||
заодно тем, чего в чужом проекте нет, отчего пайплайн там работает как прежде:
|
||
докладывает исход, записей учёта не трогает.
|
||
|
||
**Q. `av-dev-backlog` помечается устаревшим и остаётся** до перевода jellybit.
|
||
*(заменено на тему 30: плагин удалён раньше этого срока — условие пережило свою
|
||
причину.)* Описание переписывается так, чтобы не ловить триггер «добавь задачу в
|
||
беклог» — иначе агент выбирает между ним и `av-dev-pm` случайно.
|
||
|
||
### Что из этого следует
|
||
|
||
18. **Переименование `av-dev-tasks` → `av-dev-pm`** тянет `plugin.json`,
|
||
`marketplace.json` и пространство имён скиллов: `av-dev-tasks:session` →
|
||
`av-dev-pm:session`, включая ссылку из `task-pipeline:112`.
|
||
19. **Раздел «Стимулы, которые процесс создаёт» в `session` переписывается.**
|
||
Снятая граница выбила механическую опору у трёх защит: «сжать задачу до
|
||
остатка», «занизить урожай», «занизить критерии приёмки» — во всех трёх
|
||
приёмщик и исполнитель теперь совпадают. Остаются: **отчёт триажа** в
|
||
`openspec/changes/<id>/review/` (независимый артефакт, `task-batch` уже
|
||
сверяет полноту ревью по нему, а не по прозе исполнителя), **`SPRINT.md` под
|
||
git** с видимой историей и **`reopen <slug> --reason`** — закрытие не
|
||
окончательно, приёмка человеком на сессии его отменяет. Раздел обязан назвать
|
||
их поимённо, иначе обещает защиту, которой нет.
|
||
20. **Конфликт владения `docs/tasks/` снят** — канон и задачи теперь в одном
|
||
плагине.
|
||
21. **Скилл `adopt` из `av-dev-tasks` поглощается** скиллом адаптации проекта
|
||
уровня канона (требование Т1). Разбирается в теме 5.
|
||
22. **Состав `av-dev-pm`:** `tasks`, `session` (есть), `docs` — ведение канона,
|
||
`project` — старт, adopt, check, upgrade (тема 5).
|
||
|
||
## 5. Старт проекта и жизненный цикл под каноном (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Требование Т1: прийти в любой старый проект и перевести на текущие рельсы; канон
|
||
сам меняется, значит уже приведённые проекты тоже повышаются.
|
||
|
||
Существующий `adopt` (уровень задач) даёт готовую форму: **`scan` — только
|
||
чтение, карта → суждение человека → `apply` — запись одним проходом**, с отказом
|
||
до первой записи при неверной карте и с обязательным разделом «не разложилось»
|
||
поимённо. Форма переносится на уровень канона как есть.
|
||
|
||
Четыре операции различаются не поровну: `adopt`, `check` и `upgrade` — одна
|
||
машина сравнения с разными исходами, а `init` — принципиально другой режим,
|
||
разговор, а не сверка.
|
||
|
||
### Решено
|
||
|
||
**R. Два скилла: `av-dev-pm:init` и `av-dev-pm:canon`.** `init` — интервью по
|
||
входному брифу для нового проекта. `canon` — привести к канону: `check`, `adopt`,
|
||
`upgrade` одной машиной.
|
||
|
||
**S. Скелет канона заводится целиком, незаполненное называется пустым.** Все
|
||
файлы канона есть с первого дня, но незаполненный держит **одну честную
|
||
информативную строку**: «наблюдений на живых данных нет — внешний источник один,
|
||
формат документирован», «прецедентов не накоплено», «внешних зависимостей нет,
|
||
смотри на диск и на СУБД».
|
||
|
||
*Причина:* это тот же принцип, что был в контракте брифа, поднятый на уровень
|
||
файлов. Проход читает такую строку **как факт**, а не как пробел, и не тратит
|
||
обязательный вопрос впустую. Отсутствие файла он прочитать не может никак.
|
||
|
||
**Защита от вырождения в заглушки** берётся у `tasks.py`: пока на месте стоит
|
||
плейсхолдер шаблона, `check` о нём напоминает. Строка «TBD» — это не «пустое
|
||
названо пустым», и `check` обязан их различать.
|
||
|
||
**T. Скрипт `docs.py` плюс версия канона в `docs/.pm.json`.** Отдельный скрипт,
|
||
не расширение `tasks.py`: рефакторинг 2421 работающей строки ради удобства вызова
|
||
не окупается. `docs.py check` зовёт `tasks.py check` для своей части.
|
||
|
||
**Граница механизируемого объявляется вслух — иначе `check` соврёт.**
|
||
|
||
| Проверяет `docs.py` | Судит агент |
|
||
| --- | --- |
|
||
| отсутствующие пути канона | смысловой дубль (`docs/specs/recognition.md` против capability) |
|
||
| файлы в `docs/` вне канона | поведение, оставшееся в `architecture.md` |
|
||
| битые относительные ссылки | протухший факт, разошедшийся с кодом |
|
||
| версия канона и её отставание | достаточность честной строки в пустом слоте |
|
||
| нетронутый плейсхолдер шаблона | |
|
||
|
||
`check`, отчитавшийся «канон соблюдён» на проекте, где из шести файлов три
|
||
лишние, хуже отсутствующего.
|
||
|
||
### Порядок интервью `init` — зависимость, а не удобство
|
||
|
||
Цель и потребители → чем это **не** является и мера успеха → периметр и что
|
||
недоверенное → стек, хранилище, необратимое → чем краснеет гейт → первые цели в
|
||
`PLAN.md`. Каждый блок опирается на ответ предыдущего.
|
||
|
||
Вход — свободный текст «что мне нужно и почему» (образец формы: `BRIEF.md`
|
||
jellybit, 6 КБ). После `init` его дом — `passport.md`; отдельным файлом он не
|
||
остаётся.
|
||
|
||
**`init` физически не производит полный канон.** В новом репозитории нет кода, а
|
||
`architecture.md`, `database.md`, `conventions/` и `research/` выводятся из него.
|
||
Они заводятся скелетом с честной строкой («архитектуры пока нет: кода нет,
|
||
заводится первой задачей») и наполняются шагом синка документации.
|
||
|
||
### Что из этого следует
|
||
|
||
23. **`docs/.pm.json` поглощает `<tasks>/.tasks.json`.** Меняется цепочка
|
||
разрешения в `tasks.py` — сегодня он ищет `.tasks.json` вверх от текущего
|
||
каталога. Нужен переходный период либо чтение обоих.
|
||
24. **`tasks.py adopt` становится шагом внутри `canon adopt`**, а не отдельной
|
||
пользовательской операцией: `docs/tasks/` — часть той же раскладки.
|
||
25. **Версия канона — целое число**, не semver: у канона нет обратной
|
||
совместимости, есть только «приведён» и «не приведён».
|
||
26. **Журнал изменений канона** живёт в плагине —
|
||
`av-dev-pm/skills/canon/references/changelog.md`, запись на версию: что
|
||
добавилось, что переехало, что удалено, что сделать проекту.
|
||
27. **Открыто до темы 6:** звать ли `docs.py check` из гейта проекта. У healthlog
|
||
`task gate` уже сверяет миграции с документацией, так что место есть; но гейт
|
||
принадлежит проекту, и плагин может только рекомендовать строкой в отчёте.
|
||
|
||
## 6. Поддержание документов по ходу разработки (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Гейт healthlog **уже изобрёл нужный механизм** для одного документа —
|
||
`scripts/gate.py:177-181`: миграция изменена, а `docs/database.md` нет → `FAIL`.
|
||
Документ канона сверяется с кодом красным гейтом, а не напоминанием.
|
||
|
||
Против этого — прямое доказательство, что́ не работает: у `adr/` был список
|
||
триггеров прозой («выбор технологии, структурные решения, дорогой откат,
|
||
намеренный отказ»), и он дал **6 записей на 43 изменения**. Прозаический триггер,
|
||
который некому проверить, не срабатывает.
|
||
|
||
Механизируемы три документа из десяти: `database.md` (миграция), `architecture.md`
|
||
(capability в `openspec/specs/` без упоминания в обзоре), `tasks/` (`tasks.py
|
||
check`). Плюс `openspec/specs/` вливает `opsx:archive`.
|
||
|
||
### Решено
|
||
|
||
**U. Принуждённое отрицание в докладе шага синка.** Шаг обязан назвать **каждый**
|
||
документ канона: обновлён — чем, либо «не требуется, потому что…». Нетронутые
|
||
группируются одной строкой с общей причиной.
|
||
|
||
*Причина:* это тот же приём, что «границы покрытия» в отчёте ревью и «пустой
|
||
пункт называется пустым» в каноне — и единственный, о котором в этом репозитории
|
||
есть данные, что он работает. Умолчание «не написал» становится неотличимым от
|
||
«написал, что не требуется», только если отрицание обязательно.
|
||
|
||
**Триггер ADR, окончательная формулировка.** Запись заводится, когда верно одно
|
||
из трёх: **дорогой откат** (переделка стоит дороже переписывания одного файла);
|
||
**намеренный отказ** от очевидного подхода; **пересмотр прежнего решения** — тогда
|
||
у старой записи обязателен статус «заменено на». Не заводится для рутины и для
|
||
того, что видно из кода и `git log`. Источник — архивный `design.md`: ADR его
|
||
цитирует и на него ссылается, а не пересказывает.
|
||
|
||
**V. `docs.py check` обязателен в гейте проекта.** Скилл `canon` при адаптации
|
||
добавляет шаг и печатает это в отчёте.
|
||
|
||
*Причина:* гейт — единственный общий станок, который нельзя пропустить. Проверка,
|
||
которую зовёт агент, может быть не позвана; прецедент в самом healthlog уже есть.
|
||
|
||
Сверка «миграция изменена — `database.md` нет» **обобщается**: путь миграций
|
||
проекта записывается в `docs/.pm.json` рядом с версией канона, и `docs.py` делает
|
||
эту проверку сам, а не каждый проект заново.
|
||
|
||
**W. Остаток чистки помечается маркером и считается числом.** Неразобранный
|
||
раздел получает `<!-- канон: поведение → openspec/specs/<capability> -->`,
|
||
`docs.py` считает маркеры и печатает остаток. Закрывается порциями, как
|
||
переоценка задач.
|
||
|
||
**Маркеры гейт не красят.** Это долг, а не отказ: покрасневший гейт на первом
|
||
маркере сделал бы постепенный переезд невозможным, а разовый — обязательным.
|
||
Число печатается и убывает на глазах.
|
||
|
||
### Что из этого следует
|
||
|
||
28. **Шаг 9 `task-pipeline` переписывается** из четырёх пунктов прозой в
|
||
построчный доклад по документам канона.
|
||
29. **`promote.md`, шаг 3, переписывается:** «вычеркнуть пункт из брифа, правило
|
||
переезжает в перечень механизированного в разделе `## Карта`» → перечень
|
||
механизированного живёт в `conventions/README.md`. Брифа нет.
|
||
30. **`docs/.pm.json` держит не только версию канона**, но и пути, нужные
|
||
проверкам: каталог миграций — как минимум.
|
||
31. **`docs.py check` получает две сверки с кодом**, а не только раскладку:
|
||
миграции ↔ `database.md`, capability ↔ упоминание в `architecture.md`.
|
||
|
||
## 7. Раскладка скиллов и доставка скриптов (2026-08-03)
|
||
|
||
### Решено
|
||
|
||
**X. Пять скиллов в `av-dev-pm`.**
|
||
|
||
```
|
||
av-dev-pm/skills/
|
||
init/ интервью по брифу → канон нового проекта
|
||
canon/ раскладка: check / adopt / upgrade
|
||
docs/ содержимое канона: ADR из архивного design.md, промоут конвенций,
|
||
запись в research/ и review.md, чистка architecture.md
|
||
tasks/ формат и содержимое задач
|
||
session/ ритуал спринта
|
||
```
|
||
|
||
*Причина отдельного `docs`:* правила ведения содержимого канона обязаны жить у
|
||
владельца канона, а не в шаге синка чужого плагина — иначе проект без пайплайна
|
||
документацию вести не может. Это работает потому, что **вызов скилла через
|
||
пространство имён между плагинами возможен**, в отличие от
|
||
`$CLAUDE_PLUGIN_ROOT`: `task-pipeline` уже зовёт `opsx:propose` и
|
||
`av-dev-pipeline:review-pipeline`. Шаг синка зовёт `av-dev-pm:docs`, а в чужом
|
||
проекте деградирует до прозаического списка.
|
||
|
||
Симметрия, по которой резалось: **раскладка и содержимое разделены и для
|
||
документов, и для задач** — `canon` / `docs`, `tasks` / `session`.
|
||
|
||
**Y. Скрипты не копируются — живут вместе со скиллами.** Три вызывающих, три
|
||
способа дотянуться:
|
||
|
||
| Кто зовёт | Как |
|
||
| --- | --- |
|
||
| скиллы `tasks`, `canon`, `docs` | `$CLAUDE_PLUGIN_ROOT` — свой плагин, работает всегда |
|
||
| `task-pipeline`, `task-batch` | **вызов скилла** `av-dev-pm:tasks`, а не путь |
|
||
| гейт проекта | путь переменной с умолчанием на канонический путь маркетплейса; пишет `canon adopt`, внятный красный отказ, если не найден |
|
||
|
||
**Слот «Команда учёта задач» всё равно исчезает** — но снимает его не копия, а
|
||
**вызов скилла через пространство имён**. Тот же приём, которым шаг синка зовёт
|
||
`av-dev-pm:docs` (решение X): чужой плагин зовёт скилл, скилл разрешает свой
|
||
`$CLAUDE_PLUGIN_ROOT` сам. Путь наружу не выносится вовсе.
|
||
|
||
*Первоначально здесь было решено вендорить `scripts/tasks.py` и
|
||
`scripts/docs.py` в проект. Отменено после проверки фактов:*
|
||
|
||
- **CI нет ни в одном проекте** (ни `.github`, ни woodpecker, ни drone).
|
||
Pre-commit есть только у jellybit — `lefthook` с gofmt/vet/lint/test/gitleaks —
|
||
и гоняется на той же машине, где установлен плагин. Довод «не работает в CI и
|
||
у человека без Claude Code» оказался гипотетическим.
|
||
- **Пара «источник — копия» существует и без вендоринга.** Установленный
|
||
маркетплейс — git-клон; на момент разбора он стоял на `092d07c`, на четыре
|
||
коммита позади `master`, и `av-dev-tasks` с `av-dev-pipeline` в нём
|
||
отсутствовали вовсе. Довод «вендоринг создаёт вторую копию» был слабее, чем
|
||
подан.
|
||
- **Обновление маркетплейса — одна точка на все проекты.** При вендоринге каждый
|
||
проект повышается отдельно, и проекты расходятся друг с другом — ровно та
|
||
разнородность, против которой принято решение F.
|
||
|
||
**Z. Имени у процесса нет — процесс это `av-dev`.** Маркетплейс уже
|
||
`av-dev-skills`, плагины `av-dev-*`; в `CLAUDE.md` проекта пишется «процесс
|
||
av-dev, канон версии N». Имя, которое нигде не работает, — украшение.
|
||
|
||
### Что из этого следует
|
||
|
||
32. **Решение P уточняется:** оркестратор закрывает задачи **вызовом скилла**
|
||
`av-dev-pm:tasks`, а не запуском скрипта по пути. Плагина в проекте нет —
|
||
вызов не разрешается, и пайплайн, как прежде, только докладывает исход.
|
||
33. **Слот исчезает из двух скиллов** — `tasks` (слот 6) и `session` (слот 7), —
|
||
и из текстов `task-pipeline` и `task-batch`, которые на него ссылаются.
|
||
34. **`canon upgrade` отвечает за раскладку и версию в `docs/.pm.json`.**
|
||
Скрипты обновляются обновлением маркетплейса, а не проектом.
|
||
35. **Скрипты живут в `av-dev-pm/skills/{tasks,canon}/scripts/`.** `docs.py` — в
|
||
`canon`, потому что раскладку проверяет он.
|
||
36. **`canon check` сверяет версию канона проекта с версией установленного
|
||
плагина** и говорит, кто отстал. Это нужно и без вендоринга: маркетплейс —
|
||
git-клон, обновляется явно, и на момент разбора отставал на четыре коммита.
|
||
37. **Установленный маркетплейс требует обновления перед любой работой** —
|
||
сейчас в нём нет ни `av-dev-tasks`, ни `av-dev-pipeline`. Это первый шаг
|
||
выката (тема 8), иначе проверять будет нечего.
|
||
|
||
## 8. Порядок выката (2026-08-03)
|
||
|
||
### Объём
|
||
|
||
Ссылок на бриф — **168 строк в 19 файлах** `av-dev-pipeline`, из них ~48 уходят
|
||
вместе с удаляемыми `project-brief/SKILL.md`, `references/project-brief.md` и
|
||
`references/brief-template.md`. Остальное переписывается на пути канона.
|
||
|
||
### Решено
|
||
|
||
**AA. Инструмент строится целиком, потом проверяется.** Не пилот руками.
|
||
|
||
*Риск принят сознательно:* если замер покажет деградацию severity, чинить
|
||
придётся канон, зашитый к тому моменту в три скилла, скрипт и мигрированные файлы
|
||
healthlog.
|
||
|
||
*Удешевление, которое обязано быть заложено сразу:* **определение канона живёт в
|
||
единственном reference-файле**, который читают `init`, `canon` и `docs`, а не
|
||
повторяется в каждом. Правка канона — одно место плюс запись в журнал версий.
|
||
|
||
*Страховка порядка:* **замер ставится перед переездом jellybit**, а не после
|
||
всего, — он всё ещё блокирует то, что дороже всего откатывать.
|
||
|
||
**BB. Работа ведётся в `docs/tasks/` самого `dev-skills`.** Скилл `tasks` не
|
||
требует ни OpenSpec, ни языка — задачи для него просто каталог markdown. Цели —
|
||
крупные куски, задачи — следствия. Заодно первая боевая обкатка собственного
|
||
инструмента.
|
||
|
||
**CC. `AGENTIC-TASKS.md` сжимается до истории решений и переезжает в
|
||
`dev-skills`** отдельным `HISTORY.md`: почему не Scrum, числа первого замера, что
|
||
отвергнуто и почему. Он описывает процесс, а процесс живёт здесь, не в healthlog.
|
||
Остальное содержимое уже в плагинах, и второй дом для тех же правил — ровно то,
|
||
против чего документ сам и написан.
|
||
|
||
### Порядок
|
||
|
||
```
|
||
0. обновить установленный маркетплейс предусловие всего
|
||
0.5 завести docs/tasks в dev-skills, разложить 37 следствий по целям
|
||
|
||
1. РЕПОЗИТОРИЙ ПЛАГИНОВ
|
||
1.1 av-dev-tasks → av-dev-pm, пространство имён
|
||
1.2 канон одним reference-файлом — единственный дом определения
|
||
1.3 правки tasks и session: слоты, «Стимулы», .pm.json
|
||
1.4 новые init, canon, docs + docs.py
|
||
1.5 av-dev-pipeline: удалить project-brief, снять ветки деградации OpenSpec,
|
||
переписать шаг 9, девять charter'ов, promote.md, убрать слот
|
||
1.6 av-dev-backlog устаревшим; README; журнал канона v1; HISTORY.md
|
||
1.7 REMAINING.md пересобрать — часть его вопросов закрыта этим разбором
|
||
|
||
2. HEALTHLOG — первая боевая проверка инструмента
|
||
canon adopt, заполнение канона, security.md, review.md, ADR,
|
||
маркеры в architecture.md, docs.py check в гейте
|
||
|
||
3. КАЛИБРОВКА на четырёх находках healthlog БЛОКИРУЕТ шаг 5
|
||
|
||
4. один-два спринта healthlog на новом процессе
|
||
|
||
5. JELLYBIT — переезд, удаление дублей specs, растворение drafts
|
||
```
|
||
|
||
### Что из этого следует
|
||
|
||
38. **`REMAINING.md` частично устарел:** пункт 2 «Завести брифы» отменён темой 3;
|
||
закрыты открытые вопросы про `## Триггеры`, `av-dev-backlog`, имя процесса и
|
||
`AGENTIC-TASKS.md`. Пункт 1 (калибровка) стал обязательным, а не
|
||
желательным. Пересобрать на шаге 1.7.
|
||
39. **Замер — единственный шаг, который нельзя переставить.** Всё остальное в
|
||
порядке 1–5 можно тасовать; шаг 3 стоит перед шагом 5 жёстко.
|
||
|
||
## 9. Линтеры скриптов (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Три скрипта на python, 3600 строк, ни одной проверки. `tasks.py` — 2450 строк,
|
||
которые ходят по файловой системе, переименовывают и удаляют файлы задач.
|
||
Требование к самим скриптам прежнее и не обсуждается: **голый `python3` 3.12,
|
||
ноль внешних зависимостей** — они лежат рядом со скиллами и запускаются в
|
||
чужом проекте, где ничего ставить нельзя.
|
||
|
||
### Решено
|
||
|
||
**DD. `pyproject.toml` в корне `dev-skills`, зависимости через `uv`.** Файл
|
||
живёт только здесь и не уезжает никуда: он держит **линтеры**, а не зависимости
|
||
скриптов. Скрипты остаются запускаемыми любым `python3` — это проверено прогоном
|
||
всех операций через `/usr/bin/python3`, а не через `.venv`.
|
||
|
||
**EE. Ноль зависимостей охраняется двумя способами, и главный — второй.**
|
||
`banned-api` у ruff ловит частые соблазны по имени (`requests`, `yaml`,
|
||
`pydantic`, `click`, `rich`) — список заведомо неполный. Настоящий страж —
|
||
pyrefly: в окружении нет ничего, кроме линтеров, поэтому **любой** сторонний
|
||
импорт у него не разрешается. Первый способ даёт понятное сообщение, второй —
|
||
полноту.
|
||
|
||
**FF. Версии линтеров прибиты точно** (`ruff==0.16.1`, `pyrefly==1.2.0`) плюс
|
||
`uv.lock` в git. Обновление линтера меняет набор находок, а находки правятся
|
||
руками в скриптах, которые уезжают в чужие проекты. Обновление обязано быть
|
||
отдельной осознанной правкой, а не побочным эффектом `uv sync`.
|
||
|
||
**GG. `RUF001`–`RUF003` выключены.** Весь текст скриптов русский: сообщения,
|
||
докстроки, комментарии. «Похожая на латиницу кириллица» здесь норма, а не
|
||
опечатка, и три этих правила давали **311 срабатываний из 338** — шум, в котором
|
||
тонут остальные 27.
|
||
|
||
**HH. `av-dev-backlog` исключён из проверки.** *(исчерпано темой 30: плагин
|
||
удалён, исключение снято из `pyproject.toml` и `copies.py`.)* Плагин помечен
|
||
устаревшим и живёт
|
||
до перевода последнего проекта, после чего удаляется целиком. Шесть его находок
|
||
косметические (`os.replace`, `l` как имя), а правка замороженного кода без
|
||
тестов — риск без выгоды. Исключение уходит вместе с плагином.
|
||
|
||
**II. Голый `except Exception` разрешён только помеченный.** Правило `BLE`
|
||
включено, а два места последнего рубежа (`main` обоих скриптов, код выхода 4 по
|
||
словарю) несут `# noqa: BLE001` с причиной. Так третий такой except не
|
||
появляется молча.
|
||
|
||
### Что из этого следует
|
||
|
||
40. **Найдено и починено 27 находок ruff и 14 pyrefly.** Содержательных две:
|
||
мёртвая переменная `ques` в `check` (вычислялась и не использовалась —
|
||
вопросы проверяет `questions_open`) и два места в `check --fix`, где
|
||
`find_entry_index` может вернуть `None`, а результат идёт прямо в
|
||
`list.pop` и в `range`. Оба сегодня недостижимы, и недостижимость держалась
|
||
на рассуждении о вызывающем коде, а не на проверке.
|
||
*Поправлено по ревью:* там стоит `raise`, а не `continue`. Тихий пропуск
|
||
превратил бы сломанный инвариант в отчёт «индексы согласованы» — то есть в
|
||
враньё; громкий отказ кодом 4 честнее.
|
||
41. **`os` из `tasks.py` ушёл целиком.** `os.replace` → `Path.replace`,
|
||
`os.path.basename` → `Path.name`; импорт стал не нужен.
|
||
42. **`fail()` в `docs.py` объявлен `NoReturn`.** Без этого `read_config`
|
||
выглядел как возвращающий неинициализированное значение — и это ровно то,
|
||
что читатель кода тоже не мог знать наверняка.
|
||
43. **Проверка не входит ни в один гейт.** CI у репозитория нет, хука нет;
|
||
запускается руками командой из README. Заводить хук ради двух скриптов,
|
||
которые правятся раз в месяц, — плата ритуалом без выгоды.
|
||
|
||
## 10. Ревью готовых плагинов двумя проходами (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Два независимых сабагента `fable` — по одному на `av-dev-pm` и `av-dev-pipeline`.
|
||
**20 находок, из них две найдены обоими независимо.** Прошлые три круга ревью
|
||
шли по одному проходу на всё; два прохода с разными предметами дали и больший
|
||
урожай, и перекрёстное подтверждение самого дорогого дефекта.
|
||
|
||
### Что оказалось сломано по существу
|
||
|
||
**JJ. Перестановка закрытия за коммит (решение из темы 8) сломала `reopen` и
|
||
батч — и это нашли оба прохода.** `close --implemented` печатает «дорога назад:
|
||
файл восстанавливается из git», а `reopen` искал **коммит удаления**, которого в
|
||
новом порядке ещё нет: шаг 11 идёт последним, и учёт остаётся незакоммиченным.
|
||
Проверено прогоном: `reopen` отказывал кодом 2 на свежезакрытой задаче — то есть
|
||
в самом вероятном своём применении. Тем же грязным деревом ломался `task-batch`:
|
||
`git rebase` и `git worktree remove` отказывают, и **каждая успешно закрывшая
|
||
задачу ветка** уезжала бы в провалившиеся.
|
||
|
||
Починено с обеих сторон: `reopen` берёт текст из `HEAD`, если коммита удаления
|
||
нет, а шаг 11 обязан **коммитить учёт вторым коммитом** — иначе закрытие не
|
||
доезжает до основной ветки и опора «`SPRINT.md` под git» остаётся словами.
|
||
|
||
**KK. Канонический пример `docs/.pm.json` убивал `tasks.py`.** `canon.md`,
|
||
`skeletons.md`, `tasks/SKILL.md` и `adopt.md` показывали ключ `tasks.sections`,
|
||
которого скрипт не знает: `_validate_config` отвергает неизвестные ключи кодом 3
|
||
на **любой** команде. Проект, заведённый по канону дословно, остался бы без
|
||
работы с задачами целиком — а `docs.py check` при этом печатал «канон соблюдён»,
|
||
потому что чужой код 3 уходит в «не проверялось». Секции живут в заголовках `##`
|
||
индекса и второго дома не получают.
|
||
|
||
### Что из этого следует
|
||
|
||
44. **Класс находок тот же, что и в прошлые три круга: стыки.** Не новый код, а
|
||
место, где один файл ссылается на другой. `sprint.md` в пункте «Сделана» всё
|
||
ещё отсылал к порядку, который сам же тремя экранами ниже отменил; три
|
||
остатка «шаг 9а» несли **предкоммитную** позицию закрытия; путь отчёта
|
||
триажа не переживал `opsx:archive`, хотя по нему сверяют полноту ревью
|
||
четверо.
|
||
45. **Инструкция, которую нельзя выполнить, выглядит как выполненная.** Ответ на
|
||
вопрос по документированной процедуре (снять тег) оставлял задачу
|
||
незабираемой, потому что судит **раздел**, а не тег; `canon adopt` требовал
|
||
гнать `docs.py check` «до отсутствия дрейфа», недостижимого без нарушения
|
||
запрета сочинять цели; урожай спринта, заведённый после `sprint close`,
|
||
терял автотег молча.
|
||
46. **Два прохода по разным предметам дороже одного, но не вдвое.** Перекрытие
|
||
оказалось ровно в одной находке из двадцати — той самой, что подтвердилась
|
||
дважды. Практика остаётся: ревью на плагин, а не одно на репозиторий.
|
||
|
||
## 11. Зависимости между плагинами (2026-08-03)
|
||
|
||
### Целевая картина, которую проверяли
|
||
|
||
`av-dev-git` ни от чего не зависит. `av-dev-pipeline` сам по себе: задача
|
||
приходит **и обычным текстом**, и из `tasks`. `av-dev-pm` оперирует абстрактным
|
||
«сделать задачу» и не знает, чем она выполняется.
|
||
|
||
### Что показала проверка
|
||
|
||
**LL. Первые две цели выполняются, третья в исходной формулировке недостижима —
|
||
и формулировку надо поправить, а не картину.** `av-dev-pm` **владеет
|
||
конфигурационным файлом конвейера**: `docs/review.md` держит «Вопросы к
|
||
проходам» и «Триггеры профиля», то есть перечисляет проходы поимённо, а скелет
|
||
`review.md` несёт форму журнала дефектов. Кто-то этим словарём владеть обязан —
|
||
канон и есть схема данных, которую конвейер читает. Честная формулировка цели:
|
||
**`av-dev-pm` не зовёт пайплайн и не требует его наличия**. Она выполняется.
|
||
|
||
**MM. Настоящая протечка была одна — необъявленная деградация опор приёмки.**
|
||
«Стимулы» в `session` и приёмка в `sprint.md` держались на «сохранённом отчёте
|
||
триажа» по конкретному OpenSpec-пути. В проекте без конвейера ревью защита от
|
||
занижения урожая исчезала **молча**: сверять не с чем, а текст об этом не
|
||
говорил. Теперь опора названа абстрактно («независимый отчёт ревью»), путь
|
||
`av-dev-pipeline` дан как частный случай, а отсутствие конвейера обязано
|
||
попадать строкой в доклад спринта.
|
||
|
||
**NN. Ветка деградации шага 9 была неисполнима — ровно в том случае, ради
|
||
которого написана.** «Плагина нет — открой
|
||
`av-dev-pm/skills/canon/references/canon.md`»: путь в дерево маркетплейса, из
|
||
проекта без установленного плагина не разрешается ниоткуда. Кросс-плагинные
|
||
пути в дерево маркетплейса теперь не используются вообще: пайплайн ходит в
|
||
**свой** `references/project-facts.md`, а ссылки в чужой плагин даются через
|
||
`Skill <плагин>:<скилл>`.
|
||
|
||
### Что из этого следует
|
||
|
||
47. **Знаниевый цикл есть и он законен, но каждый его контракт обязан иметь
|
||
единственный дом.** Пайплайн описывает раскладку `pm`, `pm` описывает
|
||
артефакты пайплайна — пять симметричных контрактов, из них два уже
|
||
разошлись: форма журнала дефектов (шесть полей против пяти, «Причина»
|
||
потеряна) и список читателей `docs/research/` (`specs` выпал). Дома
|
||
назначены: форма журнала — у конвейера, список читателей — у канона; в обеих
|
||
копиях стоит явное указание на дом.
|
||
48. **Пайплайн больше не называет внутренние имена файлов `pm`.** `items/<slug>.md`
|
||
и `SPRINT.md` в его тексте были вторым домом для раскладки, которую проект
|
||
вправе переименовать через `docs/.pm.json`.
|
||
49. **Описания плагинов в манифестах врали умолчанием.** Ни `marketplace.json`,
|
||
ни `plugin.json` не говорили, что `av-dev-pm` для конвейера **опционален**, а
|
||
задача принимается текстом. Теперь говорят — это первое, что читает человек,
|
||
выбирая, что подключать.
|
||
|
||
## 12. Механическая проверка копий (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Разделение плагинов оставлено (тема 11), но цена его названа: пять симметричных
|
||
контрактов в двух домах, два уже разошлись — форма журнала дефектов потеряла в
|
||
копии поле «Причина», список читателей `docs/research/` потерял `specs`. Оба раза
|
||
копия выглядела актуальной, и оба раза расхождение прошло мимо трёх ревью подряд.
|
||
|
||
### Решено
|
||
|
||
**OO. Копия допустима, но обязана быть дословной и помеченной.** Разметка —
|
||
HTML-комментарии, невидимые в отрендеренном markdown: `<!-- дом: <id> -->` …
|
||
`<!-- /дом: <id> -->` и `<!-- копия: <id> из <путь> -->` … `<!-- /копия: <id> -->`.
|
||
`scripts/copies.py` требует побайтового совпадения текста между маркерами.
|
||
|
||
*Почему комментарии, а не манифест копий отдельным файлом:* маркер уезжает в
|
||
репозиторий проекта вместе со скелетом, и там он **полезен** — говорит читателю,
|
||
что у текста есть дом и правится он там. Манифест остался бы в маркетплейсе и
|
||
проекту ничего не сказал.
|
||
|
||
**PP. Идентификатор строгий — буквы, цифры, дефис — и повторяется в закрывающем
|
||
маркере.** Иначе документация о самом механизме объявляет дом и роняет проверку:
|
||
это случилось на первом же прогоне, `README.md` объявил дом примером. Теперь
|
||
пример пишется `<id>`, угловые скобки под шаблон не подходят.
|
||
|
||
**QQ. Ограда блока кода в сверку не входит.** В доме текст обрамлён своей ```,
|
||
а в скелете тот же текст лежит внутри чужой, объемлющей ограды. Сверяется
|
||
содержимое, а не разметка вокруг него.
|
||
|
||
**RR. Коды выхода — общий словарь** (0 сошлось, 1 расхождение, 2 разметка,
|
||
3 не тот каталог, 4 сбой). Третий скрипт репозитория, и третий по тем же кодам.
|
||
|
||
### Что из этого следует
|
||
|
||
50. **Помечены два контракта:** форма записи журнала дефектов (дом — конвейер
|
||
ревью, копия — скелет канона; это кросс-плагинная пара) и «когда заводить
|
||
ADR» (дом — канон, копия — его же скелет). Второй пришлось сперва **сделать**
|
||
дословным: копия говорила «обязателен статус», дом — «обязателен статус
|
||
„заменено на"», и это ровно тот класс, который и ищется.
|
||
51. **Чего проверка не ловит — копию, которую забыли пометить.** Помечать
|
||
остаётся решением человека, и это названо в `README.md` вслух: иначе зелёный
|
||
прогон читался бы как «копий больше нет».
|
||
52. **Дом без копий — расхождение, а не замечание.** Маркер, обещающий
|
||
дисциплину, за которой не за чем следить, — такая же ложная запись, как
|
||
разошедшаяся копия.
|
||
53. **Запись в журнал версий канона проверка не заменяет.** Она видит, что копия
|
||
отстала, но не видит, что проект уже унёс старую версию к себе. Это остаётся
|
||
на человеке и сказано в обоих домах.
|
||
|
||
## 13. Секции `PLAN.md` переименованы (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Секции назывались **«линия»** и **«кусты»** — метафора, требующая расшифровки
|
||
при каждом употреблении. В текстах она и расшифровывалась: «звено упорядоченной
|
||
линии продукта», «тематический куст — цель, в последовательность не встающая».
|
||
Если название приходится объяснять рядом с каждым употреблением, объясняет не
|
||
название.
|
||
|
||
### Решено
|
||
|
||
**SS. «порядок» и «темы».** Заголовок называет ровно то свойство, которым секции
|
||
различаются: в первой очередь значима и обоснована прозой, во второй порядка нет
|
||
вовсе. Расшифровывать нечего — правило написано в самом имени.
|
||
|
||
**TT. Записи в журнал версий канона не требуется — канон этих имён не знает.**
|
||
`canon.md` называет файл `docs/tasks/PLAN.md` и ничего не говорит о его секциях:
|
||
их дом — заголовки `##` индекса, а умолчание живёт в `tasks.py`. Версия канона
|
||
поэтому не меняется, и проект вправе называть секции по-своему. Причина названа
|
||
вслух, потому что соблазн повысить версию «на всякий случай» здесь сильный, а
|
||
повышение обязало бы каждый проект что-то делать — при том что делать нечего.
|
||
|
||
### Что из этого следует
|
||
|
||
54. **Умолчание одно и живёт в `DEFAULT_PLAN_SECTIONS`.** Имена секций
|
||
по-прежнему настраиваются `--plan-sections`, а домом остаются заголовки `##`
|
||
индекса — переименование не трогает механику, только умолчание и тексты.
|
||
55. **Метафора — плохое имя для секции индекса.** Секция читается человеком без
|
||
контекста, часто из вывода `list`, и второго шанса объяснить себя у неё нет.
|
||
|
||
## 14. Умолчания режимов прогона перевёрнуты (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Оба скилла держали одно и то же умолчание — «по очереди», — хотя цена очереди у
|
||
них разная. `review-pipeline` гнал проходы последовательно и требовал для
|
||
параллельности **двух** условий (явная просьба **и** поимённо названный набор).
|
||
`task-batch`, наоборот, планировал волны параллельных задач с потолком 2–3 и
|
||
считал параллельность нормой прогона.
|
||
|
||
Перепутаны оказались уровни. Проход ревью — чтение и рассуждение: он ничего не
|
||
поднимает, ни за что не дерётся и по построению не видит выводов соседа. Задача
|
||
батча — полный цикл пайплайна: гейт, поднятие сервиса вживую, вложенное ревью,
|
||
общие порты и рабочие каталоги. Дешёвое стояло в очереди, дорогое гонялось разом.
|
||
|
||
### Решено
|
||
|
||
**UU. В ревью умолчание — параллельно.** Стадии по-прежнему идут по порядку,
|
||
параллельность касается только проходов внутри стадии. Последовательно гоняем по
|
||
трём особым причинам, и каждая называется в отчёте: сказал оператор; проходы
|
||
меряют; машина занята — причём занятость видит вызывающий, а не конвейер. Просьба
|
||
«гони последовательно» **набора не требует**: очередь ничего не портит, она
|
||
только дольше, и домысливать тут нечего — в отличие от прежнего правила, где
|
||
неназванный набор блокировал отступление.
|
||
|
||
**VV. Меряющая пара — правило стадии, а не решение прогона.** `adversary` и `ops`
|
||
идут по очереди всегда: оба доказывают находки числами и оба меряют одно железо, а
|
||
испорченный оракул хуже отсутствующего. Общее «гони параллельно» этого не
|
||
отменяет; отменяет только прямое слово оператора **про эту пару**, и тогда в
|
||
границы покрытия идёт строка про замеры под соседней нагрузкой.
|
||
|
||
**WW. В батче умолчание — по одной задаче, параллельность — по графу
|
||
зависимостей.** План собирается как граф (рёбра — жёсткие зависимости и
|
||
сериализуемые пересечения) и в умолчании линеаризуется в один порядок. Просьба
|
||
«гони параллельно» разрешает использовать **ширину графа**, а не гнать всё разом:
|
||
потолок 2–3, замеряющая задача — волной по одной. Прежние правила волн сохранены
|
||
целиком, они просто перестали быть умолчанием.
|
||
|
||
### Что из этого следует
|
||
|
||
56. **Режим батча задаёт режим ревью внутри задачи, и его называет charter.**
|
||
Батч идёт по одной — машина свободна, сабагент гонит проходы параллельно;
|
||
батч идёт волнами — сабагенту предписан последовательный режим с этой самой
|
||
причиной. Сабагент своего соседа не видит, поэтому решать это ему нельзя.
|
||
57. **Ранний выход из ревью переехал на границу стадии.** Стадии идут по порядку
|
||
в любом режиме, так что остановиться между ними можно всегда; остановка
|
||
**внутри** стадии осталась побочной выгодой последовательного режима — но не
|
||
поводом его выбирать.
|
||
58. **Цена параллельного батча проверяется до первой волны.** Тесты, делящие
|
||
фиксированный порт или файл БД, и проект, умеющий поднимать один экземпляр, —
|
||
основание гнать по одной даже после просьбы, сказанное строкой: просьба была
|
||
про параллельность, а не про сломанные тесты.
|
||
|
||
## 15. Порядок проходов ревью — граф зависимостей (2026-08-03)
|
||
|
||
### Что было
|
||
|
||
Решение 14 перевернуло умолчание, но оставило порядок в прежней форме: «стадии
|
||
идут по порядку номеров, параллельность — только внутри стадии». Номер стадии при
|
||
этом ничего не означает: между стадиями 1–4 ни один проход не читает вывод
|
||
другого, так что очередь между ними была платой ни за что. А правило про замеры
|
||
держалось на **двух именах** — `adversary` и `ops`, — и рассыпалось бы в тот
|
||
день, когда мерить начнёт третий проход или проект добавит свой.
|
||
|
||
### Решено
|
||
|
||
**XX. Порядок задаёт граф; стадии остаются единицей состава.** Профиль
|
||
по-прежнему набирается стадиями, но запускается всё, у чего закрыты входящие
|
||
рёбра. Рёбер три вида, и смешивать их нельзя: **зависимость** (гейт → все
|
||
опиниативные, все проходы → триаж), **конфликт за ресурс** (ненаправленный, между
|
||
теми, кто держит машину), **барьер стоимости** (только `deep`).
|
||
|
||
**YY. Сериализует ресурс, а не имена.** Пометка «держит машину» — таблицей в
|
||
скилле: `gate`, `adversary`, `ops`, `triage`; читают и рассуждают — `specs`,
|
||
`code`, `reimpl`, `architecture`, `rubric`. Проект вправе пометить свой проход в
|
||
`docs/review.md`; снимать пометку с перечисленных нельзя. Правило теперь
|
||
самораспространяется: начнёт проход мерить — попадёт в цепочку по факту, а не по
|
||
поправке.
|
||
|
||
**ZZ. Ранний выход заменён барьером стоимости.** Он стоит там, где ранний выход
|
||
зарабатывал: перед `reimpl` (пишет реализацию целиком) и `architecture`. В
|
||
`quick`/`standard` барьера нет — стадий 3–4 там не бывает; в `design` нет по
|
||
другой причине — предметом там и является форма, защищать нечего.
|
||
|
||
**AAA. Ребро — это порядок, никогда не данные.** В обычном графе задач ребро
|
||
тянет за собой вывод предшественника; здесь это запрещено: проход, увидевший
|
||
чужие находки, соглашается с ними, и декорреляция — вся ценность конвейера —
|
||
обнуляется. Сказано в самом правиле, потому что графовый словарь провоцирует
|
||
ровно эту ошибку. Исключение одно и оно же сток: триаж.
|
||
|
||
**BBB. Диаграммы в скиллах — `mermaid`.** Граф, описанный прозой, читается как
|
||
инструкция и теряет форму; диаграмма показывает её целиком. В конвейере четыре:
|
||
общий граф прогона, граф профиля `design`, пример графа задач батча, веер
|
||
финальной сверки.
|
||
|
||
**Критерий, где диаграмма уместна: структура — граф или автомат, и проза
|
||
вынуждена его пересказывать.** По этому критерию диаграммы заведены ещё в шести
|
||
местах: жизненный цикл записи по индексам (`tasks`), четыре шага сессии с
|
||
причинами на рёбрах (`session`), исходы задачи в спринте (`sprint.md`), одиннадцать
|
||
шагов пайплайна с развилкой «тривиальная» (`task-pipeline`), храповик промоута с
|
||
обратным ребром (`promote.md`), счётчик `retune` до `drop` (`calibration.md`) и
|
||
граф вызовов между плагинами (`README.md`). Где структура — таблица соответствий
|
||
(чек-лист синка в `docs`, профили ревью, коды выхода), диаграмма не заводится:
|
||
она бы дублировала таблицу и разошлась с ней. Все диаграммы прогоняются через
|
||
`mermaid-cli` перед коммитом — синтаксическая ошибка в блоке не видна при чтении
|
||
и молча ломает рендер.
|
||
|
||
### Что из этого следует
|
||
|
||
59. **Триаж — сток по определению, а не «стадия 5».** Отсюда без отдельного
|
||
обоснования следует правило, которое раньше приходилось защищать: на неполном
|
||
графе триаж не запускается, потому что агрегировал бы половину и выглядел бы
|
||
полным.
|
||
60. **Словарь рёбер общий у ревью и батча.** «Жёсткая зависимость» и
|
||
«сериализуемое пересечение» в `task-batch` — те же два вида рёбер;
|
||
формулировки сведены, и в обоих скиллах стоит ссылка на другой.
|
||
61. **Значения режима стали `по графу` и `линейно`.** Прежние «параллельно» и
|
||
«последовательно» описывали способ запуска, а не структуру; линеаризация
|
||
осталась отступлением с тремя причинами (оператор, занятая машина, разбор
|
||
самого конвейера).
|
||
62. **Проход, держащий машину, знает об этом из своего charter'а.** `adversary` и
|
||
`ops` получили по абзацу: цепочка гарантирует им чистое железо, значит их
|
||
число — оракул, и шум в нём объясняется замером, а не соседом.
|
||
63. **У каждой диаграммы объявлено старшинство — это цена второго дома.** Схема
|
||
и проза вокруг неё описывают один факт, и разойтись они могут молча: то
|
||
самое, против чего написан `copies.py`. Механической сверки здесь нет —
|
||
дословного соответствия между текстом и графом не существует, — поэтому
|
||
работает объявление: **в `review-pipeline` старший граф** (он и есть алгоритм
|
||
планировщика, проза объясняет рёбра), **в остальных местах старшая проза**
|
||
(диаграмма там сводка). Для агента это не философия: без объявления он идёт
|
||
за тем, что конкретнее, то есть чаще за схемой.
|
||
64. **Рендер диаграмм проверяется скриптом, а не памятью автора.**
|
||
`scripts/diagrams.py` вынимает все блоки `mermaid` и гонит их через
|
||
`mmdc` или `npx @mermaid-js/mermaid-cli`; коды выхода — общий словарь, нет
|
||
рендерера — код 3, а не молчаливый успех. Причина та же, что у остальных
|
||
проверок репозитория: **ошибка в блоке не видна при чтении** — текст
|
||
правдоподобен, дифф разумен, падает только рендер. Расхождение с прозой
|
||
скрипт не ловит и не притворяется, что ловит: это работа правила 63.
|
||
|
||
## 16. Каталог вместо файла в `docs/` — отложено до переезда healthlog (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Вопрос: разрешить документам в корне `docs/` быть не только файлом, но и
|
||
каталогом — когда документ описывает несколько принципиальных решений или
|
||
перерастает 400–500 строк. Паспорт остаётся файлом в любом случае: компактность
|
||
и есть его функция.
|
||
|
||
Механизм в каноне уже работает — `conventions/`, `research/`, `adr/` каталоги с
|
||
обязательным `README.md`-индексом, — так что вопрос не «можно ли», а «от чего
|
||
лечим».
|
||
|
||
### Решено
|
||
|
||
**CCC. Порог в строках триггером не становится.** Замер по проектам: у порога
|
||
ровно один документ — `healthlog/docs/architecture.md`, 1662 строки. В нём
|
||
десять маркеров долга, а разделы — «Слои гранулярности» (211 строк), «Тренировки
|
||
и прочие секции» (222), «Условный запрос», «Свёртка и размер ответа», «Форма
|
||
ответа». Это **поведение**, чей нормативный дом `openspec/specs/`, где у проекта
|
||
уже лежат пять capability. Остальные документы 108–438 строк, `jellybit` — 169.
|
||
Порог сработал бы ровно там, где надо не разносить, а доводить переезд, и дал бы
|
||
долгу постоянное жильё: разложить 1662 строки по файлам дешевле, чем вынести их
|
||
в спеки, а после раскладки давление исчезнет и второй дом поведения останется
|
||
навсегда.
|
||
|
||
**DDD. Шов выноса — другой читатель или другой срок жизни, а не размер.** По
|
||
этому критерию кандидатов два. `review.md` — сильнее прочих: у него уже записаны
|
||
два раздела с разными сроками жизни, настройка конвейера стабильна и читается
|
||
проходами, а журнал дефектов растёт неограниченно. `architecture.md` — по шву
|
||
«окружение, деплой, наблюдатель», у которого отдельный читатель `ops`. А вот
|
||
расщепление архитектуры **по принципиальным решениям отвергнуто**: у факта
|
||
«почему решено так» дом `adr/`, и вынесенные разделы немедленно станут его
|
||
вторым домом.
|
||
|
||
**EEE. `security.md` и `passport.md` каталогом не становятся.** У `security.md`
|
||
ценность именно в цельности: периметр первой строкой и «что вне модели» читаются
|
||
враждебным проходом за один раз, а разнесённые — расходятся первыми. У
|
||
`database.md` механизм заводить не под что: 241 и 211 строк.
|
||
|
||
**FFF. Если вводить — точка входа остаётся одна.** `docs/architecture.md`
|
||
упомянут в репозитории 66 раз: девять charter'ов, карта `project-facts.md`,
|
||
`docs.py`, скелеты. Развилка «файл или каталог» размножится на девять «прочитай
|
||
либо обойди». Поэтому форма жёсткая: каталог легален только при
|
||
`<имя>/README.md`, и он **и есть** прежний документ — обзор целиком со ссылками
|
||
на вынесенное, а не оглавление к нему. Каждый файл каталога обязан быть достижим
|
||
ссылкой из `README.md`; это проверяется сегодняшним механизмом ссылок `docs.py`
|
||
и ловит файл-сироту. Вынеся раздел, `README.md` на него **ссылается, а не
|
||
пересказывает** — тот же приём, которым в архитектуре уже описаны компоненты со
|
||
ссылкой на capability.
|
||
|
||
**GGG. Решение отложено до конца переезда `healthlog` (шаг 2 TODO).** Порядок:
|
||
довести поведение в спеки, замерить остаток. Жмёт после этого — вводить каноном
|
||
версии 3, и сразу для `review.md` и `architecture.md`, а не для всех документов
|
||
корня скопом.
|
||
|
||
### Что из этого следует
|
||
|
||
65. **Цена изменения — версия канона, а не правка одного файла.** Обратной
|
||
совместимости у канона нет, поэтому в счёт входят: `docs.py` (`check_stray` с
|
||
его `ALLOWED_FILES`/`ALLOWED_DIRS`, `check_required` — обязательный путь
|
||
становится развилкой, `check_capabilities` — сегодня читает ровно один файл),
|
||
`skeletons.md`, `project-facts.md`, девять charter'ов, запись в
|
||
`changelog.md` канона и ветка `upgrade` в скилле `canon`.
|
||
66. **Раздутый документ канона — сначала подозреваемый, потом кандидат на
|
||
вынос.** Диагностика перед раскладкой — счёт маркеров долга
|
||
(`grep -c "<!-- канон:"`) и вопрос, не поведение ли это. Разложить дрейф по
|
||
файлам значит перестать его видеть.
|
||
67. **Материал для решения даёт `healthlog`, а не `jellybit`.** У второго 169
|
||
строк архитектуры — там вопрос не стоит вовсе, и принимать по нему решение
|
||
значит принимать его без предмета.
|
||
|
||
## 17. Разбор заметок: ступень ревью, род работы, роадмап (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Семь заметок из `NOTES.md`, накопленных по ходу работы: переименование
|
||
`PLAN.md`, тип у каждой задачи, цвета сабагентов по модели, кавычки во
|
||
фронтматтерах, уровни ревью для проекта, задачи в терминах функций и границ,
|
||
язык задач без англицизмов. Разного размера и из разных мест, но три из них
|
||
оказались об одном — **о том, можно ли оценить задачу, не открывая код**.
|
||
|
||
### Решено
|
||
|
||
**HHH. Цвет charter'а кодирует модель, а не роль прохода.** Раскладка
|
||
`sonnet` → green, `opus` → yellow, `fable` → red. Роль прохода видна из имени, а
|
||
стоимость прогона — ниоткуда; цвет, розданный по ролям, не отвечает ни на один
|
||
вопрос, который задают во время прогона. Дом раскладки — таблица «Модель по
|
||
проходу» в `review-pipeline/SKILL.md`.
|
||
|
||
**III. Фронтматтеры проверяются машиной, а не вниманием.** Три описания из
|
||
четырнадцати содержали `: ` в незакавыченном значении — для YAML это вложенное
|
||
отображение, то есть синтаксическая ошибка, которую **нельзя увидеть чтением**:
|
||
текст читается правильно. Тот же класс, что у mermaid-диаграмм, и лечится тем же
|
||
способом — `scripts/frontmatter.py`. Он же держит раскладку цветов (HHH) и
|
||
сверку `name` с именем каталога.
|
||
|
||
**JJJ. Между `standard` и `deep` заведена ступень `wide`.** *(содержание
|
||
триггеров пересмотрено темой 18, TTT: миграция схемы и публичный контракт ступень
|
||
не поднимают.)* Прыжок стоил самого
|
||
дорогого прохода конвейера, а платить приходилось за одну архитектурную находку:
|
||
изменений, которые трогают публичный контракт, но не вводят нового правила
|
||
слияния, — большинство. `wide` — это `standard` плюс `architecture` (вход шире
|
||
диффа, отсюда имя), семь проходов против восьми у `deep`.
|
||
|
||
**KKK. Триггер независимой реализации стал триггером профиля.** Раньше условие
|
||
«изменение вводит новое правило идентичности, слияния или разбора» стояло
|
||
**внутри** `deep`, и профиль означал то семь проходов, то восемь. Реестр состава,
|
||
который «сверяется взглядом до коммита», проверять было нечем: у профиля не было
|
||
одного правильного ответа. Теперь условие выбирает профиль, а `reimpl` в `deep`
|
||
безусловен — и он единственное, чем `deep` отличается от `wide`.
|
||
|
||
**LLL. Барьер стоимости остался только в `deep`.** В `wide` за ним стоял бы один
|
||
дешёвый проход с потолком в 3 находки, а барьер не бесплатен — он сериализует то,
|
||
что могло идти разом. Вторая причина помельче: барьер спрашивает «выживает ли
|
||
форма изменения», а `architecture` — как раз тот, кто на этот вопрос отвечает.
|
||
|
||
**MMM. Род работы — вторая ось типа, и живёт тегом.** Тип записи
|
||
(`goal`/`idea`/`epic`/`task`) отвечает «что это за запись», род
|
||
(`feature`/`fix`/`chore`/`research`) — «какого рода работа». В один префикс их не
|
||
свести: идея бывает *про* функцию, эпик функцией *и является*. Дом — тег
|
||
`kind:<род>`, потому что теги здесь и есть единственный механизм разметки, а
|
||
`list --kind` работает даром. Принятая цена: в строку индекса род не попадает
|
||
(индексы производны), и состав набора по роду виден командой, а не глазами.
|
||
Словарь **закрыт** — открытый разъехался бы на синонимах `bug`/`bugfix`/`fix`.
|
||
|
||
**NNN. У `chore` тест готовности ослаблен честно.** Вопрос «что станет наблюдаемо
|
||
иначе» для обслуживания отвечается разработчику, а не пользователю. Пока рода не
|
||
было, такие задачи либо не заводились, либо придумывали себе пользовательскую
|
||
пользу — и это второе хуже: оно проходит проверку.
|
||
|
||
**OOO. Задача называет границы, а не намерения.** Раздел «Затрагивает» —
|
||
эндпоинт, таблица и миграция, формат на диске, публичный тип пакета. Без него
|
||
задача оценивается по объёму текста, а не по объёму поверхности, и оценка
|
||
систематически занижена ровно там, где текст короткий, а границ много. Механизм
|
||
проверяет **наличие** непустого раздела: полноту перечня машина не видит, и
|
||
делать вид, что видит, хуже, чем не проверять.
|
||
|
||
**PPP. Род и границы требуются к взятию в спринт, а не к заведению.** Тот же
|
||
приём, что уже работает для критериев приёмки, и по той же причине: беклог
|
||
пополняется чаще, чем разбирается, а требование на входе выгоняет в заметки то,
|
||
что должно лежать задачей. `check` о пропаже напоминает замечанием — иначе два
|
||
живых проекта покраснели бы на 98 задачах, заведённых до этого решения.
|
||
|
||
**QQQ. `PLAN.md` → `ROADMAP.md`, вместе с ключом конфига и токенами команд.**
|
||
Слово «план» в репозитории значит три разных вещи — оглавление целей, план
|
||
реализации внутри задачи и `PLAN.json` разовой адаптации. Переименовано всё:
|
||
`tasks.plan` → `tasks.roadmap`, `--index plan` → `--index roadmap`,
|
||
`--plan-sections` → `--roadmap-sections`. Старый ключ в `docs/.pm.json` не
|
||
игнорируется молча — скрипт останавливается и называет переименование.
|
||
|
||
### Что из этого следует
|
||
|
||
68. **Версия канона 3 занята этим изменением.** Отложенное решение темы 16
|
||
(каталог вместо файла в `docs/`) вводится теперь версией **4**, а не 3.
|
||
69. **Род работы ничего не предписывает конвейеру.** Профиль ревью выбирается по
|
||
факту изменения: `chore` бывает миграцией схемы, `fix` — правкой публичного
|
||
контракта. Правило «предписание процесса в теле задачи снимается» родом не
|
||
отменяется, а подтверждается.
|
||
70. **Проверка фронтматтеров — третья проверка репозитория того же класса.**
|
||
Копии, диаграммы, фронтматтеры: всё это ошибки, невидимые при чтении. Класс
|
||
опознаётся по признаку «диff выглядит разумно, а результат ломается», и
|
||
каждый его представитель получает скрипт, а не пункт чек-листа.
|
||
71. **Ступеней профиля четыре, и правило выбора читается сверху вниз.** Первое
|
||
сработавшее условие и есть ответ: правило слияния → `deep`, контракт или
|
||
схема → `wide`, видимое снаружи поведение → `standard`, иначе `quick`.
|
||
|
||
## 18. Ступень поднимает проход, а не риск (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Наблюдение с живых проектов: полный набор проходов гоняется чаще, чем оправдано —
|
||
архитектура и независимая реализация нужны заметно реже, чем запускаются. Развилка
|
||
названа сразу: крупные задачи с частым полным ревью либо мелкие и средние задачи
|
||
со средним ревью. Выбран второй путь.
|
||
|
||
Разбор показал, что размер задач — только половина причины, и не главная.
|
||
|
||
### Решено
|
||
|
||
**RRR. Профиль — максимум по поверхности, а не средневзвешенное.** Условия
|
||
читаются сверху вниз, первое подошедшее отвечает за весь дифф. Значит цена ревью
|
||
растёт быстрее размера задачи: на крупной задаче верхний профиль оплачивается в
|
||
том числе за ту её часть, которая сама по себе была бы `quick`. Это и есть
|
||
механизм, ради которого выбран путь мелких задач.
|
||
|
||
**SSS. Ступень поднимает то, что даёт работу новому проходу, а не то, что кажется
|
||
рискованным.** Правило вывода, по которому спорные случаи решаются без нового
|
||
списка. Проверка нынешних триггеров этим правилом:
|
||
|
||
| Триггер | Кто закрывает | Где этот проход |
|
||
| --- | --- | --- |
|
||
| миграция схемы | `gate` (шаг миграций), `ops` (миграция под потоком, откат при двух версиях) | уже в `standard` |
|
||
| публичный контракт | `specs`, направление `code → spec` | во всех профилях |
|
||
| инвариант проекта | основание для `critical` у любого прохода | во всех |
|
||
| новый пакет, новое понятие | `architecture` | только `wide` |
|
||
| новое правило слияния | `reimpl` | только `deep` |
|
||
|
||
Три верхних триггера не добавляли ни одного прохода — они поднимали ступень «на
|
||
всякий случай». На проекте с базой и эндпоинтами это делало верхнюю ступень
|
||
умолчанием, то есть правило объявляло исключением то, что происходит всегда.
|
||
|
||
**TTT. Миграция схемы, публичный контракт и инвариант уехали в `standard`.**
|
||
`wide` теперь означает ровно одно: изменение вводит **новое понятие или
|
||
структурную единицу** — новый пакет или слой, новая точка входа, второй способ
|
||
делать то, что уже делается, перенос ответственности между узлами. Добавленное
|
||
поле в существующем ответе концептом не является. Это **отменяет часть JJJ темы
|
||
17**: ступень `wide` остаётся, её содержание меняется. Проект, где изменение
|
||
контракта и правда архитектурное (публичный SDK, чужие потребители), поднимает
|
||
его сам в `docs/review.md` — уточнением, а не возвратом прежнего умолчания.
|
||
|
||
**UUU. Чекпоинт `design` получил то же условие.** `review-specs` в режиме «дизайн
|
||
ДО кода» идёт всегда — это самый дешёвый чекпоинт конвейера. `review-rubric` и
|
||
`review-architecture` — только при новом понятии. Причина арифметическая: чекпоинт
|
||
стоит на **каждой** задаче, поэтому при мелкой нарезке три прохода умножаются на
|
||
число задач и становятся самой большой статьёй. Причина по существу та же, что в
|
||
SSS: рубрика на узел без нового понятия порождает свойства уже существующего
|
||
рода, записанные конвенциями и спеками.
|
||
|
||
**VVV. Шов нарезки — граница, за которой падает ступень.** Тест декомпозиции
|
||
отвечает, **допустим** ли разрез; шов отвечает, **где** его провести. Раздел
|
||
«Затрагивает» перечисляет границы; строка, поднимающая ступень выше остальных, и
|
||
есть кандидат на отдельную задачу.
|
||
|
||
**WWW. Костяк из четырёх проходов платится за каждую задачу.** Гейт, спеки, код,
|
||
триаж несокращаемы, поэтому разрез, после которого обе половины остаются в одной
|
||
ступени, делает ревью **дороже**: тот же объём тем же составом, но костяк оплачен
|
||
дважды. Резать — когда разрез снимает дорогой проход с большей части диффа.
|
||
|
||
**XXX. Верхняя ступень задана тестом, а не списком.** «Идентичность, слияние,
|
||
разбор» — формулировка, пришедшая из одного проекта, и в общем виде она не
|
||
читалась: вопрос «как это применить к моему проекту» не имел ответа в тексте.
|
||
Теперь класс задан тремя условиями, независимыми от домена и языка: вариантов
|
||
несколько и оба защитимы; спека между ними не выбирает; неверный выбор не падает,
|
||
а молча меняет смысл данных. Отрицательный тест сильнее положительных — то, что
|
||
красит гейт или роняет запрос, в класс не входит. Три слова остались как **три
|
||
места**, где такие правила водятся (граница входа данных и место их встречи), а
|
||
проект перечисляет свои места в `docs/review.md` — перечень производен от теста и
|
||
не расширяет класс.
|
||
|
||
Оговорка, без которой правило вырождается: триггер — **новое или изменённое по
|
||
существу правило**, а не код рядом с ним. Проект, чей домен и состоит из таких
|
||
правил, иначе оказывался бы в `deep` всегда — та же болезнь, от которой лечилась
|
||
ступень `wide`.
|
||
|
||
### Что из этого следует
|
||
|
||
72. **Порога в числе границ не заводится.** Тот же принцип, что в теме 16 (CCC):
|
||
размер не триггер. Шов проходит по скачку ступени, а не по длине перечня.
|
||
73. **Ступень — признак для планирования, но не запись в задаче.** Строка «делать
|
||
профилем standard» в теле — тот самый второй дом правила выбора, который
|
||
снимает гигиена полей. Профиль выбирает тот, кто видит изменение.
|
||
74. **Дешёвое место заметить разнородную задачу — показ набора спринта.** Там
|
||
«Затрагивает» уже написан, а предложение об изменении ещё не заведено: разрез
|
||
стоит одного `edit` вместо выброшенного предложения.
|
||
75. **Замер остаётся за обкаткой.** Правило выведено из состава проходов, а не из
|
||
статистики прогонов: считать, какая доля задач попадает в каждую ступень,
|
||
можно только на спринтах нового процесса (TODO шаг 4).
|
||
76. **Отсутствие верхней ступени — законное состояние проекта.** Бывают проекты,
|
||
где данные приходят нормализованными, ничего ни с чем не сливается, а внешних
|
||
форматов нет: `deep` там не срабатывает никогда, и придумывать ему повод не
|
||
надо. Раньше это читалось как недонастройка.
|
||
77. **Ступень определяет класс правила, а не вид работы.** Миграция схемы —
|
||
`standard`, но миграция, переносящая данные по правилу («сложить дубли»,
|
||
«привести к одному виду перед сравнением»), несёт правило идентичности и
|
||
потому `deep`. Одно слово в описании задачи попадает в разные ступени — это не
|
||
противоречие, смотрят не на слово.
|
||
|
||
## 19. Роадмап — состояние проекта, а не очередь работ (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Основной инструмент владельца — роадмап и набор целей: «на каком этапе проект,
|
||
что сделали и что осталось». Оценка идёт по **поведению**, а не по внутреннему
|
||
устройству: что приложение уже может делать и чего ещё не может. Отсюда
|
||
требование к формулировкам: цель отвечает на «что приложение будет делать»,
|
||
задача — на «что для этого нужно сделать».
|
||
|
||
Разбор показал, что инструмент отвечал ровно на половину этого вопроса.
|
||
|
||
### Решено
|
||
|
||
**YYY. Достигнутая цель из роадмапа не исчезает.** `close --implemented` удалял
|
||
у цели и файл, и строку — роадмап по построению показывал только «что осталось».
|
||
Свидетельство нашлось в самом роадмапе healthlog: там руками заведена секция «Что
|
||
уже пройдено» на двадцать строк прозы, и заканчивается она фразой «Эти звенья
|
||
целями не заведены: закрытая цель записи не оставляет, ей хватает коммита и
|
||
спеки». Обходной путь и его причина записаны рукой владельца. Теперь строка с
|
||
датой переезжает в секцию достигнутого; файл удаляется по-прежнему.
|
||
|
||
Вторым домом поведения это не делает: нормативное поведение живёт в
|
||
`openspec/specs/`, роадмап отвечает **когда и в каком порядке** оно появилось —
|
||
другой вопрос. Ссылки на файл в строке нет намеренно: файла больше нет, а битая
|
||
ссылка это законная ошибка `check`. Форма строки — как в `REJECTED.md`, и по той
|
||
же причине.
|
||
|
||
**ZZZ. Цель — возможность приложения, задача — шаг к ней.** Заголовок цели
|
||
отвечает на «что приложение будет уметь»: не «Работа со слиянием», а «Исход
|
||
слияния не зависит от порядка доставки». **Свойство поведения — тоже
|
||
возможность**: «сообщает о своём состоянии», «исход не зависит от порядка» —
|
||
законные цели, переформулировки в функцию не требуют. Единственный настоящий
|
||
чужак — работа над инструментом и процессом: на вопрос «что приложение будет
|
||
уметь» она не отвечает и живёт в отдельной секции роадмапа.
|
||
|
||
**ААА. Тест готовности задачи сменил защиту.** Требование «что станет наблюдаемо
|
||
иначе снаружи» переехало к цели. У задачи вместо него — **какую строку
|
||
«Завершения» своей цели она двигает**. «Отрефакторить X» проваливает тест не
|
||
потому, что невидим снаружи, а потому, что не находит строки, к которой
|
||
относится. Побочная выгода: видно и обратное — строка «Завершения», к которой не
|
||
относится ни одна задача, это незакрытая часть возможности. Отсюда требование к
|
||
«Завершению» быть **списком**, а не абзацем: на абзац не сошлёшься.
|
||
|
||
**БББ. Цель обязательна не у всякой задачи.** Прежнее правило — «у каждой задачи
|
||
должен быть `goal:`, иначе она не попадёт ни в один спринт» — было угрозой, а не
|
||
аргументом, и заставляло операционную работу выдумывать себе направление.
|
||
Граница проходит по роду работы: `feature` без цели не бывает (новая возможность
|
||
и есть содержание цели), `fix`, `chore` и `research` живут без цели законно и
|
||
входят в набор спринта помимо его цели. Это второй раз, когда род работы
|
||
окупается, — и первый, когда он что-то определяет за пределами отбора.
|
||
|
||
**ВВВ. Тип `[epic]` упразднён.** Зонтик между целью и задачами не нужен: зонтиком
|
||
стала цель, а слишком крупный шаг дробится на шаги помельче под ней. Замер: ноль
|
||
употреблений на 97 записей двух живых проектов, при том что тип занимал место в
|
||
словаре, тесте готовности, автомате переходов, `split.md` и трёх местах
|
||
`tasks.py`.
|
||
|
||
**ГГГ. Имена секций роадмапа — `Готово` / `Запланировано` / `Направления` /
|
||
`Разработка`.** Первый набор (`умеет` / `строим` / `станок`) прожил один заход и
|
||
был признан неудачным. Из четырёх предложенных имён отвергнуто одно, и по
|
||
проверяемой причине: **`окружение` уже занято** — в `architecture.md` это боевое
|
||
окружение приложения, «где работает, что рядом, кто перезапускает», и одно слово
|
||
в двух смыслах развело бы документы канона. Взято `Разработка`.
|
||
|
||
Принятый компромисс назван вслух: `Готово` слегка тянет обратно в трекерную рамку
|
||
«состояние работы», тогда как секция про **возможность**. Перевесила читаемость с
|
||
первого взгляда, а смысл несут заголовки целей внутри секции. Так же принято, что
|
||
цель в `Запланировано` может быть уже наполовину построена: это очередь, а не
|
||
«не начато», а «в работе» живёт в `SPRINT.md`.
|
||
|
||
**ДДД. Секции роадмапа канонические, секции беклога — нет.** Разница выведена, а
|
||
не назначена: у секций роадмапа есть **семантика** (достигнутое, очередь, долгое,
|
||
не про продукт), в первую пишет сам `close`, и роадмап, названный по-своему,
|
||
читался бы только своим автором. Секции беклога (`Ядро`, `Инфра`) семантики не
|
||
несут — это полки. Поэтому `check` проверяет у роадмапа три вещи: состав закреплён
|
||
(чужая секция — ошибка), все четыре обязаны быть, язык один на весь индекс;
|
||
`--roadmap-sections` у `init` упразднён. Английский набор — `Done` | `Planned` |
|
||
`Directions` | `Tooling`.
|
||
|
||
Проверено на том самом случае, ради которого правило и заводилось: секция «Что
|
||
уже пройдено», которую healthlog вёл руками, теперь называется ошибкой поимённо.
|
||
|
||
### Что из этого следует
|
||
|
||
78. **Ключа `tasks.achieved_section` не появилось.** Секция достигнутого
|
||
опознаётся по каноническому имени в любом из двух языков, и лишний knob не
|
||
заводится: канонический состав отвечает на тот же вопрос надёжнее конфига.
|
||
79. **`reopen` цели снимает строку достигнутого.** Иначе роадмап продолжает
|
||
утверждать, что приложение умеет то, что вернулось в работу.
|
||
80. **Прозаический раздел в индексе — дрейф.** Любой `##` проверка считает
|
||
секцией, поэтому «Что уже пройдено» и «Почему в таком порядке» в healthlog
|
||
формально были двумя лишними секциями, куда могла уехать задача. При
|
||
повышении они разбираются: звенья — строками в `Готово`, обоснование очереди —
|
||
прозой внутри `Запланировано`.
|
||
81. **Правил стало пять, и нулевое — про смысл, а не про механику.** «Цель —
|
||
возможность, задача — шаг к ней» стоит перед правилами о гниении беклога и
|
||
производности индексов, потому что из него следует, зачем эти механики нужны.
|
||
|
||
## 20. Форма записи: заголовок, секции, вычитка (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Обкатка обновлённого скилла на выдуманном проекте — консольные крестики-нолики
|
||
на JavaScript. Каталог задач заведён с нуля тем же скриптом: шесть целей, девять
|
||
задач, отказ, достижение цели, спринт. Смотрели три вещи: тексты, разделы, состав
|
||
задач.
|
||
|
||
Форма вылезла раньше содержания. Индексы вышли с секциями со строчной буквы и без
|
||
отбивки после заголовка — читается как список списков, а не как документ. А все
|
||
заголовки задач оказались **описательными**: «Лишние символы в ходе молча
|
||
отбрасываются», «Поле печатается одним куском кода», «Линтер и тесты гоняются
|
||
одной командой». Правило «задача отвечает на «что для этого нужно сделать»» в
|
||
скилле стояло с самого начала — но относилось к содержанию задачи, а не к её
|
||
заголовку, и потому не применялось там, где заголовок и есть всё, что видно в
|
||
списке.
|
||
|
||
### Решено
|
||
|
||
**ЕЕЕ. Заголовок отвечает на вопрос своего типа, и форм три.** Цель — утверждение
|
||
о возможности («Соперником может быть компьютер»); задача — глагол в
|
||
неопределённой форме, допускается «не» перед ним («Не отбрасывать молча лишние
|
||
символы в ходе»); идея — назывное, без обещания. Причина не стилистическая:
|
||
описательный заголовок называет **состояние**, а из состояния не видно, чего от
|
||
работы ждут — «Ничья объявляется, пока клетки есть» одинаково читается как жалоба
|
||
и как задание. В списке, где решают «брать или не брать», это разные вещи.
|
||
|
||
Отсюда же разница индексов: роадмап — список возможностей, беклог — список работ.
|
||
Перепутанные формы заголовков делают каждый из них похожим на другой.
|
||
|
||
**ЖЖЖ. Механизировано ровно то, что механизируется, — счётчиком, а не
|
||
замечанием.** `check` считает заголовки, у которых первое слово не оканчивается
|
||
на `-ть`/`-ти`/`-чь` (перед ним допускается «не»), и печатает **число** в блоке
|
||
здоровья. Замечанием на файл этого делать нельзя: проверка эвристическая, а
|
||
беклог, заведённый до правила, переоформляют не «заодно» — десятки одинаковых
|
||
строк научили бы пропускать весь блок.
|
||
|
||
**ЗЗЗ. Годность формулировки судит отдельный агент `doc-wording`, а не чек-лист
|
||
в скилле.** Самопроверка текста слабее всего там, где формулировка казалась
|
||
удачной при написании, — а пишет и проверяет иначе один и тот же агент в одном
|
||
контексте. Агент читает пачку записей и возвращает **готовые формулировки на
|
||
замену**, ничего не правя сам; заголовок и «зачем» подставляются командой и
|
||
показываются человеку, потому что именно по ним задачу выбирают. Он намеренно не
|
||
проверяет ничего из того, что ловит `tasks.py check`: повторить машинную проверку
|
||
словами значит завести правилу второй дом.
|
||
|
||
**ИИИ. Заголовок секции — с прописной, после него пустая строка.** Во всех
|
||
индексах, включая секции беклога, имена которых выбирает проект: правило про
|
||
**оформление**, а не про имя. Канонические имена стали писаться с прописной
|
||
(`Готово` | `Запланировано` | `Направления` | `Разработка`, англ. `Done` |
|
||
`Planned` | `Directions` | `Tooling`), сверка везде идёт по нижнему регистру, так
|
||
что старые индексы читаются по-прежнему и поднимаются `check --fix`.
|
||
|
||
**ККК. Имя секции принадлежит заголовку индекса, файл на неё только ссылается.**
|
||
Это разрешает единственную неоднозначность починки: расхождение файла и заголовка
|
||
**в одном регистре** правится в пользу заголовка. Без этого шага переезд на канон
|
||
оставил бы `Готово` в роадмапе и `готово` в каждом файле цели — расхождение
|
||
безвредное, но вечное, потому что свести его было бы некому.
|
||
|
||
### Что из этого следует
|
||
|
||
82. **Отбивка живёт на записи, а не на вставке.** `spaced_sections` вызывается в
|
||
`Plan.index`, через который проходит **каждая** запись индекса. Чинить
|
||
отбивку в каждом месте вставки значило бы полагаться на то, что ни одного не
|
||
забыли, — а мест вставки три (`--first`, `--after`, в конец).
|
||
83. **Обкатка нашла два дефекта, которых не нашли ни линтеры, ни свои проверки.**
|
||
Вставка в пустую секцию съедала отбивку перед следующим заголовком; мета,
|
||
разорванная пустой строкой, теряла поля молча, а `check` видел только
|
||
следствие («без рода работы») и советовал `edit --kind`, который дописывал
|
||
**второе** такое же поле. Оба класса теперь названы: пропуск пустых строк
|
||
идёт только до первой непустой, а поле меты в теле — ошибка с названной
|
||
причиной, которую `--fix` намеренно не чинит.
|
||
84. **Пустой проект показывает форму хуже живого.** Чтобы увидеть достигнутую
|
||
цель, отказ, спринт и все четыре рода работы, проект пришлось поставить на
|
||
середину пути. Это довод в пользу того, чтобы обкатку вести на *состоянии*, а
|
||
не на *старте*: у старта половина формы не наблюдаема.
|
||
85. **Мелкая цель даёт две задачи, и это не повод её укрупнять.** У цели
|
||
«Соперником может быть компьютер» третья задача напрашивалась (выбор уровня
|
||
соперника), но не мерджится порознь: без сильного соперника выбирать не из
|
||
чего. Тест декомпозиции её отбил — частичный ответ на вопрос «не выродились
|
||
ли цели в ярлыки тем».
|
||
|
||
## 21. Язык проектных текстов — информационный стиль (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Языковые правила лежали внутри скилла `tasks`, в разделе «Как написана задача»:
|
||
англицизмы, неизвестные термины, «сложность формулировки — не признак сложности
|
||
работы». Три пункта, выведенные из практики, без общей опоры и без ответа на
|
||
вопрос «а что ещё сюда относится».
|
||
|
||
Дал ссылку на чужой скилл `prepare-jira-text` — там раздел «Язык» с
|
||
информационным стилем, таблицей англицизмов-калек и таблицей жаргона. Заодно
|
||
попросил найти справку об информационном стиле Максима Ильяхова и адаптировать
|
||
его.
|
||
|
||
### Решено
|
||
|
||
**ЛЛЛ. У языка появился один дом — `canon/references/language.md`.** Не в
|
||
`tasks`, хотя пришёл он оттуда: правила относятся к документам канона, решениям
|
||
ADR, запискам разведки и сообщениям коммитов в той же мере, что к задачам, а
|
||
каталог задач и сам часть `docs/`. Раскладка отвечает, **где** текст лежит;
|
||
этот файл — **каким он должен быть**. `tasks/SKILL.md` оставил у себя четыре
|
||
правила, которые нарушаются чаще прочих, и ссылку.
|
||
|
||
**МММ. Инфостиль взят не целиком, и отброшенное названо вслух.** Он написан для
|
||
рекламы, статей и писем — текстов, где читателя надо удержать; проектный текст
|
||
читают потому, что надо. Взято: полезное действие, глагол вместо отглагольного
|
||
существительного, активный залог, факт вместо оценки, стоп-слова,
|
||
«одна мысль — одно предложение», параллельность, работающий заголовок.
|
||
Отброшено: **парцелляция** (рубленые фразы ломают причинную связь, а в решении
|
||
ценность именно в ней), **запрет вводных целиком** («если», «иначе», «в отличие
|
||
от» — это условия, то есть сведения), **запрет скобок и точки с запятой** (в
|
||
технической записи скобки несут уточнение — имя команды, единицы, слаг).
|
||
Многоточие запрещено: в проектном тексте оно значит «дописать позже».
|
||
|
||
Раздел «Что отброшено намеренно» написан не для полноты. Без него правило
|
||
читается как «пиши короче», и первый же агент начинает резать «поэтому» и
|
||
«иначе» — то есть ровно то, ради чего текст и писался.
|
||
|
||
**ННН. «Снять корону» переведено на здешнего читателя.** У Ильяхова это «надеть
|
||
корону на клиента». Здесь клиент — **ты сам через квартал** и тот, кто возьмёт
|
||
задачу. Отсюда конкретное требование: называть состояние и остаток, а не
|
||
пересказывать, как было интересно разбираться.
|
||
|
||
**ООО. Таблицы англицизмов и жаргона уехали в агента помеченной копией.** Устав
|
||
агента обязан быть самодостаточным — он не разрешает пути плагина и не ходит по
|
||
ссылкам, — а два дома у одного правила уже трижды расходились. Механизм для
|
||
этого в репозитории есть (`scripts/copies.py`), и это ровно его случай: копия
|
||
дословная и помеченная, проверка ловит расхождение.
|
||
|
||
### Что из этого следует
|
||
|
||
86. **У агента вычитки правил стало двенадцать, и они разделены на две группы.**
|
||
«Форма записи» верна только для каталога задач, «язык» — для любого
|
||
проектного текста. Разделение не косметическое: находки докладываются
|
||
группами и в этом порядке, потому что форма меняет решение «брать или не
|
||
брать», а язык — только цену чтения.
|
||
87. **Порог правки записан дважды и одинаково** — в `language.md` и в уставе
|
||
агента: правка без нарушенного правила не делается. Это единственная защита
|
||
от списка, в котором половина замечаний вкусовые: такой список перестают
|
||
читать целиком, и настоящие находки пропадают вместе с ним.
|
||
88. **Переезд на канон 3 языком ничего не требует.** Шаг в changelog так и
|
||
записан: прочитать и ничего не переписывать задним числом. Сплошная вычитка
|
||
старых документов стоит дороже, чем даёт, а правила применяются к тому, что
|
||
правится сейчас.
|
||
|
||
## 22. Обкатка агента вычитки: имя, охват и «так везде» (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Агента вычитки прогнали по тестовому набору — 13 записей выдуманного проекта.
|
||
Устав он читал сам, как обычный подрядчик.
|
||
|
||
### Решено
|
||
|
||
**ППП. Агент называется `doc-wording`, а не `task-wording`.** Имя пришло из
|
||
задач, но правила языка относятся ко всем проектным текстам: документам канона,
|
||
решениям ADR, запискам разведки. Форма записи — вторая половина устава — верна
|
||
только для файлов `docs/tasks/items/`, и теперь это сказано заголовком раздела,
|
||
а не подразумевается. Вход агента расширен: список файлов или каталог, вперемешку
|
||
тоже.
|
||
|
||
**РРР. «Так сделано везде» — не оправдание, а признак.** Агент нашёл, что раздел
|
||
«Затрагивает» в нескольких записях называет не только границу, но и её будущее
|
||
состояние («источник хода становится двумя»), — и **промолчал**, объяснив это
|
||
принятым стилем каталога. Записи писал один агент за один заход: систематичность
|
||
здесь значит ровно обратное — правило не применялось вовсе.
|
||
|
||
В устав добавлено: одна и та же ошибка в пяти файлах даёт **одну находку на весь
|
||
набор** с перечнем, но не даёт права промолчать. Принятым стилем считается
|
||
только то, что назвал зовущий или что записано в конвенциях проекта.
|
||
|
||
### Что из этого следует
|
||
|
||
89. **Находка агента попала в слово из собственного скилла.** «Цель про станок,
|
||
а не про игру» — метафора, которую я перенёс в тестовую запись из
|
||
`tasks/SKILL.md`. Проверка показала худшее: `станок` в каноне уже занят —
|
||
«общий станок» это красная проверка, врывающаяся в замороженный спринт
|
||
(`canon.md`, `session/SKILL.md`). Одно слово в двух смыслах, тот же класс,
|
||
что и `окружение` в теме 19. В `tasks/SKILL.md` заменено на «работа над
|
||
инструментом и процессом» — как названа и секция роадмапа.
|
||
90. **Одна находка на 13 записей — не провал вычитки.** Тексты писались сразу по
|
||
правилам, и находить в них было почти нечего. Показательно другое: агент
|
||
удержал порог (вкусовых правок не предложил) и явно сказал, по чему проверял
|
||
термины, — то есть отработали обе защиты, а не только та, что ищет.
|
||
|
||
## 23. Вычитка разделена на два прохода (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
В уставе агента вычитки стоял заголовок «Форма записи — только для
|
||
`docs/tasks/items/`». Условная половина устава: на документе канона она молчит,
|
||
на задаче включается.
|
||
|
||
### Решено
|
||
|
||
**ССС. Проходов два: `task-form` и `doc-wording`.** Разделены не по охвату — по
|
||
**глубине**. Язык проверяется по словам и фразам, поштучно, и это подметание:
|
||
залог, оценки, стоп-слова, англицизмы, жаргон. Форма записи требует понять, что
|
||
задача делает, и **открыть файл цели**, на которую она ссылается, чтобы сверить,
|
||
какую строку «Завершения» задача двигает. Слитый проход одну половину делает
|
||
дорогой, а вторую — поверхностной.
|
||
|
||
Отсюда и разные модели: `doc-wording` — sonnet, `task-form` — opus. Первый
|
||
подметает, второй судит смысл, и ровно на суждении обкатка показала провал —
|
||
агент сам себе объяснил находку «принятым стилем каталога» (тема 22).
|
||
|
||
**ТТТ. Условная половина устава — плохая конструкция сама по себе.** Правило,
|
||
которое «применяется только если», агент применяет по своему усмотрению, а
|
||
усмотрение и есть то, чего от него не ждут. Два коротких устава без условий
|
||
надёжнее одного длинного с ними — и это довод, годный за пределами этого случая.
|
||
|
||
**УУУ. Каждый устав отказывается от чужой половины прямо.** «Увидел не по своей
|
||
части — скажи строкой в границах покрытия, не находкой». Без такого отказа две
|
||
проверки одного места расходятся и начинают спорить, а разнимать их потом
|
||
дороже, чем не сводить. Исключение ровно одно и названо: неудачное слово **в
|
||
заголовке** судит `task-form`, потому что заголовок целиком его.
|
||
|
||
**ФФФ. Порог правки переехал в дом и копируется в оба устава.** Он теперь в
|
||
`language.md` помеченным домом `порог-правки`: правка без нарушенного правила не
|
||
делается, систематичность нарушения — не довод в его пользу. Дублировать его
|
||
руками в двух уставах значило бы получить два разных порога через месяц.
|
||
|
||
### Что из этого следует
|
||
|
||
91. **Шестое правило `task-form` — единственное, что читает больше одного
|
||
файла.** Оно же единственное, что смотрит **набор**, а не запись: строка
|
||
«Завершения», к которой не относится ни одна поданная задача, докладывается
|
||
отдельным блоком. Это граница между вычиткой и разбором, и она проведена
|
||
внутри правила, а не между агентами.
|
||
92. **Порядок вызова — сперва `task-form`.** Его находки меняют решение «брать
|
||
или не брать», а язык — только цену чтения; и переписанный заголовок
|
||
бессмысленно вычитывать до того, как он переписан.
|
||
93. **Помеченных копий стало шесть при пяти домах.** Механизм `scripts/copies.py`
|
||
впервые используется не для скелетов канона, а чтобы удержать одно правило в
|
||
двух уставах подрядчиков. Случай тот же: текст обязан быть на месте, потому
|
||
что подрядчик по ссылкам не ходит.
|
||
|
||
## 24. Обкатка двух проходов: два дефекта в собственных правилах (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Оба прохода запущены на тестовом наборе из 13 записей. `task-form` дал три
|
||
находки и блок «строки Завершения», `doc-wording` — пять находок. Разделение
|
||
окупилось сразу: `task-form` поймал ровно тот класс, на котором слитый агент
|
||
промолчал (границы, названные будущим состоянием, — тема 22, РРР).
|
||
|
||
Но два его правила разошлись с остальным каноном.
|
||
|
||
### Решено
|
||
|
||
**ХХХ. «Одна мысль — одно предложение» не распространяется на поля меты.**
|
||
`doc-wording` предложил разбить «зачем» надвое — а `task-format.md` требует от
|
||
«зачем» **одного предложения**: оно повторяется строкой индекса, и второму там не
|
||
поместиться. Агент честно выполнил тот документ, который читал; виноват не он, а
|
||
правило без оговорки. Оговорка записана и в доме (`language.md`), и в уставе:
|
||
тесно — сокращай, но не дели.
|
||
|
||
**ЦЦЦ. «Не своё» бывает двух родов, и поступают с ними по-разному.** Чужому
|
||
подрядчику — строкой в границах покрытия, чтобы находка не пропала. **Машинной
|
||
проверке — вообще ничего, даже строкой**: это не потерянная находка, а уже
|
||
проверенное. `doc-wording` отправил в «замечено не по моей части» открытый
|
||
вопрос в задаче — а его ловит `tasks.py check`, и строка получилась шумом,
|
||
который выглядит как работа.
|
||
|
||
### Что из этого следует
|
||
|
||
94. **Шестое правило нашло то, чего не искали.** Три строки «Завершения»
|
||
оказались **закрыты критериями задач, но не заявлены** самими задачами, а
|
||
одна строка цели (`checks-one-command`, «названа в README и в описании
|
||
работы над проектом») — закрыта наполовину. Агент назвал оба толкования и
|
||
выбирать не стал, как и велено. Выбрано сужение цели: описания работы над
|
||
проектом у выдуманной игры нет вовсе, и строка обещала то, чего негде
|
||
исполнить.
|
||
95. **Спорные находки полезны тем, что показывают спор правил, а не вкуса.**
|
||
Из пяти языковых находок три приняты сразу, две отклонены — и обе отклонённые
|
||
указывали на одно и то же место канона (правило 4 без оговорки). Вкусовых
|
||
находок не было ни одной: порог держится.
|
||
|
||
## 25. Секция `Сопровождение` и общий словарь трёх мест (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
`Разработка` — имя, которое называло слишком много: роадмап **весь** про
|
||
разработку, и секция с таким именем не отличалась от остальных ничем. Предложено
|
||
`Сопровождение` (англ. `Operations`).
|
||
|
||
### Решено
|
||
|
||
**ЧЧЧ. Секция называется `Сопровождение` / `Operations`, и её смысл расширен.**
|
||
Было «инструмент и процесс», стало «чем держат проект: инструмент, процесс,
|
||
эксплуатация». Расширение не косметическое: английское `Operations` при узком
|
||
смысле обещало бы эксплуатацию, а внутри лежал бы линтер. Либо слово, либо
|
||
смысл — сошлись на смысле, потому что метрики, логи, инфраструктура и выкладка
|
||
в эту секцию просятся и так.
|
||
|
||
**ШШШ. Версия канона не менялась, и это законно.** ~~Ни один проект на каноне 3
|
||
не стоит: healthlog и jellybit держат канон 2, повышение только предстоит.~~
|
||
|
||
**Отменено в тот же день (тема 26).** Посылка была ложной: healthlog уже переехал
|
||
на канон 3, и правка записи версии 3 задним числом переписывала то, по чему он
|
||
ехал. Правило осталось верным, применение — нет: черновиком запись версии
|
||
является ровно до того, как **первый** проект по ней поехал.
|
||
|
||
**ЩЩЩ. Сопровождение и эксплуатация — целое и часть, и словарь у трёх мест
|
||
общий.** Тема живёт в трёх документах, и раньше каждое место говорило своим
|
||
словом. Теперь: сопровождение — всё, чем держат проект (инструмент, процесс,
|
||
выкладка, метрики, логи, инфраструктура, дежурство); эксплуатация — его часть,
|
||
работа системы на проде.
|
||
|
||
| Место | Уровень | Что там |
|
||
| --- | --- | --- |
|
||
| `ROADMAP.md`, секция `Сопровождение` | план | работы, которые собираемся делать |
|
||
| `architecture.md`, раздел «Эксплуатация» | состояние | как устроено сейчас |
|
||
| эксплуатационный проход ревью | оптика | «это упало через неделю на проде» |
|
||
|
||
**Сливать три места в одно слово было бы ошибкой**: они отвечают на разные
|
||
вопросы — план, состояние, проверка. Синхронизирован **словарь**, а не границы;
|
||
дом словаря — `canon.md`.
|
||
|
||
Слово **«поддержка» запрещено вовсе**: в нём слышится помощь пользователю, а это
|
||
третья работа, к этим двум не относящаяся.
|
||
|
||
### Что из этого следует
|
||
|
||
96. **Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
|
||
сообщает о своём состоянии» — возможность (наблюдает пользователь сервиса);
|
||
«дежурный видит состояние на одном экране» — сопровождение (наблюдаем мы).
|
||
Одни и те же метрики попадают в разные секции роадмапа, и это верно.
|
||
97. **`check --fix` чужую секцию не переименовывает — и правильно.** На
|
||
переименовании `Разработка` → `Сопровождение` проверка назвала секцию
|
||
роадмапа чужой и остановилась: регистр она правит сама, смысл — нет. Ровно
|
||
то поведение, которое нужно проекту при повышении канона.
|
||
|
||
## 26. Канон 4: правка задним числом отменена (2026-08-04)
|
||
|
||
### Что было
|
||
|
||
Секцию `Разработка` переименовали в `Сопровождение` без повышения версии канона —
|
||
на посылке «ни один проект на каноне 3 не стоит» (тема 25, ШШШ). Посылка
|
||
оказалась ложной: healthlog уже переехал, `docs/.pm.json` держит `"canon": 3`, а
|
||
роадмап — секцию `Разработка` с прописной. Правка записи версии 3 переписывала
|
||
то, по чему он ехал.
|
||
|
||
### Решено
|
||
|
||
**ЭЭЭ. Запись версии — черновик ровно до первого переехавшего проекта.** После
|
||
этого она **история**, и любое изменение канона заводит новую версию, даже если
|
||
меняется одно слово. Проверять это дёшево: `grep '"canon"' */docs/.pm.json` по
|
||
живым проектам. Дорого — обратное: проект, повышенный по тексту, которого больше
|
||
не существует, невоспроизводим.
|
||
|
||
Запись версии 3 восстановлена дословно (`Разработка` | `Tooling`), переименование
|
||
уехало в версию 4. jellybit, стоящий на каноне 2, прочтёт обе записи подряд и
|
||
заведёт `Разработка`, чтобы через шаг переименовать; в шаг версии 3 добавлена
|
||
оговорка «едешь сразу на 4 — заводи `Готово` последней и не переставляй дважды».
|
||
Лишний шаг — плата за честную историю, и она мала.
|
||
|
||
**ЮЮЮ. `Готово` переехало вниз, и порядок секций стал каноническим.**
|
||
Достигнутое **копится**: через год этой секции больше, чем всех остальных
|
||
вместе, — и стоя первой она отодвигает за экран ровно то, ради чего роадмап
|
||
открывают чаще всего. Порядок теперь проверяется (`roadmap_lint`) и правится
|
||
(`check --fix` переставляет секции вместе с содержимым): без проверки порядок
|
||
разъедется молча, а переставлять секцию с десятком строк руками — работа, на
|
||
которой ошибаются.
|
||
|
||
**ЯЯЯ. Индексы позиций считаются из самого кортежа.** `ACHIEVED` был `0` и стал
|
||
`3`; хардкод индексов пережил бы перестановку молча и сломал бы `close`. Теперь
|
||
`PLANNED, DIRECTIONS, OPERATIONS, ACHIEVED = range(len(ROADMAP_SECTIONS))` —
|
||
переставили секцию, индексы переехали сами.
|
||
|
||
### Что из этого следует
|
||
|
||
98. **Отбивка нужна и перед заголовком.** Перестановка блоков ставит два
|
||
заголовка вплотную — `spaced_sections` правил только строку после. Дефект
|
||
нашёлся сразу же, на первой перестановке демо-набора: класс правки,
|
||
существующий только потому, что появилась другая правка.
|
||
99. **`check --fix` переставляет, но не переименовывает.** Чужую секцию он
|
||
оставляет ошибкой, и на переименовании `Разработка` → `Сопровождение`
|
||
останавливается: имя — решение человека, порядок — механика. Тот же разрез,
|
||
что между регистром (правит) и составом (не трогает).
|
||
100. **Версия канона отделяет состояния проектов, а не редакции текста** — и
|
||
ровно поэтому её нельзя не поднять, когда состояние хоть одного проекта
|
||
уже зафиксировано.
|
||
|
||
## 27. Тип записи стал единственной осью и задаёт схему (2026-08-05)
|
||
|
||
Заметка просила «каждый тип задач сделать своей сущностью»: эмодзи на тип, тип
|
||
первым полем меты, категория вместо секции, описание типа с обязательными
|
||
разделами и алгоритмом, идеи в конец. Разбор показал, что первый шаг обязан быть
|
||
другим — не добавить типу свойств, а **сократить число осей**.
|
||
|
||
**ААББ. Осей было две, и ортогональность была фальшивой.** Тип записи
|
||
(`goal`/`idea`/`task`) и род работы (`kind:<род>` тегом) давали двенадцать клеток
|
||
произведения, из которых законны шесть: у цели род запрещён, у задачи обязателен,
|
||
у идеи пуст и на практике не ставится. Плюс «алгоритм работы над записью такого
|
||
типа» крепится не к `task`, а к `fix` и `research` — то есть к роду. Ось, к
|
||
которой пишется алгоритм, и была настоящим типом. Оси схлопнуты в одну из пяти
|
||
значений: `goal` | `feature` | `fix` | `chore` | `research`.
|
||
|
||
**ВВГГ. Тип `idea` упразднён: состояние не может быть типом.** Он значил не род
|
||
работы, а незаполненность — «первый, второй или третий вопрос теста готовности не
|
||
отвечается». Состояние меняется по мере того, как запись дописывают, а тип меняют
|
||
командой, и на этом расхождении `idea` и жила: её приходилось «понижать» и
|
||
«повышать» вручную. Теперь состояние выводится из заполненности — **`research`
|
||
без раздела «Вопрос» это сырьё**, — и различие держит та же машина, что и всё
|
||
остальное.
|
||
|
||
Цена решения названа сразу: `research` теперь вбирает и замер реальности, и
|
||
сырую функцию («Подсказка следующего хода»). Обосновано это тем, что у обоих
|
||
**один исход — записанный ответ, а не изменение системы**, и одна приёмка. Имя
|
||
`rnd` из заметки отклонено в пользу `research`: аббревиатура читается как
|
||
`random` и не расшифровывается тому, кто вернётся к беклогу через квартал, а
|
||
`research` уже стоял в файлах живых проектов — миграция тронула только бывшие
|
||
идеи.
|
||
|
||
**ДДЕЕ. Дом типа — поле меты, эмодзи производна.** Прежнее правило «отдельного
|
||
поля типа нет: два места для одного факта разъезжаются» отменено не потому, что
|
||
разонравилось, а потому, что его аргумент был против **префикса плюс поля**. При
|
||
переносе дома в мету дом остаётся один; из индекса тип при этом пропадал бы — там,
|
||
где принимают решение «брать или не брать», — и это чинит эмодзи. Она стоит в H1,
|
||
а не в строке индекса, чтобы инвариант «заголовок в индексе дословно» остался
|
||
нетронутым: одна проверка вместо двух.
|
||
|
||
**ЖЖЗЗ. Поле места назвали по типу, а не одним словом на всех.** «Категория»
|
||
вместо «Секции» — просьба заметки, но одинаковое переименование закрепило бы
|
||
конфляцию: у задачи поле называет полку домена, в которую она вернётся из
|
||
спринта, у цели — часть роадмапа, то есть состояние очереди. Разные имена
|
||
(`Категория` / `Секция`) выбраны именно потому, что **какое поле обязательно,
|
||
решает тип** — то самое, ради чего затевалась вся правка.
|
||
|
||
**ИИКК. Два новых обязательных раздела появились из уже записанных правил,
|
||
которые нечем было проверить.** «Не воспроизводится — это `research`, а не `fix`»
|
||
стояло в каноне и не проверялось: раздел `Воспроизведение` делает его
|
||
проверяемым. Приёмка разведки — «записанный ответ, а не изменённый код» — тоже
|
||
стояла, но `sprint take` требовал от `research` два-пять критериев с оракулами,
|
||
и они писались ради проверки; вместо них `Вопрос` и `Куда ляжет ответ`.
|
||
|
||
**ЛЛММ. Сортировка «по важности» отклонена, «сырьё в конец» взято.** Первая
|
||
требует, чтобы кто-то важность поддерживал, — это ровно тот приоритет, от
|
||
которого правило 4 отказалось сознательно. Вторая **выводится из типа и
|
||
заполненности**, а не назначается человеком, и потому проверяется машиной и
|
||
приоритетом не становится. Разрез прошёл по признаку «кто источник порядка», а не
|
||
по признаку «полезно ли».
|
||
|
||
### Что из этого следует
|
||
|
||
101. **Правило можно отменять его собственным аргументом.** «Отдельного поля типа
|
||
нет» держалось на «два места для одного факта»; перенос дома оставил одно
|
||
место, и правило перестало применяться. Проверять надо не запись правила, а
|
||
то, выполняется ли ещё его посылка.
|
||
102. **Схема, шаблон и проверка растут из одной таблицы.** `TYPE_SCHEMA` кормит и
|
||
`body_template`, и `schema_verdict`: иначе `add` кладёт то, на чём
|
||
`sprint take` потом откажет. Тот же приём, что нормализатор `spaced_sections`
|
||
для оформления индексов.
|
||
103. **`--fix` не угадывает того, чего нет.** Тип переносится из тега `kind:` и
|
||
префикса `[goal]`/`[idea]` детерминированно, но записи, заведённые до
|
||
появления рода работы, не несут ни того ни другого — `feature` от `chore`
|
||
машина не отличает. Они уходят в `НЕОДНОЗНАЧНО` поимённо, а не получают
|
||
значение по умолчанию, которое врало бы ровно там, где по нему принимают
|
||
решение.
|
||
104. **Мигрирующие шаги обязаны читать отложенный текст, а не диск.** Шагов,
|
||
правящих мету, стало пять, и второй, перечитавший файл, стёр бы правку
|
||
первого. Общий `stage()` поверх `files` снял целый класс отказов, который до
|
||
этого держался на том, что шагов было мало.
|
||
|
||
## 28. Слаг подкреплён проверкой, обещанный судья заведён (2026-08-05)
|
||
|
||
Два пункта заметок, оба про одно: правило было записано и никем не исполнялось.
|
||
|
||
**ННОО. Правило про английские слаги существовало и не проверялось ничем.**
|
||
`canon.md` говорил «слаги файлов, capability и задач — английские, kebab-case»
|
||
одной строкой в хвосте раскладки; `docs.py` имён файлов не смотрел вовсе. Итог
|
||
предсказуем и нашёлся в самом плагине: единственный пример ADR в скилле `docs`
|
||
назывался `ADR-2026-08-03-ochered-tablicej`. Раскладка канона при этом
|
||
приглашала к нарушению — в схеме стояли плейсхолдеры `<тема>.md`, то есть слово
|
||
«тема» по-русски там, где надо было писать `<slug>`.
|
||
|
||
Разрез проверки — по тому, что машина знает точно: кириллица в имени и не-kebab-case
|
||
**жёстко**, форма `ADR-ГГГГ-ММ-ДД-slug.md` жёстко, транслит **эвристикой**, то есть
|
||
замечанием. Набор маркеров транслита подобран так, чтобы **ложных срабатываний не
|
||
было вовсе**: выброшены `ost` (ловит `post`, `cost`), `sch` (`schema`), `ya`
|
||
(`yaml`), `nost` (`nostalgia`), хвост `ii` (`radii`). Цена названа: `sostoyanie-partii`
|
||
проходит мимо. Правило, краснеющее на правде, приучает пролистывать весь блок —
|
||
это дороже пропуска.
|
||
|
||
**ППРР. Канон три версии обещал судью, которого не было.** В `canon.md` есть
|
||
таблица «Что проверяет машина, а что человек», и её правая колонка — смысловой
|
||
дубль, поведение в `architecture.md`, протухший факт, достаточность честной
|
||
строки — описывала работу, которую никто не делал: скилл `canon` предлагал
|
||
агенту судить об этом самому, то есть проверять то, что он же и писал. Заведены
|
||
`doc-consistency` и `doc-code-drift`, а колонка получила третий столбец с именем
|
||
судьи: обещание без адресата и есть тот способ, которым правило перестаёт
|
||
исполняться.
|
||
|
||
**ССТТ. Агентов двое, разрез по глубине, а не по охвату.** Тот же довод, что
|
||
развёл `task-form` и `doc-wording`: сверка текста с текстом дёшева и зовётся на
|
||
каждом синке документации, сверка с кодом требует читать репозиторий и зовётся
|
||
раз в спринт. Слитый агент делает дешёвую половину редкой либо дорогую —
|
||
поверхностной.
|
||
|
||
**УУФФ. Перечень фактов, сверяемых с кодом, закрыт.** Имя основной ветки,
|
||
команды, пути, зависимости поимённо, настройки с числовым значением, единые точки
|
||
проекта, capability, проверяемые инварианты. «Сверить архитектуру с кодом» —
|
||
задача без дна, и агент, которому её поставили, выдаёт правдоподобную труху
|
||
вместо находок. Отсюда и форма доклада `doc-code-drift`: он начинается **таблицей
|
||
проверенного**, а не находками, — по ней видно, чего он не смотрел.
|
||
|
||
**ХХЦЦ. Карта домов уехала в устав агента помеченной копией.** Устав ссылался на
|
||
файл плагина, а агент работает в репозитории проекта, где плагина может не быть.
|
||
Копия дословная, под маркерами `дом`/`копия`, и `copies.py` теперь её сторожит —
|
||
механизм для этого в репозитории уже был.
|
||
|
||
### Что из этого следует
|
||
|
||
105. **Записанное правило без проверки не исполняется даже автором.** Слаг ADR
|
||
нарушен в единственном примере, который плагин показывает как образец. Тот
|
||
же класс, что «прозаический триггер ADR дал 6 записей на 43 изменения»:
|
||
умолчание становится отличимым только когда его проверяют.
|
||
106. **Плейсхолдер — часть правила.** `<тема>.md` в схеме раскладки перевешивал
|
||
строку правила, стоявшую двумя абзацами ниже: образец читают вместо текста.
|
||
107. **Эвристика настраивается по ложным срабатываниям, а не по полноте.** Ноль
|
||
ложных при одном пропуске лучше, чем наоборот: пропуск стоит одной ненайденной
|
||
находки, ложное срабатывание — доверия ко всему блоку.
|
||
108. **Докстрока разошлась с кодом ровно там, где её читают.** `copies.py`
|
||
показывал закрывающие маркеры как `<!-- /дом -->`, а требовал
|
||
`<!-- /дом: <id> -->`; нашлось это первой же попыткой ими воспользоваться.
|
||
Пример в докстроке — тот же образец, что плейсхолдер в схеме.
|
||
|
||
## 29. Обкатка `doc-consistency` на самом dev-skills (2026-08-05)
|
||
|
||
Первый прогон агента — по репозиторию, который его же и содержит. Два прохода
|
||
(av-dev-pm; пайплайн плюс верхний уровень), 17 находок, все подтверждены по
|
||
файлам.
|
||
|
||
**ЧЧШШ. Агент нашёл ровно тот класс, ради которого заводился, и в свежей
|
||
работе.** Пять находок — остатки прежней модели типов в файлах, которые я не
|
||
дошёл поправить двумя коммитами раньше: `adopt.md` держал имена секций **канона
|
||
2**, `from-review.md` и `TODO.md` — упразднённый `[idea]`, `task-batch` в другом
|
||
плагине — «задачи-идеи». Правку модели я вёл от документов, которые менял, а не
|
||
от документов, которые на них ссылаются, — и обратный обход не сделал ни разу.
|
||
|
||
**ЩЩЪЪ. Самая дорогая находка была моей и свежей.** Таблица типов в `canon.md`
|
||
объявляла цель у `fix` запрещённой, а `tasks/SKILL.md` и `task-fix.md` —
|
||
необязательной; код на стороне вторых. Копия разошлась с домом **за один
|
||
день** — я написал обе половины в одном коммите. Это и есть цена второго дома в
|
||
чистом виде: не «когда-нибудь разойдётся», а «разошлось прежде, чем высохли
|
||
чернила».
|
||
|
||
Исход не «поправить значение», а **убрать причину**: `canon.md` дважды объявлял,
|
||
что фиксирует только словарь типов, — значит колонкам «разделы» и «цель» в нём
|
||
не место. Осталась таблица из двух колонок и ссылка на дом схемы.
|
||
|
||
**ЫЫЬЬ. Копии перечня «чем держат проект» разъехались втроём.** `canon.md`,
|
||
`tasks/SKILL.md` и `task-goal.md` пересказывали его своими словами: «метрики и
|
||
логи» против «мониторинга», «проверки» есть в двух из трёх. При этом
|
||
`tasks/SKILL.md` **ссылался на дом рядом с собственным пересказом** — ссылка не
|
||
мешает копии разойтись, если копия всё равно стоит.
|
||
|
||
**ЭЭЮЮ. Находка про коммиты снята как неверная, и это дефект самого агента.**
|
||
Он прочитал `av-dev-git/skills/commit/SKILL.md` («без `Co-Authored-By`») как
|
||
описание практики этого репозитория и предъявил 38 коммитов с трейлером. Но
|
||
dev-skills — **маркетплейс плагинов**: скилл коммита здесь продукт, уезжающий в
|
||
чужие проекты, а не правило, которому подчиняется сам репозиторий. Устав агента
|
||
не различает «документ описывает этот репозиторий» и «документ описывает то, что
|
||
репозиторий производит».
|
||
|
||
**ЮЮЯЯ. Счётчики в документах отменены как класс.** `REMAINING.md` держал «после
|
||
разбора двенадцати тем и 16 коммитов» (стало 28 и 52) и «три неизмеренных
|
||
изменения подряд» (стало больше). Оба числа обязан двигать человек, и оба
|
||
отстали молча. Заменены на формулировки, которые не надо поддерживать, и в шапку
|
||
записана причина.
|
||
|
||
### Что из этого следует
|
||
|
||
109. **Правка модели идёт по обратным ссылкам, а не по изменённым файлам.**
|
||
Меняешь дом — обойди тех, кто на него ссылается: `grep` по упразднённому
|
||
слову дал бы все пять остатков за минуту. Это дешевле любого агента и
|
||
должно идти до него.
|
||
110. **Ссылка на дом не отменяет копию, стоящую рядом.** Проверять надо не
|
||
«есть ли ссылка», а «есть ли пересказ»; `tasks/SKILL.md` имел и то и другое.
|
||
111. **Копия расходится с домом в пределах одного коммита.** Прежняя оценка
|
||
(«разойдётся на первой правке») занижена: расхождение возникает при
|
||
написании, если оба места пишет один проход.
|
||
112. **Агент, читающий репозиторий-продукт, обязан различать «про нас» и «про
|
||
то, что мы производим».** Иначе он предъявляет продукту практику его
|
||
потребителя. Устав `doc-consistency` этого различения не содержит — остаток
|
||
записан в REMAINING.
|
||
113. **Число в документе — обязанность, которую никто не берёт.** Счётчик тем,
|
||
коммитов, правок протухает молча; формулировка без числа дешевле его
|
||
сопровождения.
|
||
|
||
## 30. `av-dev-backlog` удалён (2026-08-05)
|
||
|
||
Плагин был помечен устаревшим решением Q и жил до перевода jellybit. Удалён
|
||
раньше этого срока.
|
||
|
||
**ААББВВ. Замороженный плагин стоит дороже, чем кажется.** Он не менялся, но
|
||
платил собой в каждой проверке репозитория: `exclude` в `pyproject.toml`,
|
||
`SKIP_DIRS` в `copies.py`, два абзаца README, оговорка в описании маркетплейса,
|
||
чтобы не ловить триггер «добавь задачу в беклог». Пять исключений ради кода,
|
||
который никто не читает, — и каждое надо было объяснять всякий раз, когда
|
||
кто-нибудь спрашивал, почему проверка обходит каталог.
|
||
|
||
**ААББГГ. Понимание старой раскладки уехало из плагина раньше самого плагина.**
|
||
`docs/backlog/` читает не `backlog.py`, а `av-dev-pm:tasks` — `adopt.md` и
|
||
адаптер в `tasks.py` держат ту же раскладку как **вход миграции**. Плагин
|
||
перестал быть единственным, кто её знает, ещё когда писался `adopt`; условие
|
||
«живёт до перевода последнего проекта» с тех пор охраняло пустоту.
|
||
|
||
**ААББДД. Опасение про порядок снятия не подтвердилось.** Удаление опередило
|
||
снятие: на jellybit плагин оставался включённым, когда записи в маркетплейсе уже
|
||
не было, и ожидалась ручная чистка `enabledPlugins` и `installed_plugins.json`.
|
||
`claude plugin uninstall` отработал штатно — он идёт **по реестру, а не по
|
||
манифесту маркетплейса**, и отсутствие записи там ему безразлично. Предупреждение
|
||
из README снято, вместо него записан проверенный факт.
|
||
|
||
### Что из этого следует
|
||
|
||
114. **Устаревшее удаляют, а не замораживают.** Заморозка выглядит бесплатной,
|
||
но растекается исключениями по конфигам и требует объяснения в каждом
|
||
месте, куда попала. Если удалять пока рано — назвать условие и срок; условие
|
||
без срока переживает свою причину.
|
||
115. **Условие «живёт до X» проверяют на живость, а не на X.** Здесь X (перевод
|
||
jellybit) не наступил, но причина условия отпала раньше: знание раскладки
|
||
переехало в `adopt`. Перепроверять надо основание, иначе условие держит само
|
||
себя.
|
||
116. **Порядок снятия и удаления из маркетплейса свободный.** `uninstall` живёт
|
||
реестром, манифест ему не нужен. Правило записано после проверки, а не
|
||
из осторожности, — и осторожность здесь стоила бы лишнего абзаца в README
|
||
про починку, которой не бывает.
|
||
|
||
## 31. Ревизия покрытия `av-dev-pm` продакт-оптикой (2026-08-05)
|
||
|
||
Сабагент в роли продакт-менеджера оценил покрытие жизненного цикла личного
|
||
проекта (один человек, недели-месяцы) скиллами и агентами `av-dev-pm`. Скоуп
|
||
сужен по ходу разбора: деплой и разбор инцидентов на проде делаются вручную,
|
||
скиллов под них не заводим. Осталось планирование, разработка и доработка.
|
||
|
||
**ААББЕЕ. Шаг 2 сессии требовал чисел, которых процесс отказался собирать
|
||
решением.** `cadence.md` делал обязанностью пересмотр «ориентира по размеру
|
||
спринта, прироста беклога на закрытую задачу, времени на задачу» и «сколько
|
||
заняли задачи **против ожидания**» — с обоснованием «иначе обязанность висит
|
||
ничья». Данных под это нет: у записи нет дат заведения, взятия и закрытия,
|
||
`close --implemented` удаляет файл, `sprint close` очищает `SPRINT.md`. Хуже
|
||
того, «против ожидания» и «время на задачу» требуют оценки и тайм-бокса, а
|
||
`session/SKILL.md` в «Почему не Scrum» их прямо не берёт: пункт противоречил
|
||
решению, стоящему через файл от него.
|
||
|
||
Исход — **выкинуть, а не подпереть данными**. На практике числа не
|
||
пересматривались ни разу, и заводить под них учёт дат значило бы обслуживать
|
||
обязанность, которой никто не брал. Осталось качественное: что сломалось в
|
||
процессе, что оказалось дороже, чем выглядело при заведении, какие правила не
|
||
сработали. Шаг 3 пункт 9 переименован из «переоценки по измеренному» в
|
||
«переоценку по пройденному», судит человек по памяти о спринте. Рядом записано,
|
||
что замеров нет **намеренно** — иначе следующий читатель заведёт их обратно как
|
||
недостающие.
|
||
|
||
**ААББЖЖ. `doc-consistency` переехал с каждого синка на сессию, к
|
||
`doc-code-drift`.** Агент на `opus` зовётся шагом 9 пайплайна, то есть на каждой
|
||
задаче: 5–8 opus-проходов за спринт по документам, которые за спринт меняются на
|
||
несколько абзацев. Обоснование в каноне («сверка текста с текстом дёшева») верно
|
||
относительно второго агента, но не в абсолюте на одиночке.
|
||
|
||
Довод сильнее денег: **расхождение между двумя документами по определению
|
||
требует двух документов**, а на большинстве задач синк правит один. И пачка,
|
||
отбираемая работой, не видит того, чего работа не касалась, — а расхождение живёт
|
||
ровно там: правка отменяет решение в одном документе, парный статус нужен в
|
||
другом. Это был открытый вопрос `REMAINING` про охват ADR при пересмотре; переезд
|
||
его закрыл. Цена — потеря привязки находки к задаче, которая её породила: по теме
|
||
29 именно эта привязка дала пять самых точных находок. Принято сознательно.
|
||
|
||
**ААББЗЗ. Отмена цели получила порядок, но не флаг.** `close` запрещал закрыть
|
||
цель с живыми задачами, а что делать с этими задачами, не говорил нигде: шаг 3
|
||
сессии знал только «та ли цель», `task-goal.md` описывал одно достижение, а
|
||
`session/SKILL.md` вдобавок утверждал «цель постоянна». Человек получал отказ с
|
||
перечнем и никакой подсказки.
|
||
|
||
Порядок записан: сперва задачи поштучно (`close --reason` своей причиной либо
|
||
`edit --goal` на другую цель), потом сама цель через `close --reason` в
|
||
`REJECTED.md`, а не в `Готово` — отменённая цель не умеет ничего. Флаг
|
||
`--cascade` отвергнут: отмена цели редка и дорога, и поштучный разбор здесь не
|
||
церемония, а единственный момент, когда видно, что из задач переживёт цель.
|
||
Каскад превратил бы его в один Enter. **Причина у каждой задачи своя**: «цель
|
||
отменена» это пересказ команды, в `REJECTED.md` от него нет пользы через квартал.
|
||
|
||
Место процедуры — переоценка на сессии, а не отдельный заход: отмена цели **и
|
||
есть** разбор всех её задач, а разбор задач — шаг 3.
|
||
|
||
**ААББИИ. У брошенного спринта появился второй законный исход, без порога.**
|
||
`--dissolve` во всех текстах был привязан к блокеру, и скрипт отказывал словами
|
||
«роспуск объясняется блокером». Вернувшийся к набору, который стоял месяц, не
|
||
имел законного хода: двигать нельзя (заморозка), распускать не по чему. Теперь
|
||
роспуск объясняется блокером **или тем, что набор протух**.
|
||
|
||
Порога в неделях сознательно нет — это тот же класс, что выкинутые числа шага 2:
|
||
счётчик простоя пришлось бы вести руками, а решает всё равно человек. Признак не
|
||
срок, а **что набор перестал быть твоим**: перечитываешь, зачем эти задачи вместе
|
||
— он протух. Туда же добавлена точка входа «вернулся, а спринт открыт»: `check`,
|
||
`SPRINT.md`, развилка продолжать/распустить. Середины у развилки нет намеренно —
|
||
«доделаю пару штук и решу» это работа по набору, которого ты не понимаешь.
|
||
|
||
**ААББКК. Журнал канона прогоняется как есть, а проверка исхода поручена
|
||
судьям.** Схлопнуть записи 3 и 4 в один переход «с 2 на 4» отвергнуто: журнал
|
||
описывает не только *что сделать*, но и порядок, в котором это делалось, и слитая
|
||
запись экономит один проход ценой невоспроизводимости остальных. Оба живых
|
||
проекта пройдут 2→3→4 по записям.
|
||
|
||
Взамен появилась проверка исхода: **шагом 6 `adopt` и шагом 6 `upgrade` зовутся
|
||
оба судьи документов**. Это прямой ответ на открытый вопрос REMAINING «как
|
||
проверять, что канон не разошёлся с проектами после `upgrade`»: `check` сверяет
|
||
**число** в `.pm.json` с версией скрипта и про существо записи не знает ничего.
|
||
Проект несёт `"canon": 4` и может не иметь того, чего требовала любая из
|
||
пройденных версий — записи применяются руками, а ручной проход по трём записям
|
||
подряд ровно то место, где половина шага делается и забывается.
|
||
|
||
У `adopt` добавка другого рода: там судьи ловят не недоделанную миграцию, а
|
||
последствия переноса — факт, растащенный по двум домам, поведение, осевшее в
|
||
`architecture.md`, ADR, оторванный от своего `design.md`. Им передаётся
|
||
объявленное переходное состояние из шага 5, иначе честная строка в незаполненном
|
||
слоте вернётся находкой.
|
||
|
||
### Что из этого следует
|
||
|
||
117. **Обязанность без источника данных отменяют, а не механизируют.** Первый
|
||
позыв — дать шагу данные (дописать даты, сводку спринта). Но обязанность,
|
||
не исполнявшуюся ни разу, дешевле снять: механизация под неё производит
|
||
учёт, который надо вести, ради разбора, который не делается.
|
||
118. **Требование, противоречащее решению через файл от него, — не мелочь, а
|
||
признак копии.** «Против ожидания» пережило решение «не берём оценки»,
|
||
потому что стояло в другом документе. Обратный обход по решению «что мы не
|
||
берём» нашёл бы это сразу — тот же приём, что и следствие 109.
|
||
119. **Частота вызова агента выводится из того, что он ищет.** Судья
|
||
расхождений **между** документами бессмысленен там, где документ один;
|
||
значит его место не на задаче, а на наборе задач. Цена вызова подтвердила
|
||
вывод, но не она его дала.
|
||
120. **Запрет обязан называть выход.** `close` верно не давал осиротить задачи,
|
||
но текст отказа перечислял препятствия и молчал о ходе. Проверка без
|
||
названного следующего шага — половина работы: она защищает данные и бросает
|
||
человека.
|
||
121. **Признак вместо порога там, где счётчик пришлось бы вести руками.**
|
||
«Набор перестал быть твоим» проверяется в момент вопроса и ничего не
|
||
требует хранить; «прошло N недель» требует учёта, который никто не ведёт, и
|
||
всё равно кончается решением человека.
|
||
122. **Версионирование без единого переехавшего проекта — не журнал миграций, а
|
||
история правок.** Довод за схлопывание был верен по факту и отвергнут по
|
||
принципу: обкатка на живых проектах и проверяет, работает ли механизм.
|
||
Схлопнуть значило бы не прогнать его ни разу и оставить вопрос открытым.
|
||
123. **Проверка версии не есть проверка миграции.** Число в `.pm.json` двигает
|
||
тот же проход, что делал шаги, — и двигает независимо от того, все ли
|
||
сделаны. Механической проверки существа нет; там, где её нет, ставится
|
||
судья, а не отметка.
|