From bf6a1731153cefc4b12bfa6235e2536acd563623 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Thu, 13 Aug 2026 12:40:56 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B6=D1=83=D1=80=D0=BD=D0=B0=D0=BB=20=D1=80?= =?UTF-8?q?=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D0=B9:=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=20=D0=BF=D0=BE=20=D1=82=D0=B5?= =?UTF-8?q?=D0=BC=D0=B5=20=D0=BD=D0=B0=20=D1=84=D0=B0=D0=B9=D0=BB,=20?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D0=BA=D0=B8=20=D1=80=D0=B5=D1=88=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B9=20=D1=81=D1=82=D0=B0=D0=BB=D0=B8=20=D0=BD=D0=BE?= =?UTF-8?q?=D0=BC=D0=B5=D1=80=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DECISIONS.md (4040 строк, 65 тем) → decisions/, файл на тему плюс указатель; - буквенные метки решений заменены сквозными Р1–Р234, следствия получили префикс С при прежних номерах: схема букв выродилась до пятибуквенных и сломалась — `АЕАКЛ` была занята и темой 53, и темой 65; - 42 перекрёстные ссылки переписаны под новые номера и стали живыми; где номер означал тему, а слово стояло «решение», формулировка исправлена. --- DECISIONS.md | 4040 ----------------- HISTORY.md | 2 +- README.md | 8 +- REMAINING.md | 4 +- TODO.md | 10 +- decisions/01-openspec-status.md | 86 + decisions/02-project-doc-canon.md | 106 + decisions/03-review-brief-is-canon.md | 115 + decisions/04-plugin-boundaries.md | 76 + decisions/05-project-start-lifecycle.md | 89 + decisions/06-docs-upkeep.md | 68 + decisions/07-skills-layout-scripts.md | 83 + decisions/08-rollout-order.md | 70 + decisions/09-script-linters.md | 67 + decisions/10-two-pass-plugin-review.md | 51 + decisions/11-plugin-dependencies.md | 52 + decisions/12-mechanical-copy-check.md | 53 + decisions/13-plan-sections-renamed.md | 31 + decisions/14-run-mode-defaults-flipped.md | 54 + decisions/15-review-pass-order-graph.md | 90 + decisions/16-directory-instead-of-file.md | 73 + decisions/17-notes-tier-work-kind-roadmap.md | 100 + decisions/18-tier-raises-pass-not-risk.md | 107 + decisions/19-roadmap-is-state-not-queue.md | 101 + decisions/20-record-form-heading-sections.md | 86 + decisions/21-project-text-language.md | 66 + decisions/22-wording-agent-trial.md | 40 + decisions/23-wording-split-two-passes.md | 54 + decisions/24-two-pass-trial-defects.md | 41 + .../25-maintenance-section-shared-vocab.md | 56 + .../26-canon-4-retroactive-edit-cancelled.md | 53 + decisions/27-record-type-single-axis.md | 82 + decisions/28-slug-check-and-judge.md | 65 + decisions/29-doc-consistency-trial.md | 65 + decisions/30-av-dev-backlog-removed.md | 41 + decisions/31-pm-coverage-product-review.md | 126 + decisions/32-vocabulary-sweep.md | 54 + decisions/33-review-cost-cut.md | 73 + decisions/34-throughput-vs-depth.md | 87 + decisions/35-model-revision.md | 75 + decisions/36-review-topics-project-docs.md | 106 + decisions/37-gate-and-autotests-one-name.md | 35 + decisions/38-plugin-seam-no-pass-names.md | 35 + decisions/39-sprint-without-goal.md | 47 + decisions/40-three-doc-categories.md | 60 + decisions/41-task-sizing-once.md | 40 + decisions/42-quick-cheaper-than-standard.md | 52 + decisions/43-design-review-tiers.md | 31 + decisions/44-task-label-single-value.md | 50 + decisions/45-label-corrector-small-share.md | 63 + decisions/46-label-rule-own-document.md | 38 + decisions/47-openspec-setup-skill.md | 51 + decisions/48-foreign-tool-form-by-query.md | 43 + .../49-shared-rule-home-outside-plugin.md | 38 + decisions/50-wording-split-by-plugin.md | 39 + decisions/51-av-dev-pm-split.md | 40 + decisions/52-validator-follows-file.md | 25 + decisions/53-canon-and-docs-two-skills.md | 24 + decisions/54-plugin-seam-rule-home.md | 96 + decisions/55-task-pipeline-becomes-resolve.md | 71 + decisions/56-plugin-and-skill-renames.md | 36 + decisions/57-sprints-cancelled-groom.md | 58 + decisions/58-doc-judges-healthcheck.md | 39 + decisions/59-four-subagent-audit.md | 58 + decisions/60-service-file-named-by-owner.md | 68 + .../61-research-and-solve-two-scenarios.md | 68 + decisions/62-wording-called-by-editor.md | 44 + decisions/63-maintenance-third-scenario.md | 101 + decisions/64-three-plugins-merged.md | 65 + decisions/65-axes-registry-home.md | 56 + decisions/README.md | 121 + scripts/addresses.py | 8 +- 72 files changed, 4253 insertions(+), 4053 deletions(-) delete mode 100644 DECISIONS.md create mode 100644 decisions/01-openspec-status.md create mode 100644 decisions/02-project-doc-canon.md create mode 100644 decisions/03-review-brief-is-canon.md create mode 100644 decisions/04-plugin-boundaries.md create mode 100644 decisions/05-project-start-lifecycle.md create mode 100644 decisions/06-docs-upkeep.md create mode 100644 decisions/07-skills-layout-scripts.md create mode 100644 decisions/08-rollout-order.md create mode 100644 decisions/09-script-linters.md create mode 100644 decisions/10-two-pass-plugin-review.md create mode 100644 decisions/11-plugin-dependencies.md create mode 100644 decisions/12-mechanical-copy-check.md create mode 100644 decisions/13-plan-sections-renamed.md create mode 100644 decisions/14-run-mode-defaults-flipped.md create mode 100644 decisions/15-review-pass-order-graph.md create mode 100644 decisions/16-directory-instead-of-file.md create mode 100644 decisions/17-notes-tier-work-kind-roadmap.md create mode 100644 decisions/18-tier-raises-pass-not-risk.md create mode 100644 decisions/19-roadmap-is-state-not-queue.md create mode 100644 decisions/20-record-form-heading-sections.md create mode 100644 decisions/21-project-text-language.md create mode 100644 decisions/22-wording-agent-trial.md create mode 100644 decisions/23-wording-split-two-passes.md create mode 100644 decisions/24-two-pass-trial-defects.md create mode 100644 decisions/25-maintenance-section-shared-vocab.md create mode 100644 decisions/26-canon-4-retroactive-edit-cancelled.md create mode 100644 decisions/27-record-type-single-axis.md create mode 100644 decisions/28-slug-check-and-judge.md create mode 100644 decisions/29-doc-consistency-trial.md create mode 100644 decisions/30-av-dev-backlog-removed.md create mode 100644 decisions/31-pm-coverage-product-review.md create mode 100644 decisions/32-vocabulary-sweep.md create mode 100644 decisions/33-review-cost-cut.md create mode 100644 decisions/34-throughput-vs-depth.md create mode 100644 decisions/35-model-revision.md create mode 100644 decisions/36-review-topics-project-docs.md create mode 100644 decisions/37-gate-and-autotests-one-name.md create mode 100644 decisions/38-plugin-seam-no-pass-names.md create mode 100644 decisions/39-sprint-without-goal.md create mode 100644 decisions/40-three-doc-categories.md create mode 100644 decisions/41-task-sizing-once.md create mode 100644 decisions/42-quick-cheaper-than-standard.md create mode 100644 decisions/43-design-review-tiers.md create mode 100644 decisions/44-task-label-single-value.md create mode 100644 decisions/45-label-corrector-small-share.md create mode 100644 decisions/46-label-rule-own-document.md create mode 100644 decisions/47-openspec-setup-skill.md create mode 100644 decisions/48-foreign-tool-form-by-query.md create mode 100644 decisions/49-shared-rule-home-outside-plugin.md create mode 100644 decisions/50-wording-split-by-plugin.md create mode 100644 decisions/51-av-dev-pm-split.md create mode 100644 decisions/52-validator-follows-file.md create mode 100644 decisions/53-canon-and-docs-two-skills.md create mode 100644 decisions/54-plugin-seam-rule-home.md create mode 100644 decisions/55-task-pipeline-becomes-resolve.md create mode 100644 decisions/56-plugin-and-skill-renames.md create mode 100644 decisions/57-sprints-cancelled-groom.md create mode 100644 decisions/58-doc-judges-healthcheck.md create mode 100644 decisions/59-four-subagent-audit.md create mode 100644 decisions/60-service-file-named-by-owner.md create mode 100644 decisions/61-research-and-solve-two-scenarios.md create mode 100644 decisions/62-wording-called-by-editor.md create mode 100644 decisions/63-maintenance-third-scenario.md create mode 100644 decisions/64-three-plugins-merged.md create mode 100644 decisions/65-axes-registry-home.md create mode 100644 decisions/README.md diff --git a/DECISIONS.md b/DECISIONS.md deleted file mode 100644 index 6483b37..0000000 --- a/DECISIONS.md +++ /dev/null @@ -1,4040 +0,0 @@ -# Решения по устройству процесса - -Журнал согласований: что решено, почему и что из этого следует. Пишется по ходу -разбора тем, одна тема — один раздел. Причина обязательна: через месяц она -забывается раньше факта. - -Незакрытые остатки прошлого захода — [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 шагов) - и `/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` растворяется в `/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//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//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//review/` (независимый артефакт, `task-batch` уже - сверяет полноту ревью по нему, а не по прозе исполнителя), **`SPRINT.md` под - git** с видимой историей и **`reopen --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.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. Остаток чистки помечается маркером и считается числом.** Неразобранный -раздел получает ``, -`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/.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: `` … -`` и `` … ``. -`scripts/copies.py` требует побайтового совпадения текста между маркерами. - -*Почему комментарии, а не манифест копий отдельным файлом:* маркер уезжает в -репозиторий проекта вместе со скелетом, и там он **полезен** — говорит читателю, -что у текста есть дом и правится он там. Манифест остался бы в маркетплейсе и -проекту ничего не сказал. - -**PP. Идентификатор строгий — буквы, цифры, дефис — и повторяется в закрывающем -маркере.** Иначе документация о самом механизме объявляет дом и роняет проверку: -это случилось на первом же прогоне, `README.md` объявил дом примером. Теперь -пример пишется ``, угловые скобки под шаблон не подходят. - -**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 "`, а требовал - ``; нашлось это первой же попыткой ими воспользоваться. - Пример в докстроке — тот же образец, что плейсхолдер в схеме. - -## 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` двигает - тот же проход, что делал шаги, — и двигает независимо от того, все ли - сделаны. Механической проверки существа нет; там, где её нет, ставится - судья, а не отметка. - -## 32. Сквозной проход по словарю: пять слов сняты, девять закрыты списком (2026-08-05) - -Проход упрощения (тема 31) уткнулся в один и тот же класс у всех пяти агентов: -слово, живущее в трёх-шести файлах разом. Правка в одном месте развела бы -словарь, правка во всех — уже не упрощение текста скилла. Каждый агент честно -остановился и записал слово в свой отчёт, и одни и те же слова всплыли в разных -отчётах. Разобрано отдельным проходом. - -**ААББЛЛ. «Слово прижилось» не проверяется, поэтому заменено списком.** Оговорка -в `language.md` звучала так: не переводится «термин, у которого нет точного -русского эквивалента и который в команде уже прижился». Проверить это на глаз -нельзя — прижившимся выглядит любое слово, встреченное трижды, и ровно так пять -агентов подряд и рассудили. Оговорка заменена **закрытым списком из девяти -терминов** с колонкой «что называет»: интейк, триаж, провенанс, дедуп, чек-лист, -дифф, промпт, сущности OpenSpec, роды проходов ревью. Слово не из списка и не из -таблицы имён вещей — находка, а не принятый стиль. - -Список заведён домом `язык-словарь` в `language.md` и копией в уставе -`doc-wording`. Копия обязательна: агент работает в репозитории проекта, где -плагина может не быть, и без списка предъявил бы «интейк» как англицизм. - -**ААББММ. Пять слов сняты, и все пятеро выглядели словарём, не будучи им.** -`конфляция` → смешение (4 места), `декорреляция` → разведённость (6), -`непоймание` → почему не поймали (9), `эвал-сет` → проверочный набор (4), `гайд` -→ руководство (6). Латинизм или калька при живом русском слове в каждом случае. - -Разбор `декорреляции` показателен: проект **уже владел** нужным словом — «агенты -разведены по глубине», «разведены по охвату» — и держал рядом латинский синоним -того же понятия. Это не англицизм, а второй дом для слова. - -`непоймание` снято ещё и потому, что форма журнала дефектов, которую канон кладёт -в проекты, спрашивает «Почему не поймали» — а проза рядом называла это -«причиной непоймания». Скелет и проза о скелете говорили разными словами. - -**ААББНН. Снятое записано вместе с оставленным, в одном списке.** Иначе снятое -возвращается: слово уходит из текстов, но ничто не мешает следующему проходу -завести его заново — оно ведь короткое и точное. Пять слов названы поимённо с -заменой каждого. - -### Что из этого следует - -124. **Escape hatch без перечня — это разрешение, а не исключение.** «Термин, - который прижился» освобождает от правила любое слово: проверка «прижился ли» - возвращает «да» всякий раз, когда слово встретилось. Исключение из правила - обязано быть списком, иначе оно съедает правило. -125. **Слово, от которого агент отказался править, — материал для отдельного - прохода, а не мусор отчёта.** Пять независимых агентов сошлись на одном - наборе слов, ни разу друг друга не видя. Список «что не тронул» оказался - полезнее списка правок именно этим. -126. **Снятое слово называется вместе с заменой и остаётся записанным.** Убрать - из текстов недостаточно: без записи «это снято и вот чем заменено» слово - возвращается первым же, кто найдёт его удачным. - -## 33. Стоимость ревью: снят самый дорогой проход и самая дорогая модель (2026-08-06) - -Прогоны стали долгими, а счёт в токенах — заметным. Разбор шёл не по находкам, а -по статьям расхода: что в конвейере стоит больше всего и что из этого окупается. -Две статьи названы прямо оператором. - -**ААББОО. Проход независимой реализации снят целиком, и с ним профиль `deep`.** -`reimpl` писал свою реализацию узла, не открывая существующую, и диффил по -решениям. Его счёт определялся **объёмом вывода** — он один писал код, а не читал -его, — и на прогоне это была самая большая строка расхода. Снят по решению о -стоимости. - -Профиль `deep` от этого не «похудел», а исчез: `reimpl` был **единственным**, чем -он отличался от `wide` (обоим оставалось бы 0, 1, 2, 4, 5). Держать два имени для -одного состава нельзя — ровно от этой болезни лечилась ступень `wide` (решение -JJJ): у профиля обязан быть один правильный ответ, иначе реестр состава нечем -проверять. Ступеней теперь три: `quick`, `standard`, `wide`. - -Вместе с профилем ушло всё, что обслуживало только его: - -- **барьер стоимости** — он существовал ровно затем, чтобы дорогой проход не - писал реализацию против кода, который через час перепишут. Дорогого прохода - нет, и граф стал плоским во всех профилях: от гейта до триажа. Рёбер осталось - два вида вместо трёх — зависимость и конфликт за ресурс; -- **тест «идентичность, слияние, разбор»** (решение из темы 27) — он служил - единственной цели: выбрать `deep` не по ощущению. Выбирать больше нечего, и - полторы страницы теста сняты вместе с проектным перечнем мест в - `docs/review.md`; -- **стадии перенумерованы**: 0 гейт, 1 сверка, 2 враждебный и эксплуатационный, - 3 архитектурный, 4 триаж. Дыра на месте третьей читалась бы как пропущенная - стадия. - -**ААББПП. Снятие записано как сознательное сужение, а не как «класс оказался -пустым».** `calibration.md` требует замера на двух проектах перед удалением -прохода, и замера не было — было решение о цене. Значит и в «Честном пределе» -стоит честная строка: **«не знаю, чего не знаю» больше не достаёт никто.** Остаток -независимого взгляда дают профиль `design` (код пишется под его находки) и -`architecture` (второй способ, лишние слои), но альтернативной реализации, с -которой можно сдиффить решения, у конвейера нет. Класс уходит в границы покрытия -каждого прогона, а у проекта — в подраздел «перестали проверять сознательно». - -Без этой записи снятие через месяц читается как «проверено и признано лишним», -и вернуть проход было бы не на чем. - -**ААББРР. Самая дорогая модель снята со всех проходов.** На ней сидели трое: -`review-triage`, `review-architecture` и `doc-code-drift` из `av-dev-pm`. Все трое -переведены на `opus`. Основание для верхней модели — «ошибка распространяется -дальше самой находки» — никуда не делось, но оно объясняет, почему эти двое **не -опускаются до `sonnet`**, а не почему им нужна ступень выше `opus`: разницы в -пользу более дорогой модели не показал ни один прогон, а время и счёт она множила. - -Палитра цветов схлопнулась до двух: `sonnet` → green, `opus` → yellow. Красного в -репозитории больше нет, и `frontmatter.py` теперь отвергнет модель вне этих двух — -раскладка проверяется механически, как и раньше. - -### Что из этого следует - -127. **Профиль, у которого не осталось собственного прохода, — не профиль.** - Ступень стоимости определяется тем, что она **добавляет**; сняли добавку — - сняли ступень, а не оставили имя. Иначе два имени указывают на один прогон, и - состав снова нечем проверить. -128. **Удаление по цене и удаление по замеру записываются по-разному.** Первое - обязано назвать класс, который перестал проверяться, и оставить его в - границах покрытия. Второе — сослаться на замер. Смешение их даёт самый - дорогой вид тишины: пробел, выглядящий как решённый вопрос. -129. **Механика, обслуживающая один проход, снимается вместе с ним.** Барьер - стоимости, тест выбора верхней ступени и проектный перечень мест держались - только на `reimpl`. Оставшись, они выглядели бы работающими правилами и - тратили бы внимание на каждом прогоне. - -## 34. Пропускная способность против глубины: тяжёлые проходы уехали в верхнюю ступень (2026-08-06) - -Тема 33 сняла самую большую разовую статью расхода, но не тронула главную — -**частоту**. Меряющая пара стояла в `standard`, то есть на большинстве задач, и -именно она делала прогон долгим: два прохода держат машину, идут цепочкой и -доказывают находки запуском. Разбор шёл от цели, названной прямо: **лучше -поправить в следующей задаче, чем держать одну два часа.** - -**ААББСС. `adversary` и `ops` переехали в `wide`, и это решение по цене, а не по -ценности.** Стадия осталась самой урожайной за всю историю замеров — пять из семи -выживших находок дозапуска и единственная находка про молчаливый старт отката. Но -её ценность оплачивается на **каждой** задаче, а получается на немногих: оракул -добывается запуском, запуск — это машина, цепочка и часы. Ступень, которая раньше -была умолчанием, стала исключением на 5–10% задач. - -**ААББТТ. Заведён `review-basics` — мелкая осадка двух тяжёлых проходов, без -единого запуска.** Он стоит только в `standard` и берёт ту половину вопросов, на -которые отвечают **чтением**: таймаут и отказ соседа, идемпотентность и -одновременная запись, остановка на середине, частичный откат при двух версиях, -наблюдаемость и тишина, очевидный рост объёма — плюс два вопроса архитектурного: -второй способ мимо единой точки (грепом, не картой) и что отсюда удалить. Потолок -4 находки, машину не держит, ничего не меряет. - -Отдельная его обязанность — **вопрос 4, частичный откат**. Без него правило -«миграция схемы не поднимает ступень» рассыпалось бы: раньше миграцию разбирал -`ops`, а он теперь в `wide`. Проход заведён не «до кучи», а затем, чтобы у -`standard` остался хоть один взгляд на ось времени. - -Модель у него верхняя, `opus`, и это не противоречит слову «средний»: усилие -режется **входом и потолком**, а не моделью. Дешёвая модель на проходе -с мнением платит триажем — это записанный замер, и отменять его без нового замера -нельзя. - -**ААББУУ. Объём и незнакомость изменения вошли в правило выбора ступени.** Раньше -ступень выбиралась только по классу («вводит ли новое понятие»), и правило прямо -запрещало смотреть на размер. Теперь вопросов два: крупное или незнакомое (трогает -несколько узлов, переносит ответственность, форму решения нащупывают по ходу) → -`wide`; мелкое (один узел, форма очевидна заранее, откат — обратная правка) → -`quick`; всё остальное → `standard`. Причина смены: цена разбирательства растёт -именно с объёмом и неизвестностью, а не с классом правила. - -Отрицательный тест `quick` сохранил прежнюю мудрость в новой рамке: **что после -мерджа не откатывается обратной правкой — не `quick`, каким бы маленьким ни был -дифф.** Три строки миграции идут в `standard`. - -**ААББФФ. Спорный случай решается вниз, и асимметрия объяснена ценой.** Между -`standard` и `wide` — в пользу `standard`: ошибка сюда стоит находки на следующей -задаче, ошибка обратно стоит трёх тяжёлых проходов на каждой задаче, выбранной -неверно. Между `quick` и `standard` — тоже в пользу `standard`, но по другой -причине: там разница в один дешёвый проход, зато единственный, кто на нижних -ступенях смотрит на отказы. - -Доля `wide` 5–10% записана как **проверка правила, а не пожелание**: если ступень -уходит каждой третьей задаче, её выбирают по ощущению важности. - -**ААББХХ. Сделка записана вместе с механизмом обратной связи, иначе это тихая -потеря качества.** На `quick` и `standard` не проверяется ничего, что требует -запуска: построенный путь, эксперимент против драйвера, любое число. Это самая -крупная граница покрытия конвейера, и она обязана идти строкой в каждом таком -прогоне поимённо. Обратная связь — журнал дефектов `docs/review.md`: класс, -который ловят только меряющие проходы, начал всплывать после мерджа — значит -ступень выбирают слишком низко. Плюс сам `basics` обязан сигналить строкой, если -видит, что ступень занижена: он единственный, кто смотрит на дифф целиком на -нижних ступенях. - -### Что из этого следует - -130. **Стоимость прохода — это его цена, умноженная на частоту, и вторая - переменная важнее.** Тема 33 убрала самый дорогой проход, тема 34 — - самый частый. Второе дало больше, хотя снятый проход был дешевле каждого - отдельного `reimpl`. -131. **Урожайность прохода не отвечает на вопрос, где ему стоять.** Меряющая пара - осталась самой ценной и всё равно уехала вверх: ценность оправдывает - существование прохода, но не его частоту. -132. **Замена тяжёлого прохода лёгким записывается как сужение, а не как - эквивалент.** `basics` задаёт те же вопросы чтением, и его ответы поэтому - слабее — условия вместо оракулов. Назвать это «покрыли то же дешевле» значит - соврать себе на первом же прогоне. -133. **Ступень, выбираемая по классу изменения, слепа к объёму.** Правило, - запрещавшее смотреть на размер, защищало от выбора по ощущению важности — и - заодно отправляло трёхстрочную правку и переборку пяти узлов в один профиль. - Признаков нужно два: класс отвечает за обратимость, объём — за цену - разбирательства. - -## 35. Ревизия моделей: переведены двое из девяти, и критерий оказался не тот (2026-08-06) - -Сквозной проход по тринадцати уставам с одним вопросом: кого из девяти -`opus`-агентов можно опустить на `sonnet` без потери. Ответ — двоих, и по дороге -выяснилось, что критерий, которым конвейер до сих пор раздавал модели, отвечает -не на тот вопрос. - -**ААББЦЦ. Модель выбирается по цене ошибки, а не по роду прохода.** Прежнее -деление — applicative против generative — раздаёт модели по тому, **откуда** -проход берёт критерий. Но платит проект не за происхождение критерия, а за -разбирательство с находкой. Рабочий признак: - -- находка приходит **со ссылкой на записанный источник** (строка спеки, цель в - манифесте, значение в конфиге, номер правила) — её опровержение стоит одного - открытия файла. Дешёвая модель ошибается здесь **проверяемо**; -- находка есть **суждение** («это второй способ», «этот оракул негоден», «эти два - документа противоречат») — опровержение стоит рассуждения, а рассуждение стоит - триажа или человека. - -Признак объясняет прежнюю раскладку лучше, чем она сама себя: `gate`, `code` и -`ops` не потому дёшевы, что применяют чек-лист, а потому, что каждая их находка -показывает пальцем на строку. - -**ААББЧЧ. `doc-code-drift` → `sonnet`.** У него закрытый перечень из восьми -правил, и каждое — пара «факт в документе ↔ команда, которой он проверяется». -Устав прямо запрещает суждение («верность и полноту не проверяешь»), требует -формы «написано X, в коде Y, проверено командой Z» и правила «нечем проверить — -не находка». Ложная находка опровергается **той же командой, которая её -породила**. Это самый чистый случай признака за весь разбор. - -**ААББШШ. `task-form` → `sonnet`.** Семь пронумерованных правил с таблицами форм -и поимённым перечнем подмен. Но решило не это, а потребитель: его находка — -готовая формулировка, которую человек читает и отклоняет командой, а не -оркестратор, который **молча реализует**. Довод, державший `triage` на верхней -модели, здесь не работает вовсе: ошибка стоит строки чтения. - -**ААББЩЩ. `review-specs` рассмотрен и оставлен на `opus` — по причине, обратной -общей.** Он самый частый `opus`-проход конвейера (идёт и в `design`, и на коде, -то есть дважды за задачу), и по устройству он applicative: SKILL.md сам называет -стадию 1 «два applicative-прохода, оба дешёвые», хотя платит за одного `sonnet`, а -за другого `opus`. Расхождение разобрано и закрыто текстом: держит его наверху -направление `code → spec`, где надо заметить **отсутствие** — тихий фолбэк, -самодеятельный дефолт, проглоченную ошибку. Прочие держат `opus` из-за цены -ложных находок, этот — из-за цены пропущенных, а пропуск не оставляет следа -нигде: ни в отчёте, ни в границах покрытия. - -**ААББЭЭ. Остальные шестеро оставлены, и у каждого своя причина.** -`adversary` и `rubric` порождают критерий по построению (второй — с запретом -открывать код в первой фазе). `architecture` — чистое суждение о структуре. -`triage` — сток, его ошибка становится кодом. `doc-consistency` ошибается ровно в -ту сторону, которую дороже всего опровергать: путает «упомянуто в двух местах» с -«оба утверждают». `basics` заведён час назад, половина его вопросов — суждение, и -модель у него выбрана решением оператора в этой же сессии. - -**ААББЮЮ. Это разбор уставов, а не замер, и так и записано.** `calibration.md` -двигает модель инъекцией дефекта; здесь инъекции не было. Двое переведены потому, -что их ошибка **обнаруживается той же проверкой, что породила находку**, — то -есть цена ошибки ограничена сверху независимо от модели. Для остальных такой -границы нет, и трогать их без замера нельзя. - -### Что из этого следует - -134. **Дешёвая модель безопасна там, где её ошибку опровергает та же команда, - что породила находку.** Не «где критерий записан» — записанный критерий - бывает и у суждения, и у сверки, а разница между ними в том, чем кончается - спор. -135. **Ошибка бывает двух родов, и модель защищает от разных.** Ложная находка - стоит триажа и видна; пропущенная не стоит ничего сегодня и не видна вовсе. - Проход, у которого дороже второе, держится на верхней модели даже будучи - applicative. -136. **Потребитель находки — часть её цены.** Одна и та же ошибка стоит строки - чтения, если её читает человек, и разросшегося кода, если её молча реализует - оркестратор. Модель раздаётся с оглядкой на это, а не только на устройство - прохода. - -## 36. Темы ревью: документ проекта стал направлением проверки (2026-08-06) - -Замечено при сверке документов канона с составом ступеней: **три документа -остались без читателя ниже `wide`** — `security.md`, `database.md` и `adr/`. -Проект поддерживал их, а на 90% задач их не открывал никто. Причина оказалась не -в переезде проходов, а в том, как описан состав прогона. - -**ААББЯЯ. Тема первична, проход вторичен, и это правило 0 конвейера.** Список тем -нигде не был записан: он существовал побочным продуктом списка проходов. Проход -уезжал в верхнюю ступень — и тема уезжала с ним **беззвучно**: отчёт честно -говорил «`ops` не запускался» и не говорил «эксплуатацию не смотрел никто», а -нужно второе. Теперь прогон описывается таблицей «тема → дом → глубина → кто -закрывает», и таблица есть в каждом отчёте. - -**АВААА. Тема есть документ, и список тем открытый.** Всё, что проект кладёт в -`docs/`, становится темой ревью; запретить нельзя, разрешения не надо. Не темы -ровно две: `docs/tasks/` и `docs/review.*` (настройка самого конвейера — слой над -темами). Отсюда главное следствие: **`docs/` перестал быть документацией и стал -конфигурацией конвейера.** Проект настраивает проверку тем, что пишет о себе, а -не отдельным файлом настроек, который разошёлся бы с документами. - -Ядро — шесть тем: `requirements`, `autotests`, `conventions`, `architecture`, -`security`, `operations`. Их дома канон обещает. Всё сверх — темы проекта, и их -разбирает `basics`: именных проходов конечное число, а тем столько, сколько -заведёт проект, поэтому приёмник обязателен. - -**АВААБ. Тема живёт файлом или каталогом, на выбор проекта.** `docs/security.md` -и `docs/security/` — одно и то же. Прежде форма была задана поимённо -(`conventions`, `research`, `adr` — каталоги, остальные — файлы), и обосновать -это было нечем; заодно в TODO висел открытый вопрос «а если `architecture.md` -разрастётся». Теперь ответ механический: разросся — стал каталогом с `README.md`, -и это не смена версии канона. Обе формы сразу — ошибка, и `docs.py` её ловит: два -дома для одного факта расходятся молча. - -**АВААВ. Ступень выбирает разметчик, а не автор.** Заведён `review-scope` -(`sonnet`), стадия 0, до гейта: находит документы, выводит темы, назначает -глубины, выбирает ступень с обоснованием. Довод сильнее, чем синхронизация -документов: **до сих пор профиль называл тот же оркестратор, который написал -код** — то есть в точке выбора глубины проверки разведённости с автором не было -вовсе, и решала она под давлением «я почти закончил». Вызывающий пайплайн профиль -больше не передаёт. - -Право у разметчика симметричное — поднять и понизить, — но обоснование -обязательно всегда, а не только при отступлении от умолчания. - -**АВААГ. Разметчик передаёт адреса, а не пересказ.** Проект однажды уже держал -файл-посредник между документами и проходами (`review-brief.md`) и убрал его: -второй дом расходится с первым и выглядит актуальным. Пересказ в задании — тот же -посредник, живущий один прогон. Исключение одно: **отсутствие дома** — этого -проход сам дёшево не выяснит. - -`sonnet` ему хватает потому, что вывод устроен как **список**: каждый файл в -`docs/` обязан попасть в план темой или строкой «не тема, потому что», и план -сверяется с `ls docs/` за секунду. Выбор ступени — суждение, но у него три -независимых корректора: отрицательный тест `quick`, правило «спорный случай -вниз» и сигнал `basics` о заниженной ступени. - -**АВААД. `quick` и `standard` совпали составом и разошлись глубиной.** Требование -«нижние ступени закрывают все темы, просто не так глубоко» иначе не выполняется: -темы одни и те же, а различать ступени больше нечем. Глубин три и они про способ -доказательства, а не про старательность: **сверка** (открыть дом, открыть дифф, -сравнить), **разбор** (построить сценарий рассуждением), **доказательство** -(прогнать, померить, построить путь). Третья есть только в `wide` — она одна и -требует машины. - -Цена принята: это единственное место конвейера, где профиль не выводится из -списка проходов, поэтому глубина объявляется в отчёте наравне со ступенью. - -**АВААЕ. `review-code` переписан: технический разбор плюс конвенции.** Обнаружено -по ходу: **никто не читал код как код.** `specs` сверял с требованиями, `basics` — -с отказами окружения, `architecture` — с устройством, а `code` был проходом -только по прозаическим конвенциям и прямо объявлял, что дефекты рантайма и логики -не его. «Здесь ошибка в логике» не говорил никто, и это была самая крупная дыра -конвейера — крупнее любой недосмотренной темы. - -Теперь у прохода две половины: девять классов технического дефекта (необработанная -ветка отказа, пустое и нулевое, граница диапазона, перепутанный операнд, -неосвобождённый ресурс, изменение под итерацией, неверно применённый интерфейс -библиотеки, недостижимая ветка, «сделано соседнее») и прежняя сверка с -конвенциями. Модель поднята до `opus` по признаку темы 35: цена **пропущенной** -находки — дефект в проде, и она не оставляет следа ни в отчёте, ни в границах -покрытия. - -**АВААЖ. Вопросы проекта переадресованы темам.** В `docs/review.*` было «Вопросы к -проходам» в форме `ops: <вопрос>` — и когда `ops` уехал в `wide`, вопрос перестал -задаваться молча. Стало «Вопросы по темам». Туда же «Недоступно проверке» — по -темам, обоими подразделами. - -### Что из этого следует - -137. **Состав, описанный исполнителями, теряет предмет при перестановке - исполнителей.** Список проходов отвечает «кто работал», а нужен ответ «что - проверено». Первое выглядит полным ровно тогда, когда второе неверно. -138. **Открытый список нуждается в приёмнике, иначе он обещание.** Разрешить - проекту завести свою тему и не назначить, кто её разбирает, — то же, что не - разрешать. -139. **Регулятор глубины проверки нельзя оставлять в руках автора.** Не потому - что он злонамерен, а потому что давление «я почти закончил» действует - всегда и в одну сторону. -140. **Дыру в покрытии находят не там, где ищут находки.** Три осиротевших - документа нашлись сверкой канона с составом ступеней, а отсутствие - технического ревью кода — сверкой оптик проходов между собой. Ни то ни - другое не всплыло бы на прогоне: прогон честно сообщал, что все запущенные - проходы отработали. - -## 37. `gate` и `autotests` сведены к одному имени (2026-08-07) - -Тема звалась `autotests`, закрывающий её проход — `gate`, и на всех трёх ступенях -это была одна и та же клетка таблицы. Одна сущность под двумя именами — та же -ошибка, что и два разных под одним, только тише: она не путает, а **теряет**. -Вопрос проекта в `docs/review.*` адресуется теме; адресованный проходу — не -приезжает никуда, и ровно этот отказ уже случился однажды с `ops` (тема 36, АВААЖ). - -**АГААА. Победило имя темы, а не имя прохода.** Три довода, по убыванию веса: - -1. **Тема первична (правило 0), а имена тем — это имена документов.** - `docs/autotests.md` проект напишет: что покрыто, что нарочно нет, где - `testdata`. `docs/gate.md` не напишет никто — гейт это команда, а не предмет. -2. **Слово «гейт» уже занято дважды** — команда проекта и ребро графа («пока гейт - красный, проходы с мнением не идут»). Третье значение сделало бы отчёт нечитаемым: - «гейт красный» и «гейт нашёл» — про разное. -3. **Тема шире гейта.** «Хватает ли проверок» и «чего в гейте намеренно нет» за - пределы красного/зелёного выходят. Назвать целое именем инструмента — тихо его - сузить. - -Цена названа честно: `autotests` звучит уже своего содержимого — линт, типы, -сканер уязвимостей тестами не являются. Гасится строкой в уставе: тема — это -«проверено ли машиной», а не «есть ли тесты», и гейт в ней инструмент, а не -граница. - -### Что из этого следует - -141. **Тема и проход, совпадающие один в один на всех ступенях, обязаны носить - одно имя.** Пока имён два, у сущности два адреса, а адресуют её по одному — - и какой из двух окажется живым, решает случай. -142. **Слово, уже значащее что-то в предметной области проекта, нельзя брать - именем роли конвейера.** «Гейт» принадлежит проекту раньше, чем ревью, и - спор за него ревью проигрывает. - -## 38. Шов между плагинами: канон не называет имён проходов (2026-08-07) - -Замечено при сведении тем документации с ревьюверами: `av-dev-pm` в шести местах -называл конвейер поимённо — от прозы канона до **вывода `docs.py` пользователю** -(«свои темы проекта: … — их разбирает `review-basics`»). Плагины при этом -раздельные: `av-dev-pm` работает без конвейера, `av-dev-pipeline` — без канона, -поразрядно деградируя. - -**АДААА. Общий словарь — имена тем и имена ступеней, и только они.** Ими проект -настраивает ревью: вопросы по темам и триггеры профиля. Имён проходов канон не -называет нигде. Направление зависимости при этом несимметрично и это верно: -**конвейер называет документы канона поимённо, потому что он их читатель**, а -обратной ссылки быть не может — документ живёт дольше, чем раскладка проходов. - -Заодно вычищены описательные адресации того же класса: «архитектурный проход -судит», «враждебный проход выдумает», «там идут враждебный, эксплуатационный и -архитектурный проходы». Последняя — худшая из них: это утверждение о **составе -ступени**, живущее на стороне, которая о составе не знает. - -**АДААБ. Пример в правиле не должен нарушать само правило.** Объяснение, почему -вопросы адресуются темам, звучало так: «вопрос, адресованный `ops`, перестал -задаваться в тот день, когда `ops` уехал в верхнюю ступень». Правило про -нестабильность имён, иллюстрированное именем. Стало «адресованный проходу» — и -работает даже после того, как проход переименуют. - -### Что из этого следует - -143. **Ссылка из вывода скрипта дороже ссылки из прозы.** Устаревшую строку в - документе чинит тот, кто её читает; устаревшее имя в сообщении `docs.py` - доезжает до чужого проекта и там объясняется недоумением. -144. **Список, который никто не ведёт, честнее списка, который ведут двое.** - Читателей документа не перечисляет ни одна сторона — читатель назначается - планом прогона. Прежняя ссылка на «таблицу читателей» пережила саму таблицу - и обещала то, чего нет, — с той самой правки, которая таблицу и убрала. - -## 39. Спринт без цели — законный случай (2026-08-07) - -Цель была обязательной: `sprint start --goal` требовал слаг, `check` считал -ошибкой набор без названной цели, `sprint take` отказывал задаче под чужой -целью. Модель описывала только спринт развития — а спринт бывает под багфикс, -под техдолг, под здоровье проекта. Такой набор собран **по работоспособности, а -не по направлению**, и цели у него нет не по недосмотру. - -Обходной путь существовал и был хуже прямого: завести цель-пустышку («Здоровье -проекта») и вешать под неё `fix`-и. Тогда `ROADMAP.md` — документ про то, что -приложение умеет, — обрастает строками про то, что оно не ломается, а тег -`goal:` перестаёт значить направление. - -**АЕААА. Цель у спринта необязательна, но её отсутствие — ответ, а не молчание.** -`sprint start` принимает `--goal <слаг>` **или** `--no-goal`, и голое отсутствие -обоих — отказ с объяснением. Причина в стимуле: цель называет человек, и это -единственный продуктовый вопрос всей сессии. Разреши мы заводить спринт просто -без флага — забытый флаг, лень спросить и осознанное решение стали бы неотличимы -на выходе, а дешевле всего из трёх агенту именно не спрашивать. - -**АЕААБ. В спринте без цели цель не проверяется вовсе.** Набор берёт что угодно -готовое к взятию, включая задачи под разными целями: сверять не с чем. Правило -«набор служит одной цели» не ослаблено, оно просто не применяется — целей в -таком наборе не больше одной, их ноль. Взамен машинной проверки остаётся показ -набора человеку до заморозки: у бесцельного спринта это **единственная** -проверка состава, и в скилле это сказано прямо. - -**АЕААВ. Признак «спринт идёт» — слаг, а не цель.** Прежде код спрашивал цель и -получал заодно ответ про то, открыт ли спринт; теперь эти вопросы разошлись. -Слаг подходит на роль признака лучше цели по существу: он есть у любого спринта, -потому что без него нечем проставить `sprint:<слаг>`, то есть нечем собрать -урожай. Поле «Цель» в шапке остаётся на месте и у бесцельного набора — пишется -прозой без ссылки: **«цели нет» и «цель потерялась» обязаны различаться**. - -### Что из этого следует - -145. **Необязательное поле, которое всё же решают, заводится парой «значение или - явный отказ».** Умолчанием тут был бы не выбор, а его отсутствие — и - отличить его от забывчивости уже не смог бы никто, включая автора. -146. **Признак «сущность существует» нельзя вешать на её необязательное поле.** - Пока цель была обязательной, `sprint_goal()` отвечал сразу на два вопроса, - и это работало ровно до тех пор, пока второй ответ не понадобился отдельно. -147. **Снятая проверка называет, что осталось вместо неё.** Цель не проверяется - — значит, за состав отвечают показ человеку и строка доклада; иначе - послабление читается как «здесь можно не думать». - -## 40. Три категории документов: не всякий документ — тема ревью (2026-08-07) - -Решение 36 объявило: **каждый документ проекта — тема ревью**. Правило дало -открытый список тем и сделало `docs/` конфигурацией конвейера — это работает и -остаётся. Но оно же оказалось неверным ровно наполовину, и потому вредным -целиком. - -Паспорт и схему хранилища ревью читает, но темами они не являются: по ним нельзя -сказать «в этом изменении сделано не так», они задают границу, по которой судит -**чужая** тема. Журнал решений и журнал наблюдений ревью изменения не нужны -вовсе: ADR объясняет прошлое решение, а не предъявляет требование к изменению. - -Ломалось это механически. Разметчик, применявший правило буквально, обязан был -либо завести фантомные темы `passport`, `adr`, `database`, `research` и -продублировать ими работу тем `architecture` и `operations`, либо потерять четыре -документа молча. Обе ветки случались; в собственном образце плана разметчика -`docs/passport.md` не попадал ни строкой, а его же обязательная арифметика -покрытия («документов найдено N, все N разнесены») при этом не сходилась. - -**АЕАБА. Разрез один и проверяемый: можно ли по документу сказать «в этом -изменении сделано не так».** Отсюда три категории. **Тема** — да, прямо -(`conventions`, `security`, `architecture`, свои документы проекта). **Источник -темы** — нет, но он задаёт границу для чужой темы (`passport`, `database`, -`CLAUDE.md`, `openspec/specs/`). **Процессный документ** — нет, он про то, как мы -работаем (`tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json`). - -**АЕАББ. Открыта одна категория из трёх.** `источник` и `процессный` перечислены -поимённо и проектом не пополняются; открыта только `тема`. Прежняя формулировка -«не темы ровно две» противоречила собственной раскладке канона — `.pm.json` был -третьим, и правило-исправление жило в чужом плагине, в коде `docs.py`. Теперь -документ, которого нет в раскладке, — однозначно своя тема проекта, и решать -нечего. - -**АЕАБВ. «Не судит по нему» и «не открывает» — разные вещи.** `docs/review.*` -проходы читают на каждом прогоне: там вопросы по темам, журнал дефектов, типовые -узлы, типовые ложноположительные. Это чтение конвейером **своей обвязки**, а не -критерия. `adr/`, `research/` и `tasks/` не открывает никто. - -**АЕАБГ. Цена решения записана, а не подразумевается.** Расхождение изменения с -записанным решением прогоном больше не ловится — это работа сверки документации -между спринтами. Измеренные числа проекта из ревью тоже ушли: проход, -опирающийся на число, обязан **снять его сам, на этом прогоне**, и приложить -команду замера. Обе потери идут обязательными строками в границы покрытия -каждого прогона, и пишет их триаж — не проход, потому что проход о том, чего в -конвейере нет, пожаловаться не может. - -### Что из этого следует - -148. **Плоское правило, верное наполовину, хуже двух правил.** Оно не даёт - половине случаев легального ответа, и исполнитель выбирает между двумя - плохими ветками — фантомной сущностью и молчащей потерей. Заметно это - становится не на определении, а на первом же образце вывода. -149. **Открытым делается одно множество, а не все.** Открытый список ценен тем, - что в него попадает незнакомое; если открыты все категории, незнакомое - попадает в произвольную. -150. **Отказ читать документ — тоже граница покрытия, и её пишет сток.** Строку - «этого не смотрел никто» некому подать снизу: проход, которого нет, отчёта - не присылает. - -## 41. Разметка задачи: одна величина, посчитанная один раз (2026-08-07) - -Разметка была стадией 0 **ревью кода** и платилась на каждом прогоне. Перед ревью -дизайна ту же самую величину — «крупное или незнакомое?» — называл сам пайплайн -задачи, то есть оркестратор, который только что довёл предложение до `propose`. -Одно и то же измерялось дважды, и один из двух раз без разведённости с автором — -ровно в той точке, ради которой разметчик и заведён. - -**АЕАВА. Разметка идёт один раз на задачу, сразу после `propose`.** Её план -обслуживает обе стадии ревью: состав ревью дизайна и таблицу тем для ревью кода. -Диффа она не видит — кода ещё нет; размер оценивается по дельта-спекам и перечню -границ задачи. - -**АЕАВБ. Осей две, ступень — максимум по ним.** **Размер** (малое, среднее, -крупное) — про объём; **сложность** (знакомое, незнакомое) — про то, известна ли -форма решения заранее. Раньше обе были склеены в один вопрос «крупное **или** -незнакомое?»: ответ получался тот же, но разметка не могла сказать «среднее, но -совершенно знакомое» — а это и есть рабочее умолчание. - -**АЕАВВ. Ступень после кода не пересматривается.** Дифф может выйти крупнее -ожидания — ступень не двинется. Пересмотр означал бы либо второй запуск -разметчика (то, ради устранения чего он и переехал), либо машинный порог, который -на нетипичной задаче срабатывает не туда. Расхождение факта с разметкой ловит -журнал дефектов, постфактум, — так же, как и всякую другую ошибку выбора ступени. - -**АЕАВГ. План на диск не пишется.** Файл-план стал бы четвёртым артефактом рядом -с `proposal.md`, `tasks.md` и `design.md`, пережил бы задачу и разошёлся бы с ней -молча. Прервался пайплайн — разметка повторяется; это самый дешёвый его проход. - -### Что из этого следует - -151. **Величина, из которой выводится состав, считается один раз и одним - агентом.** Два места, считающие одно и то же, расходятся; расходятся они - молча, и побеждает то, у которого меньше разведённости с автором. -152. **Разведённость — свойство момента, а не роли.** Тот же агент, спрошенный - до написания кода и после, даёт разные ответы; переезд по времени сделал - больше, чем сделал бы любой запрет. - -## 42. `quick` стал дешевле `standard` тремя способами (2026-08-07) - -`quick` и `standard` совпадали составом (шесть проходов) и различались глубиной -трёх тем: сверка против разбора. На практике это означало один проход, задающий -на один вопрос меньше, и потолок 4 вместо 2. Нижняя ступень не экономила почти -ничего и называлась отдельной ступенью зря. - -Отдельно выяснилось, что дешевизна конвейера держалась на двух заявленных -рычагах — узкий вход и потолок находок, — и **оба применялись к одному проходу -из шести**. У `specs` и `code` потолка не было вовсе, а вход `code` включал -чтение дома конвенций «весь и целиком» на каждой задаче. - -**АЕАГА. `quick` теряет приёмник тем.** Темы `security`, `operations` и -`architecture` на этой ступени закрывает `code` сверкой с **записанными -инвариантами** `CLAUDE.md`, потолком 1 находка на все три. Это не «глубина -ниже» — это **другой дом темы**, куда более узкий, и в плане он так и называется. - -**АЕАГБ. Приёмник тем запускается тогда и только тогда, когда ему есть что -принимать.** Правило было в `wide` («нет своих тем проекта — не запускается») и -теперь распространено на `quick`. Совпадение неслучайное: темы ядра `basics` -держит ровно на одной ступени из трёх, а приёмником проектных тем работает на -всех. - -**АЕАГВ. Вход и потолок применены к каждому проходу с мнением.** На `quick` -`specs` читает только дельта-спеку, `code` — только индекс конвенций. Потолки -напечатаны и раздельны по половинам `code`: 3 технических, 2 конвенционных, 1 по -инвариантам. Раздельность обязательна — конвенционных находок больше по -построению, и в общем списке они вытеснили бы техническую половину, чей пропуск -дороже. - -**АЕАГГ. Сработавший потолок объявляется.** Проход, срезавший находки, говорит -строкой, сколько осталось за срезом и какого рода. Молчащий срез неотличим от -«больше не нашлось» — тот же класс молчащего пропуска, против которого написан -весь конвейер. - -**АЕАГД. Отрицательный тест `quick` стал жёстче, а не мягче.** Вопросы «обратима -ли миграция» и «что с записями новой версии после отката» задавал приёмник тем; на -`quick` его нет. Значит изменение, которое не откатывается обратной правкой, на -`quick` не идёт вовсе — каким бы малым оно ни было. - -### Что из этого следует - -153. **Ступень, не дающая экономии, не нужна.** Две ступени, различающиеся одним - вопросом одного прохода, — это одна ступень с шумом в отчёте. -154. **Рычаг, применённый к одному исполнителю, — не рычаг, а исключение.** - Заявленный механизм экономии проверяется перечислением: к кому он применён и - к кому нет. -155. **Проход без потолка выдаёт столько находок, сколько нашёл поверхностей.** - Ровно из-за этого был снят проход независимой реализации; тот же механизм - работал у `code` и `specs` и не был замечен, потому что счёт никто не считал. - -## 43. Ревью дизайна тоже растёт ступенями (2026-08-07) - -Состав ревью дизайна включался одним условием: `specs` всегда, `rubric` и -`architecture` — вместе, «при крупном или незнакомом». Значит `standard` получал -на предложении ровно один проход, то есть не отличался от `quick` ничем. - -**АЕАДА. Три ступени вместо двух: `quick` — `specs`; `standard` — плюс `rubric`; -`wide` — плюс `architecture` и вопрос автору о трёх формах решения.** - -**АЕАДБ. Рубрика съехала вниз, архитектура осталась наверху, и это не -симметричная правка.** Они зарабатывают на разном. Рубрика порождает **свойства -узла** и окупается уже на среднем изменении: её выход уезжает приёмочными -критериями в `tasks.md` и работает потом на всей задаче. Архитектура отвечает на -вопрос «не появился ли второй способ», а он на среднем знакомом изменении -отвечается «нет» ещё до запуска — держать её ниже `wide` значит платить за -предсказуемый ответ на каждой задаче. - -**АЕАДВ. Тривиальность задачи больше не решает состав ревью.** Раньше она решала, -звать ли ревью предложения вовсе; теперь глубину обеих стадий называет ступень, а -тривиальная задача просто получает `quick`. «Пропустить ревью дизайна» и «пройти -его одним самым дешёвым проходом» — разные вещи: сверка дельта-спек стоит -меньше, чем разбор того, что она поймала бы на готовом коде. - -### Что из этого следует - -156. **Проходы, включаемые одним условием, стоит разводить по тому, на чём они - зарабатывают.** Общее условие — признак того, что их не сравнивали между - собой, а не того, что они равноценны. -157. **Средняя ступень обязана отличаться от нижней на обеих стадиях.** Иначе - «рабочее умолчание» отличается от исключения только именем. - -## 44. Метка задачи: одно значение, по которому выбираются все ревьюверы (2026-08-07) - -Решения 41–43 развели классификацию на две оси и свели состав обеих стадий ревью -к их максимуму. Значения этого максимума назывались `quick`, `standard`, `wide`, -а сам он — «ступень». Оба имени описывали **ревью**: как глубоко смотрим, на -какой ступеньке идём. Классифицируется же при этом **задача**, и результат -классификации принадлежит ей, а не прогону. - -Расхождение не косметическое. Пока величина называлась свойством ревью, её было -естественно пересчитать на каждом прогоне — что конвейер и делал, пока разметка -не переехала к `propose`. Имя тянуло назад к устройству, из которого её только -что вынули. - -**АЕАЕА. Классификация выдаёт задаче метку: `small`, `medium` или `large`.** -Метка принадлежит задаче, ставится один раз при разметке и дальше только -читается. Все проходы обеих стадий получают её в задании и обязаны напечатать в -границах покрытия. - -**АЕАЕБ. Метка — единственный вход выбора исполнителей.** Ни класс задачи, ни её -тип, ни тривиальность, ни ощущение важности состав больше не определяют. У -конвейера один переключатель, и он напечатан в каждом отчёте. - -**АЕАЕВ. Слово «ступень» удалено, а не оставлено синонимом.** Два имени одной -вещи расходятся — это ровно решение #37 про тему и проход. Метка ordered: `small` -< `medium` < `large`, и там, где нужен порядок, говорится «младшая» и «старшая -метка», а не вводится второе существительное. - -**АЕАЕГ. Метка — не синоним размера, и это записано там, где ошибиться легче -всего.** Совпадают они в одном углу таблицы из трёх: малое **незнакомое** -изменение получает `large`, трогая один узел. Поэтому план печатает три строки — -размер, сложность, метка, — каждую со своим обоснованием, и выводить одну из -другой запрещено. Проход, определивший объём диффа по метке, ошибётся именно на -том случае, ради которого верхняя метка и заведена. - -### Что из этого следует - -158. **Имя величины должно называть её носителя, а не потребителя.** «Ступень - ревью» звала пересчитывать себя на каждом прогоне ревью; «метка задачи» - считается там же, где живёт задача. -159. **Переключатель состава должен быть один и печатный.** Пока их два — - тривиальность и ступень, — состав выводится из пересечения, а пересечение - нигде не напечатано целиком. -160. **Русские слова для осей, английские для значения.** Оси — суждение и - читаются прозой (`малое`, `знакомое`); метка — идентификатор, который - проходы сравнивают, и потому она английская. Тот же разрез, что «имена - файлов английские, текст русский» в каноне, и он же снимает путаницу - «крупное» против `large`. - -## 45. Корректор метки, доля `small` и корпус оценки (2026-08-07) - -Три правки по следам решений 41–44, и все три закрывают дыры, которые эти решения -и открыли. - -**АЕАЖА. Сигнал о заниженной метке переехал в `review-code`.** Он жил в -`review-basics` — единственном месте. А `basics` с меткой `small` не запускается, -если у проекта нет своих тем: значит на типичном проекте задача с меткой `small` -шла **без рантайм-проверки** того, что метка верна. Дыра появилась ровно вместе с -удешевлением `small` и попала в самую вероятную точку ошибки: занижают туда, где -дешевле, а цена занижения там же и выросла — три темы ядра смотрятся только -против инвариантов. - -`code` подходит по построению: он идёт при **любой** метке, видит дифф целиком, а -на `small` уже читает инварианты — то есть держит в руках весь материал, из -которого сигнал выводится. У `basics` сигнал остаётся вторым, подтверждающим: он -смотрит оптикой тем и видит то, чего не видно из кода как кода, — что вопросов, -отложенных до `large`, накопилось слишком много. Триаж теперь обязан сказать и -когда сигнала **нет**: «корректор отработал, возражений нет» и «корректор не -запускался» по молчанию неразличимы. - -**АЕАЖБ. У `small` появилась доля, и она сформулирована сравнением, а не числом.** -`small` не должен обгонять `medium`; ориентир — до трети задач. Проверка нужна -именно теперь: пока `quick` и `standard` совпадали составом, дрейф между ними не -стоил ничего, и её не было. Сейчас он стоит трёх тем ядра. У дрейфа вниз есть -стимул, и он назван: метку выбирает не автор, но по описанию, написанному -автором, — занижённое описание даёт занижённую метку без чьего-либо умысла. - -**АЕАЖВ. Размер оценивается по корпусу из пяти источников, а не по дельта-спекам.** -Разметчик читал `proposal.md` и `tasks.md`, но `design.md` не открывал вовсе, а -метод был описан одной фразой «размер считается по дельта-спекам». Дельты -описывают заказанное **поведение** и молчат об объёме работы: шесть шагов в двух -узлах видны в `tasks.md`, а факт, что форму решения выбирали из нескольких, — -только в `design.md`. Каждый источник получил свою строку по каждой оси, и каждая -цифра в обосновании обязана быть привязана к источнику поимённо. - -Отсюда два правила, которых раньше не было. **Расхождение источников по объёму -разрешается в пользу большего** — и это не «спорное решается вниз»: то правило -разрешает ничью при равных данных, а здесь один источник просто видел больше. -**Само расхождение — довод за `незнакомое`:** если о задаче написано так, что -источники не сходятся в объёме, форму решения по ней не знают. Отсутствие -`design.md` у нетривиальной задачи читается так же — «форму знали заранее» ничем -не подтверждено. - -### Что из этого следует - -161. **Корректор обязан идти чаще, чем корректируемое.** Проверяющий, который - запускается реже проверяемого, оставляет дыру именно там, где выбор был - самым дешёвым, — то есть там, где ошибаются. -162. **Отсутствие сигнала — тоже сигнал, и его надо печатать.** Молчание - корректора неотличимо от его отсутствия, а решения по ним разные. -163. **Проверка доли формулируется сравнением, а не порогом.** «Меньше, чем - `medium`» считается по любому журналу и не требует спорить о числе; порог - «не больше 30%» спорен ровно настолько, насколько несопоставимы задачи. -164. **Оценка по одному источнику — оценка по остатку.** Источники о задаче - отвечают на разные вопросы; пропущенный не ухудшает точность понемногу, а - оставляет ось без данных. - -## 46. Правило выбора метки съехало из скилла в отдельный документ (2026-08-07) - -**АЕАЗА. У правила выбора метки теперь свой дом — `references/review-levels.md`, -а в скилле остался диспетчер.** `SKILL.md` конвейера дорос до 1168 строк, и -двести с лишним из них отвечали на вопрос, который на обычной задаче не задаётся -вовсе: **как** выбирается метка. Метку называет `review-scope` один раз, до обеих -стадий; всем остальным нужна не она, а состав по уже названной метке — три строки -таблицы. Переехали правило двух осей, «спорное решается вниз», «максимум по -поверхности», разбор того, чем `small` дешевле `medium`, и обе проверки долей. -Остались таблица состава, схема процесса и раздача тем. - -**Форма выбрана одна на все метки, а не по документу на метку.** Предлагался -разрез по образцу типов задач в `av-dev-pm:tasks`, где у `fix`, `feature` и -`chore` по своему файлу. Аналогия не переносится, и по двум причинам. Типы задач -**разъединены** — общее вынесено в `task-format.md`, а в файле типа лежит только -своё; метки же **вложены**: `medium` это `small` плюс два прохода, `large` — -`medium` плюс доказательство. Три файла повторяли бы костяк трижды, а `copies.py` -такое не ловит: он сверяет дословные копии по маркерам, тогда как здесь вышли бы -почти-копии с намеренными мелкими отличиями — расхождение, неотличимое от -задуманного. Вторая причина сильнее первой: ценность этого текста **в -сравнении**. Читателю нужно не «что делает `small`», а «чем `small` отличается от -`medium`» — на этот вопрос отвечают и выбор метки, и «спорное вниз», и корректор. -Сравнение, разложенное по трём файлам, не читается. - -**Механика рычагов осталась в скилле, а не уехала с меткой.** Непуск, вход и -потолок общие для всех проходов и всех меток, их дом — раздел «Модель по -проходу». В переехавшем тексте от них только то, что они делают с `small`, и -ссылка на дом; точные потолки не продублированы. - -### Что из этого следует - -165. **Дом правила — там, где правило выбирают, а не там, где его применяют.** - Применяют состав на каждой задаче, выбирают метку один раз; текст, - обслуживающий выбор, в потоке применения лежит мёртвым грузом. -166. **Вложенные вещи не режутся по файлу на вещь.** Разъединённое (типы задач) - режется, вложенное (метки) — нет: разрез вложенного даёт дублирование - общей части, а дублирование намеренно неточное машина не сверит. - - -## 47. OpenSpec заводится скиллом, а его конфиг — часть канона (2026-08-07) - -**АЕАИА. `init` заводит OpenSpec сам, а не оставляет это человеку.** Каталог -`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил: -`openspec/specs/` объявлен домом темы `requirements`, `config.yaml` описан -абзацем — а заводилось всё руками, и не проверялось ничего. Новый проект выходил -из `init` с полным каноном документов и без каталога, без которого не работают ни -`opsx:propose`, ни ревью дизайна, ни сверка требований. Команда названа поимённо -(`openspec init --tools claude`) в трёх местах — скилле, каноне и отказе скрипта: -отказ без команды заставляет искать её в другом месте. - -**АЕАИБ. Файл из коробки хуже отсутствующего, и потому проверяется машиной.** -`openspec init` кладёт `config.yaml`, где `context` и `rules` — закомментированный -пример на английском. Такой файл читается как настроенный: он есть, он валиден, -имя правильное. Работает он как пустой, и узнаётся это по уже написанному -предложению — на другом языке, с capability по имени пакета, без единого `SHALL`. -`docs.py` проверяет четыре вещи, и каждая про молчащий пробел: каталог есть; имя -именно `config.yaml` (`config.yml` OpenSpec не читает и об этом не сообщает); -`context` и `rules.specs` не остались примером, а правила называют `SHALL`; -`context` называет `passport` и `CLAUDE.md`. - -**АЕАИВ. Форма конфига — маршрутизатор, и это разрез, а не пожелание.** -Утверждение, которое можно опровергнуть, открыв другой файл проекта, — пересказ; -строка, которая говорит, какой файл открыть, — ссылка. `context` читается при -порождении **каждого** артефакта, туда удобно дописать «чтобы агент знал», и -именно поэтому в нём заводятся вторые дома инвариантов, конвенций, гейта и правил -ревью. Машина этот разрез не проверяет — отличить ссылку от пересказа она не -умеет, — и он отдан `doc-consistency` отдельным абзацем правила «один факт — один -дом», с `config.yaml`, добавленным ему во вход. - -**Обязательными сделаны ровно два адреса — паспорт и `CLAUDE.md`.** Причина в -порядке работы: предложение пишется **до** того, как кто-либо откроет `docs/`, и -без этих двух строк его пишут, не зная ни границы домена, ни инвариантов. -Длинный список адресов превратил бы `context` во второй дом ровно тем способом, -против которого правило и заведено. - -**Образец конфига лёг в канон, а не в конвейер**, как планировалось решением C. -Форма документа принадлежит тому, кто владеет каноном документов; конвейер её -читатель. Иначе `av-dev-pipeline` завёл бы у себя описание файла, который заводит -и проверяет `av-dev-pm`, — тот же шов, что разбирали, убирая имена проходов из -канона. - -### Что из этого следует - -167. **Предпосылка, за которой никто не следит, — не предпосылка, а пожелание.** - Если условие названо обязательным, его должен кто-то заводить и кто-то - проверять; иначе оно живёт ровно до первого проекта, где о нём забыли. -168. **Заполненная форма и заполненный смысл — разные вещи, и первая маскирует - вторую.** Файл на месте, валиден, с правильным именем — и пуст по существу: - это худший вид пробела, потому что выглядит он как его отсутствие. - - -## 48. Форма чужого инструмента держится опросом инструмента, а не памятью (2026-08-07) - -**АЕАИГ. Схема и артефакты OpenSpec записаны в скрипте как слепок, и у слепка -есть сторож.** Проверка формы `config.yaml` знает имя схемы и перечень артефактов -(`proposal`, `specs`, `design`, `tasks`). Это не наше решение, а состояние чужого -инструмента: OpenSpec переименует артефакт — правила под прежним именем перестанут -применяться, конфиг останется выглядеть написанным, а канон продолжит требовать -прежнее. Все три стороны при этом молчат. - -Сторожем сделано сравнение версий: `check` спрашивает `openspec --version` -(десятые доли секунды) и сравнивает `major.minor` с той, на которой форма -сверялась. Разошлось — **замечание**, не отказ, с именем команды, которая -перепроверяет. Патч-версия в сравнение не берётся намеренно: формы она не -меняет, а нагоняй на каждый багфикс приучает пролистывать весь блок. - -**АЕАИД. Перепроверка спрашивает инструмент, а не нас.** `docs.py openspec-form` -берёт `openspec templates --json` — перечень артефактов текущей схемы — и -печатает, что разошлось с константами. Дорогой вызов вынесен из `check` -сознательно: он стоит втрое дороже опроса версии, а ответ меняется только вместе -с версией. Дешёвая проверка служит **воротами** дорогой, и дорогая не ржавеет, -потому что зовут её не по памяти. - -**Чинится расхождение в плагине, а не в проекте, и это сказано в трёх местах.** -Проект в такой ситуации не виноват и починить ничего не может: у него нет ни -констант скрипта, ни скелета, ни журнала версий канона. Замечание поэтому -адресовано владельцу плагина, а команда печатает три адреса правки списком. - -**Заодно поймано ложное срабатывание на живом конфиге.** Первый вариант искал -ключи `rules` отступом по всему файлу и нашёл их внутри литерального блока -`context: |`: строки «Language: Russian» и «av-dev-pm:review-pipeline» выглядят -ключами. Проверка теперь идёт от строки `rules:` до следующего ключа нулевой -колонки. Правило, краснеющее на правде, хуже отсутствующего — его перестают -читать целиком. - -### Что из этого следует - -169. **Знание о чужом инструменте, записанное у себя, — это слепок с датой.** - Он законен, пока рядом стоит тот, кто заметит, что дата протухла; без - сторожа он превращается в уверенное враньё. -170. **Дешёвая проверка как ворота дорогой.** Опрос версии стоит копейки и - точно говорит, могла ли измениться дорогая величина. Так дорогая проверка - остаётся редкой и при этом не забытой. - - -## 49. Дом общего правила вышел из плагина (2026-08-09) - -**АЕАКА. Язык уехал в `shared/`, потому что общее правило не может принадлежать -половине.** Дом языка лежал в `av-dev-pm/skills/canon/references/language.md` — -внутри одного скилла одного плагина. Пока плагин был один, это читалось как «дом -рядом с главным потребителем». Разделение на самодостаточные `docs` и `tasks` -превращает то же место в утверждение, что язык принадлежит канону: плагин задач, -поставленный без канона, потерял бы правила письма вместе с ним. Дом переехал в -`shared/` и не принадлежит ни одному плагину, а плагины везут дословные копии. -Самодостаточность держится **копией, а не ссылкой**: `shared/` нужен этому -репозиторию, а не установленному плагину. - -**АЕАКБ. Устав вычитки стал копией целиком, а не четырьмя таблицами из десяти.** -`doc-wording` копировал из дома англицизмы, словарь, жаргон и порог правки — -четыре блока; девять правил он излагал своими словами, и эти слова с домом никто -не сверял. Там дрейф и копился молча: в доме правило «одна мысль — одно -предложение» требовало выносить придаточное, в уставе — не резать причинную -связь, и каждая версия выглядела полной. Теперь блок один, `язык-правила`, и -берётся он целиком. Условие переезда: текст правил написан безлично, а всё, -обращённое к проходу («пиши так-то», «про это молчи»), вынесено из блока в свой -раздел устава. **Правило принадлежит дому, способ доложить о нём — уставу.** - -**АЕАКВ. `порог-правки` остался отдельным блоком, и это следствие разметки, а не -вкуса.** Его берёт `task-form`, который правил языка не проверяет вовсе. -Вложенных блоков `copies.py` не знает — лежи порог внутри `язык-правила`, забрать -его отдельно было бы нечем, и `task-form` вёз бы весь устав чужого прохода. -Разрез домов идёт **по потребителю, а не по теме**: три блока вместо одного -стоят двух лишних маркеров и снимают ложную зависимость. - -### Что из этого следует - -171. **Общее правило не хранится внутри одного из тех, кто им пользуется.** Пока - пользователь один, дом рядом с ним выглядит удобством; со вторым - пользователем то же место начинает утверждать, что правило принадлежит - первому. -172. **Пересказ своими словами — это копия, которую никто не сверяет.** Блок, - взятый целиком, читается дороже, но расхождение в нём ловит машина; - сокращённое изложение экономит строки и платит молчаливым дрейфом. - - -## 50. Вычитка раздвоилась по плагину, а не по правилу (2026-08-09) - -**АЕАКГ. Решение ППП отменено, и отменено не по своей оси.** ППП говорило: агент -называется `doc-wording`, а не `task-wording`, потому что правила языка относятся -ко всем проектным текстам — документам канона, решениям ADR, запискам разведки, -— а не к одним задачам. Утверждение верно и сегодня; оно и есть причина, по -которой правила уехали в `shared/`. Но из общности **правила** не следует -общность **прохода**: `docs` и `tasks` расходятся самодостаточными плагинами, а -самодостаточный плагин не может зависеть от агента соседа. Проходов теперь два, -`doc-wording` и `task-wording`, и разведены они **по охвату** — впервые в этом -репозитории: и `task-form` против вычитки, и `doc-consistency` против -`doc-code-drift` разведены по глубине. - -**АЕАКД. Разрез по охвату дублирует устав, и потому весь общий текст стал -домом.** Два прохода судят по одним и тем же девяти правилам; отличаются они -входом, соседями по границе и тем, чем подставляется находка — командой `edit` у -задач, редактором у документов. Написать уставы порознь значило бы завести ровно -тот дрейф, который днём раньше нашёлся внутри самого `doc-wording`. Общими -домами стали `язык-правила`, `порог-правки` и новый `вычитка-доклад` — форма -находки и границы покрытия. Копий в каждом уставе 151 строка, своего непустого -текста — 61 у `doc-wording` и 75 у `task-wording`, и это ровно то, чем проходы -отличаются: вход, соседи, машинная проверка, способ подстановки. - -**АЕАКЕ. `вычитка-доклад` — контракт прохода, а не правило языка, и лежит он всё -равно в `shared/language.md`.** Заводить под пятнадцать строк отдельный файл -дороже, чем назвать раздел честно. Признак дома здесь не тема, а **число -потребителей больше одного при обязательной дословности**: разойдись два прохода -формой доклада, зовущий скилл разбирал бы два формата вместо одного. - -### Что из этого следует - -173. **Общность правила и общность исполнителя — разные оси.** Правило бывает - одно на всех и при этом требует по исполнителю на упаковку: правило - принадлежит предметной области, исполнитель — тому, кто его поставляет. -174. **Разрез по охвату обязан быть оплачен домом.** Разделение по глубине даёт - два разных текста и держится само; разделение по охвату даёт два - одинаковых, и без помеченной копии они разъезжаются — тем вернее, что - каждый по отдельности выглядит осмысленным. - - -## 51. av-dev-pm расколот: владение пошло по тому, что ставится порознь (2026-08-09) - -**АЕАКЖ. Один плагин владел двумя вещами, и это мешало обеим.** `av-dev-pm` держал -документацию проекта и учёт работ. Пока владелец был один, сцепки выглядели -удобством: `docs.py` требовал `docs/tasks/` и звал внутрь `tasks.py` -подпроцессом, настройки задач лежали ключом в `docs/.pm.json`, язык проектных -текстов — внутри скилла `canon`. Каждая из трёх на расколе оказалась не -удобством, а утверждением, что половина принадлежит другой половине. Теперь -плагина два, `av-dev-docs` и `av-dev-tasks`, и каждый ставится сам по себе. - -**АЕАКЗ. Самодостаточность держится копией, а не ссылкой.** Ссылка в дерево -соседнего плагина работает ровно до того момента, когда сосед не установлен, — а -это и есть тот случай, ради которого раскол делался. Поэтому все относительные -ссылки, пересекшие границу, сняты: вместо них имя скилла через пространство имён -и оговорка, что вызов может не разрешиться. То, что нужно обоим **дословно**, -стало общим домом в `shared/`: язык проектных текстов и словарь «Сопровождение и -эксплуатация». Второй выбран не по теме, а по числу владельцев — его делят -роадмап, `architecture.md` и тема ревью `operations`, то есть три плагина, и ни -один им не владеет. Три перечня «чем держат проект» уже разъезжались молча. - -**АЕАКИ. OpenSpec отдан тому, кто им работает, а не тому, кто о нём написал.** -Версия 7 канона объявила `openspec/` своим слотом, и разрез вышел не по владению: -без каталога не запускается конвейер, а не канон. Заведение и форма файла уехали -в скилл `av-dev-pipeline:openspec`, отсутствие каталога стало для `docs.py` -неприменимостью вместо отказа. **Остаток назван, а не замолчан:** проверка формы и -сторож версии пока остались в скрипте канона, потому что своего скрипта у -конвейера нет ни одного, — то есть у файла сейчас два плагина, один заводит, -другой проверяет. Это записано и в журнале версий как временное состояние. - -### Что из этого следует - -175. **Сцепка внутри одного владельца не видна, пока владелец один.** Она - выглядит удобством ровно до раскола и обнаруживается не рассуждением, а - попыткой поставить половину отдельно. Отсюда и порядок работ: сперва - разнести, потом чинить то, что перестало сходиться. -176. **Разрез владения идёт по тому, кто инструментом пользуется, а не по тому, - кто о нём написал.** Канон описывал OpenSpec подробнее всех и потому казался - его владельцем; работает по нему конвейер, и слот принадлежит конвейеру. - - -## 52. Валидатор поехал за файлом: у конвейера появился свой скрипт (2026-08-09) - -**АЕАКК. Названный остаток закрыт, и закрыт он ценой первого скрипта в -конвейере.** Решение 51 отдало OpenSpec конвейеру и честно оставило хвост: -проверка формы `config.yaml` и сторож версии остались в `docs.py`, потому что -своего скрипта у пайплайна не было ни одного. Хвост оказался не косметическим — -это ровно то состояние, против которого написан весь канон: **у файла два -владельца, один заводит, другой проверяет**, и разойтись они могут молча. 252 -строки переехали в `av-dev-pipeline/skills/openspec/scripts/openspec.py`; в -`docs.py` от темы не осталось ни константы. - -**Переезд оплатился сразу, и не тем, чего ждали.** Прежняя проверка требовала, -чтобы `context` называл `docs/passport.md` и `CLAUDE.md`, **безусловно** — то есть -на проекте без канона документов требовала ссылку на несуществующий файл. Пока -проверка жила в скрипте канона, допущение «канон есть» было незаметным: скрипт -канона запускают там, где канон есть. В скрипте конвейера то же допущение стало -видно на первом же прогоне. Теперь адрес требуется только к документу, который в -проекте есть, а его отсутствие идёт строкой «не проверялось» с названной ценой. - -### Что из этого следует - -177. **Неявное допущение видно из другого дома, а не изнутри своего.** «Канон - есть» было верно всюду, где код лежал, и потому не читалось как допущение - вовсе. Переезд — самый дешёвый способ его обнаружить: не разбор, а смена - места, из которого на код смотрят. - - -## 53. `canon` и `docs` остаются двумя скиллами (2026-08-09) - -**АЕАКЛ. Слияние отклонено, и довод у него не про объём.** Оба скилла лежат в -одном плагине, и слить их казалось естественным завершением раскола. Мешает -`description`: это не аннотация, а **триггер** — по нему загрузчик решает, звать -ли скилл вообще, и ровно ради его сохранности заведён `frontmatter.py`. Моменты -вызова у этих двух разные. `canon` срабатывает на «проверь документацию», -«переведи на канон», «пришёл в старый проект»; `docs` — на «задача сделана, -обнови документацию», «заведи ADR», «запиши наблюдение». Одно описание покрывает -оба хуже, чем два покрывают каждое своё, и потеря здесь не в читаемости, а в том, -что скилл перестаёт находиться. - -Второй довод — тот же разрез, что репозиторий подтверждал уже трижды: -**раскладка против содержимого**, «где лежит» против «что внутри». Он по глубине, -а такой разрез, в отличие от разреза по охвату (решение 50), даёт два разных -текста и держится сам, без помеченных копий. - -### Что из этого следует - -178. **Границу между скиллами держит не тема, а момент вызова.** Два текста об - одном предмете живут порознь законно, если зовут их в разные минуты; и - наоборот — один предмет, разрезанный так, что оба куска нужны одновременно, - разрезан неверно. - - -## 54. Стык плагинов: правило получило дом, адреса остались у владельцев (2026-08-09) - -**АЕАКМ. Вопрос пришёл с другой стороны: ревью опирается на документы проекта, -но не должно жёстко предполагать, где файл лежит; напрашивалось оглавление -адресов и сводка возможностей скиллов в `CLAUDE.md` проекта.** Отклонено и то и -другое, но не потому, что проблемы нет. - -**Оглавление адресов — второй дом раскладки.** Канон жёсток намеренно: пути -фиксированы, проект подгоняется под них, и цена этого записана в самом каноне. -Указатель в `CLAUDE.md` отменяет ровно эту цену — раскладка получает второе -описание, и разойдутся они молча. Здесь молчание особенно дорогое: прогон ревью -умеет **честно деградировать**, и протухший адрес попадает прямо в эту машинерию — -файл не открылся, в границах покрытия появляется строка «документа в проекте -нет», и отчёт выглядит добросовестным. Прямой путь в той же ситуации ломается -громче. - -**Сводка возможностей — второй дом описаний.** `description` во фронтматтере это -триггер, по нему скилл и выбирается; переписанная руками сводка тех же описаний -не сверяется ничем. - -**Настоящий пробел был в другом, и он измерен.** Правило обращения к соседнему -плагину стояло в пяти местах в пяти редакциях: - -| Где стояло | Довод | Ветка «не разрешился» | -| --- | --- | --- | -| `task-pipeline` | устаревшая проектная копия | нет | -| `task-batch` | то же | нет | -| `review-pipeline` | вшито в пункт про удаление проектных копий | нет | -| `openspec` | путём в чужое дерево — никогда | есть | -| `canon` | — | есть | - -Два разных довода, и ни в одном месте не было обоих; три места из пяти молчали о -том, что делать при неразрешившемся вызове, — то есть о единственном, ради чего -правило написано. Плюс невысказанный инвариант: `$CLAUDE_PLUGIN_ROOT` ведёт -только в свой плагин, употреблён двадцать раз и нигде не оговорён — а именно он -соблазняет дописать `/../av-dev-docs/`. - -**Сделано:** дом `shared/plugin-boundary.md`, блок `граница-плагинов`, семь -помеченных копий — четыре скилла конвейера и три скилла канона. В дом вошли -полное имя, запрет пути в чужое дерево, ветка «не разрешился» с обязанностью -доклада и признак присутствия по заведённому соседом файлу. - -**Разрез, по которому дом наполнялся: правило общее, последствие местное.** «Нет -`av-dev-tasks` — учёт остаётся владельцу» знает только конвейер; «нет конвейера — -`docs.py` о каталоге `openspec/` молчит» знает только канон. Держи дом -последствия — он знал бы наперечёт всех своих потребителей и стал бы вторым -каноном. - -**Адреса при этом наружу не поехали.** У них владелец есть: раскладку `docs/` -держит канон, каталог задач — плагин задач. `shared/` заводится **только для -фактов без владельца**; чужое с владельцем остаётся дома, а сходимость упоминаний -в чужих деревьях проверяет машина — `scripts/addresses.py`, тем же заходом. - -**Что выяснилось при написании чекера: судить незнакомое нельзя.** Первый прогон -дал шесть находок, и три из них были не дрейфом, а свойством канона: `docs/**` — -шаблон, а `docs/accessibility.md` в двух местах — пример **своей темы проекта**, -которую канон разрешает заводить произвольно. Список тем открытый, значит -незнакомое имя опровергнуть нечем, и проверка «есть ли такой документ у -владельца» ловила бы законное. Переименование при этом ловится точно и по другому -основанию: канон, убирая слот, кладёт его в карту переездов `RETIRED` — она и -есть перечень запрещённого. Рядом одна догадка: имя, почти совпавшее с -каноническим, читается как опечатка. Порог замерен по репозиторию — законные -имена дают до 0.64, опечатки от 0.91, и между ними пусто. - -Четвёртая находка оказалась настоящей: `REMAINING.md` иллюстрировал смысловой -дубль адресом `docs/specs/recognition.md` — слотом, упразднённым в версии 1 -канона, то есть при десяти нынешних. -Пример, который сам протух, — ровно то, ради чего чекер и писался. - -### Что из этого следует - -179. **Механизм честной деградации превращает протухший адрес в правдоподобный - доклад.** Там, где отсутствие источника — законный исход с названной ценой, - ошибка адреса неотличима от этого исхода. Значит адрес в таком месте обязан - сверяться машиной, а не аккуратностью: единственная альтернатива — - ломаться громко, а именно её деградация и убирает. -180. **`shared/` — для фактов без владельца, и только.** У адресов владелец есть, - и вынести их наружу значило бы отобрать у него его же предмет. Признак - верного дома не «нужно нескольким», а «никому из них не принадлежит». -181. **Общее правило и его последствия живут порознь.** Правило можно вынести в - дом, последствие — нет: оно знает про место, а место про правило знать не - обязано. Дом, вобравший последствия, становится реестром потребителей и - устаревает быстрее их всех. -182. **Проверять надо запрещённое, а не незнакомое, когда словарь открыт.** - Открытый список делает «нет такого имени» неопровержимым, и проверка на - принадлежность перечню начинает ловить законное. Ловится ровно то, что - владелец объявил упразднённым: карта переездов — не побочный артефакт - миграции, а перечень запрещённого, и стоит она ровно там, где нужна. -183. **Замер порога записывается рядом с порогом.** Число, выбранное на глаз, - через месяц неотличимо от подогнанного под один случай. Обе стороны разрыва - названы (0.64 и 0.91) — и видно не только, что порог верен, но и насколько - он не на грани. - -## 55. `task-pipeline` стал `resolve`: два плановых стопа вместо полной автономии (2026-08-09) - -**АЕАКН. Автоматическое решение задач агентом признано утопией — «работает, но -работает плохо», — и хуже того, автор перестал ориентироваться в собственном -процессе.** Отсюда разворот: задачи решаются по одной, а в цикл возвращается -человек. `task-batch` удалён целиком; `task-pipeline` переписан в `resolve`. - -**Прежняя доктрина звучала «умолчание — делать, а не спрашивать», и она не -отменена, а ограничена.** Полностью автономный прогон плох не тем, что ошибается, -а тем, что ошибку видно на готовом коде: развилка, стоившая бы абзаца до -`propose`, стоит переписывания после `apply`. Постоянное же согласование -возвращает ту цену, ради ухода от которой пайплайн и писался. Разрез поэтому по -**месту**, а не по важности решения: развилка, найденная до ближайшего чекпоинта, -копится в него; найденная после последнего — по-прежнему уходит вопросом в запись, -и задача доводится в объявленных границах. - -**Чекпоинтов два, и второй обязателен всегда.** - -- **«варианты»** — у исследовательской задачи, до первого требования. Признак - ветки не объём работы, а **отсутствие одного очевидного способа решения**: - обсуждать варианты после `propose` поздно, предложение уже воплотило один из - них, и разговор пойдёт не о выборе, а о переделке. Форма ограничена сверху — - 2–4 варианта: больше четырёх человек не сравнивает, а признаёт неспособность - сравнить и просит рекомендацию. -- **«объяснение»** — у всякой задачи, **после** ревью дизайна. Порядок обоснован: - человек читает то, что уже просеяла машина, и не тратит внимание на выловимое - `review-specs`. Внимание здесь самый дорогой ресурс процесса. - -**Объяснение не стало новым артефактом, и это главная правка первоначального -замысла.** Задумывалось отдельным разделом в `design.md`; при разборе оказалось, -что оно там было бы **третьим домом** одного и того же: в `proposal.md` уже есть -`## Why` («в чём проблема»), в `design.md` — рассмотренные варианты. Поэтому -объяснение **собирается из двух существующих артефактов**, а требование к их -форме уехало в `openspec/config.yaml` — `rules.proposal` и `rules.design`. Это -единственное место, применяющееся **в момент написания**, а не после. -Побочная выгода: `design.md` с названными причинами отказа — половина будущего -ADR, а промоут ADR читает именно архивный `design.md`. - -**Закрыт вопрос, висевший в плане открытым: что делает автоматический участок, -когда ревью кода спорит с одобренным дизайном.** Признак проверяемый — -**меняются ли дельта-спеки**. Не меняются: находка внутри дизайна, дожимается -сама. Меняются: решение стало другим, а одобрено было прежнее — разметка -пересчитывается (правило уже было) и **чекпоинт повторяется**. Чекпоинт, который -можно обойти находкой ревью, не значит ничего, и хуже того — человек уверен, что -одобрил именно то, что уехало в коммит. - -**Удаление `task-batch` обошлось дороже своего каталога.** На нём держались: -третий режим `review-specs` (стык после слияния) вместе с исключением «живого -change нет — берём источником актуальные спеки»; единственное исключение из -правила `review-triage` «плана нет — не запускаюсь»; и обоснование имени основной -ветки в каноне — «в неё вливает батч». Первые два — послабления, существовавшие -только ради батча, и с ним они исчезли, сделав оба правила строже. - -### Что из этого следует - -184. **Автономность ограничивается местом, а не важностью решения.** «Спрашивать - о важном» неисполнимо: важность оценивает тот же, кто хочет закончить. - «Копить до ближайшего планового стопа» проверяемо и не требует суждения. -185. **Чекпоинт ставится после машинной проверки, а не до неё.** Внимание - человека тратится только на то, чего машина не ловит; порядок наоборот - сжигает его на выловимом и обесценивает саму остановку. -186. **Объяснение для человека не заводит своего артефакта.** Если оно - собирается из уже существующих, оно не может с ними разойтись; отдельный - текст «то же, но понятнее» — третий дом, и расходится он молча. -187. **Послабление, введённое ради одного потребителя, уходит вместе с ним.** - Исключение переживает своего заказчика и выглядит общим правилом; удаляя - потребителя, ищи его исключения — они и есть настоящий хвост. - -## 56. `av-dev-pipeline` → `av-dev-code`, `review-pipeline` → `review` (2026-08-09) - -**АЕАКО. Имя описывало устройство, а не предмет.** «Пайплайн» говорит, что внутри -конвейер, — а плагин занят кодом по задачам, и после появления чекпоинтов он уже -не конвейер в чистом виде: между остановками автоматика, на остановках разговор. - -**Набор имён стал параллельным, и это довод сам по себе:** `docs` / `tasks` / -`code` / `git` — каждое называет **материал**, которым плагин занят. Прежнее имя -выбивалось: три существительных и одна метафора устройства. По той же причине -отвергнут `av-dev-solve` — глагол в ряду существительных, плюс заикание в главном -вызове (`solve:resolve`), — и `av-dev-work`: «работы» в этом репозитории уже -значат конкретное (цели и задачи роадмапа, секция «Сопровождение»), и имя начало -бы спорить со словарём. - -Заодно `review-pipeline` стал `review`: слово «пайплайн» ушло из плагина целиком, -а не наполовину, и скиллы выровнялись — `resolve` / `review` / `openspec`. - -**Журнал версий канона переписан вместе со всеми, и это не нарушение правила «не -переписываем задним числом».** Разрез проходит не по типу файла, а по типу -высказывания. Наблюдение и причина — неприкосновенны: их правка есть -фальсификация. **Предписание и адрес обязаны оставаться исполнимыми**: запись -версии 10 велит «проверить, что плагин `av-dev-pipeline` установлен», и проект, -дошедший до неё, выполнит невыполнимое. `DECISIONS.md` при этом не тронут — в нём -нет предписаний проекту, только записи о принятых решениях; там прежнее имя -верно, потому что описывает состояние на дату записи. - -### Что из этого следует - -188. **Имя плагина называет материал, а не устройство.** Устройство меняется — - конвейер обзавёлся остановками, — а материал остаётся. Имя по устройству - протухает первым и при этом выглядит осмысленным. -189. **Журнал не переписывается в наблюдениях и обязан оставаться исполнимым в - предписаниях.** Правило «не задним числом» защищает от подделки фактов, а не - от починки инструкций: инструкция, ссылающаяся на несуществующее, — не - свидетельство эпохи, а поломка с отложенным сроком. - -## 57. Спринты отменены: приоритет стал порядком строк, `session` стал `groom` (2026-08-09) - -**АЕАКП. Спринт отвечал на вопрос «что делать дальше» замороженным набором, а -между наборами на этот вопрос не отвечал никто.** Процесс идёт задача за задачей, -и набор перестал что-либо удерживать: он не синхронизировал (некого), не -ограничивал по времени (тайм-бокс не брали) и не защищал от врывания (врывалось -ровно два класса, оба назывались правилом). Осталась цена — обязанность собрать, -показать, заморозить и распустить. - -**Приоритет вернулся, и вернулся туда, где ему место.** Прежнее правило «порядка -нет, есть цель» было обосновано **набором спринта**, и с ним потеряло опору. -Приоритет — свойство очереди, а не задачи, поэтому его дом **индекс**: то же -исключение из правила «файл — источник истины», что уже было у «в каком индексе -лежит запись». Числом в файле он быть не мог — два соседних файла смогли бы -утверждать одно место, а строка индекса противоречить обоим. - -**Гейт готовности стоял на `sprint take` и чуть не исчез вместе с ним.** Это было -единственное место, где запись судили целиком: тип, цель у `feature`, пустой -раздел вопросов, схема типа. Без спринта момента не осталось бы вовсе, а узнают -о недописанной задаче на приёмке, когда сверять уже не с чем. Момент назвали -заново — команда `tasks.py ready <слаг>`, и зовёт её тот, кто берёт задачу. -Отказ там **код 1, а не 2**: запись не дописана — это рабочая ситуация, а не -ошибка употребления. - -**`session` стал `groom`, и предмет сузился до двух вопросов** — что сейчас -самое важное и что перестало быть важным. Из четырёх шагов прежней сессии выжили -два (вопросы, переоценка порциями), один заменился (расстановка очереди вместо -набора спринта), два выпали: - -- **приёмка закрытых задач** — грумингу не по предмету. Ритуала у неё больше нет, - остаётся `reopen` по требованию. Цена названа прямо: приёмка происходит только - тогда, когда что-то уже бросилось в глаза; -- **разбор процесса** — его якорем был прошедший спринт. Вместе с ним из скилла - ушёл прямой вызов агентов `doc-consistency` и `doc-code-drift`, и это **не - потеря, а починка**: агенты принадлежат `av-dev-docs`, и груминг звал их мимо - правила обращения к соседу, без ветки «плагина нет». Груминг теперь только - **называет повод** сверить канон, а когда их звать — решает их владелец. - -**Побочно найдено:** `canon.md` — дом определения канона — объявлял себя версией -7, когда скрипт шёл на 11. Пять версий дом врал о себе, и не заметил никто: -машина сверяет версию проекта с константой скрипта, а прозу в заголовке не -читает. - -### Что из этого следует - -190. **Правило, обоснованное механикой, умирает вместе с ней — и надо проверять, - что вопрос умер тоже.** «Порядка нет» держалось на наборе спринта; набор - ушёл, а вопрос «что делать дальше» остался и повис без ответа. Снимая - механику, ищи не только то, что на ней стояло, но и то, на что она отвечала. -191. **Гейт живёт в моменте, а не в команде.** Проверка готовности была свойством - `sprint take` — и была бы потеряна как деталь удаляемой команды. Момент - «запись впервые судят целиком» существует независимо от того, чем он - назван, и переезжает вместе с процессом. -192. **Версия в прозе, которую не читает машина, протухает молча.** Дом канона - назвал себя версией 7 при текущей 11: сверка шла по константе скрипта, а - заголовок документа не сверял никто. - -## 58. Судьи документов получили свой скилл — `healthcheck` (2026-08-09) - -**АЕАКР. Момент вызова был свойством чужого ритуала и исчез вместе с ним.** -`doc-consistency` и `doc-code-drift` звались шагом сессии между спринтами. Сессия -стала грумингом, груминг судит задачи, а не документы, и звать чужих агентов он -не вправе — они живут в `av-dev-docs`. На живом проекте их не звал бы **никто**, -кроме разовых `adopt` и `upgrade`. - -Чинить это возвратом вызова в груминг было нельзя: это ровно то нарушение -границы, которое там и обнаружилось (вызов агента чужого плагина по имени, без -ветки «плагина нет»). Момент нужно было назвать **у владельца** — и оказалось, -что владельца-то у них и нет: `canon` их звал, но владел раскладкой, а не -суждением. - -**Скилл `av-dev-docs:healthcheck`.** Предмет — то, чего машина не видит: -разошлись ли документы между собой и с кодом. Разрез с `canon check` проверяемый: -**машина сверяет форму, healthcheck — утверждения.** «Раздел есть» проверит -скрипт; «написано, что зависимость одна, а в манифесте их три» — суждение. - -**Почему скилл, а не просто описание агентов.** Триггер у агента и так есть — его -`description`. Но двоим нужна **оркестровка**: позвать обоих на весь канон разом, -передать `doc-code-drift` раздел запретов, разобрать урожай порциями, назвать -границы покрытия и то, кого именно позвал. Этого агент о себе не знает. - -**`doc-wording` внутрь не взят, и это разрез, а не забывчивость.** Ему -оркестровка не нужна: он один и работает по названному списку документов. И ритм -другой — он нужен там, где текст только что писали, а не там, где он год лежал. -Скилл, собравший всех троих «потому что все про документы», склеил бы разные -вопросы под одним вызовом. - -### Что из этого следует - -193. **Момент вызова — такая же собственность, как сам инструмент.** Агент, - чей момент назначен чужим ритуалом, теряет его вместе с ритуалом и - замолкает беззвучно: он исправен, его просто никто не зовёт. -194. **Оркестровка — вот что отличает скилл от агента.** Одному исполнителю с - ясным входом скилл не нужен, его находит описание. Скилл заводят там, где - надо решить, кого звать, что передать, в каком объёме и что делать с - результатом. - -## 59. Аудит четырьмя сабагентами: описания отстают от механики молча (2026-08-09) - -Реорганизация была объявлена законченной «на бумаге», и я запустил по аудитору на -плагин — консистентность, самостоятельность, интегрируемость. Гейт при этом был -зелёным и остался честен: он проверяет ровно то, что умеет. - -Нашлось около полусотни расхождений, и они **одного рода**. Каждый раз я правил -механику — вырезал спринт из скрипта, переименовал скиллы, перенёс судей — и -каждый раз не правил то, что механику **описывает вовне**: скелеты документов, -докстринги скрипта, уставы агентов, манифесты плагинов, README. - -Самое дорогое: **скелет `CLAUDE.md` уносил слоты спринта в каждый новый проект** -через две недели после отмены спринтов. Скелет не описывает, а порождает: его -отставание не читается, оно исполняется. - -**Механизм приоритета не запускался ни разу.** `--section` у `move` был -обязательным, а все три места, где груминг предписывает расстановку, дают команду -без него — usage error. Скилл написан, прогнан не был, и разницы между рабочим и -бумажным процессом не видно, пока его не запустят. - -**Правило границы я же и нарушал.** Восемь дословных копий «путь в дерево чужого -плагина не пишется никогда» — и пять мест, где путь написан, одно из них строкой -выше собственного «пути туда конвейер не выносит». - -Отдельно: **у описания плагина было два дома**, и три из четырёх разошлись. Класс -закрыт не дисциплиной, а машиной — `frontmatter.py` теперь сверяет `plugin.json` с -`marketplace.json`, а гейт разбужен на `*.json`. - -Правки разобраны четырьмя пропусками по одному сабагенту на пропуск, с проверкой -результата каждого: скелеты и канон, исполнимость учёта задач, границы и стыки, -словарь и манифесты. Скриптовые правки проверены поведением на фикстурах, включая -настоящий git-репозиторий для `reopen`. - -**Чего аудит не даёт.** Это было чтение. Ни один скилл по-прежнему не исполнялся -на живом проекте, и находки вроде «чекпоинт вырождается в ритуал» такой проверкой -не берутся по построению. - -### Что из этого следует - -195. **Механика проверяется прогоном, описание — только чтением.** Поэтому после - каждой правки механики отстают именно описания, и отстают молча. Меняя - механику, ищи её отражения поимённо: скелеты, докстринги, уставы агентов, - манифесты, README. -196. **Скелет дороже документа: он не описывает, а порождает.** Отставший - документ врёт одному читателю; отставший скелет уезжает в каждый новый - проект и становится там обязательным. -197. **Копия правила не заставляет его исполнять.** Правило исполняется там, где - его проверяет машина или чужой глаз; восемь копий на видном месте не - помешали автору нарушить его пятью строками. -198. **Бумажный процесс неотличим от рабочего, пока его не запустили.** Команда, - которую никто не набрал, может не существовать вовсе — и именно так и было. -199. **Два дома у факта расходятся не когда-нибудь, а сразу.** Из четырёх пар - описаний плагина совпала одна — та, которую с момента заведения не правили. - -## 60. Служебный файл зовётся по плагину-владельцу; у задач появилась своя версия формата (2026-08-11) - -Файл версии канона звался `docs/.pm.json` — по плагину `av-dev-pm`, который -распался на четыре ещё в решении 56 и которого больше нет. Имя пережило -владельца на два месяца и указывало в пустоту: читающий его искал плагин, о -котором в репозитории не осталось ни строки. Переименован в `docs/.docs.json` -записью 13 журнала канона. - -**Правило, которое из этого вынуто и теперь держит все три файла:** имя -служебного файла — имя плагина, который его завёл. `.docs.json` — канон, -`.tasks.json` — задачи, `openspec/config.yaml` — конвейер. По этому же следу -скиллы узнают, что сосед в проекте работал, и правило перестало быть просто -перечнем — оно выводимо. - -**Прежнее имя `docs.py` не читает.** Соблазн «прочитать оба и не мешать людям» -здесь стоит дороже, чем везде: по этому числу `upgrade` решает, какие записи -журнала применять, и два дома для него разъехались бы молча в том самом месте, -где расхождение и вредно. Вместо совместимости — узнавание: `check` видит файл -под старым именем и печатает готовую команду `git mv`. - -**У каталога задач появилась своя версия формата** — ключ `tasks` в -`<каталог задач>/.tasks.json` и свой журнал версий в скилле `av-dev-tasks:tasks`. -До сих пор её не было вовсе, хотя `docs.py` в комментарии уверенно ссылался на -«свою версию формата» соседа: описание опережало механику ровно так, как описано -в решении 195. Формат задач при этом менялся — записями 8, 11 и 12 чужого -журнала. - -**Число именно своё, а не копия канонического.** Плагин ставится в одиночку: -проект, взявший учёт работ без канона документов, каталога `docs/` не имеет -вовсе, а значит не имеет и версии канона — сверять было бы не с чем. Копия -чужого числа в `tasks.py` была бы вторым домом одной версии и разъехалась бы при -первом же обновлении одного плагина без другого. - -**Переезды, случившиеся до появления числа, задним числом в новый журнал не -переписаны.** Версия 1 — это формат на день её появления; что проекту нужно было -пройти до неё, названо шагом «догнать формат по журналу канона» с поимёнными -признаками отставания (каталог в `docs/tasks/`, живой `SPRINT.md`). Второй -перечень тех же шагов разошёлся бы с первым — это ровно та ошибка, из-за которой -план однажды повторял записи версий 3, 4 и 5 построчно. - -**Конфиг задач стал обязательным.** Раньше он заводился только ради имён, -отличных от умолчания, и проект с умолчаниями жил без файла вовсе. Версия — не -настройка, от которой можно отказаться, поэтому `init` и `adopt apply` пишут его -всегда, а `check` требует числа. - -Отдельно стоит сказать, чтобы не спутали при чтении журнала: `.docs.json` -однажды уже был отвергнут — решением F, но **как указатель путей**. Отвергнут -был указатель, а не имя; сегодняшний файл путями проекта не распоряжается, он -объявляет версию и называет то немногое, чего из раскладки не вывести. - -### Что из этого следует - -200. **Имя служебного файла — часть границы плагинов, а не деталь.** Оно - называет владельца, и по нему же владельца узнают. Пережившее владельца имя - врёт дважды: указывает на несуществующее и прячет того, кто файл ведёт на - самом деле. -201. **Версия нужна каждому формату, который живёт в чужом репозитории.** Без - числа «приведён ли проект» не имеет определённого ответа, и отставший - каталог выглядит здоровым до первой команды, которая об него споткнётся. -202. **Своя версия — у своего плагина, всегда.** Общее число на два плагина - переживает ровно до первого проекта, где поставлен один из них. -203. **Версию двигают руками, и это не слабость проверки.** Число отвечает - на вопрос «по какой записи повышать», а не «сделаны ли шаги по существу». - Машина, приписывающая недостающее число сама, объявляет проект приведённым - к формату, которого никто не проходил. - -## 61. Разведка и решение — два сценария одного скилла, а не два скилла (2026-08-11) - -Скилл `resolve` вёл обе работы одной цепочкой: у исследовательской задачи были -свои три шага и свой чекпоинт вариантов, после которого она **вливалась в общую -ветку** и продолжалась кодом. Разведка тем самым была не работой со своим -исходом, а прологом к коду: её ответ оседал в `design.md` будущего change, и -разведка, кончившаяся знанием, документов проекта не касалась вовсе. - -Сперва я развёл их на два скилла — `resolve` и `research`, с исходом и стопом с -обеих сторон. Через час работы стало видно, чем это плохо: **классифицировать -задачу приходится человеку до вызова**, а «есть ли у неё очевидный способ -решения» видно только после чтения записи. Разделение переносило самое трудное -суждение туда, где для него меньше всего данных. - -**Решено: точка входа одна, сценария два, выбирает сценарий скилл.** Оба сценария -живут справочниками — `references/solve.md` и `references/research.md`, — а в -`SKILL.md` остались вход, развилка и правила, не зависящие от сценария. Тем же -приёмом сложен скилл задач: общая часть в `SKILL.md`, алгоритм каждого типа в -`references/task-*.md`. - -**Порознь и одинаково — это отдельное решение.** Сперва разведка уехала в -справочник, а решение осталось в теле скилла: так вышло само, потому что решение -там уже лежало. Асимметрия читается как старшинство — сценарий в теле выглядит -основным, а сценарий в справочнике оговоркой, — и удерживает шестисотстрочный -файл, который грузится целиком даже ради разведки. - -**Что у разведки появилось своего.** Исход — знание, а не пролог: ответ уезжает в -документы канона (`av-dev-docs:docs`), задачи заводятся и уточняются -(`av-dev-tasks:tasks`), написанное коммитится, запись закрывается. Кода сценарий -не пишет вовсе. OpenSpec ему не нужен — это единственное место скилла, где тот не -предпосылка. - -**Переход между сценариями — событие с названным исходом.** Решение, упёршееся в -незнание способа, останавливается; разведка, выбравшая способ, доводится до конца -и **не переходит в код тем же прогоном** — следующий запускает человек. Причина -не в церемонии: разведка только что переписала постановку, и брать её в работу -тем же заходом значит решать за человека, стоит ли делать это сейчас, — а это -приоритет. - -**Канон пришлось тронуть, и это версия 14.** ADR цитировал только архивный -`design.md`. У решения, принятого разведкой, `design.md` нет по построению — -change по нему не будет никогда, — и такое решение либо не попадало в `adr/` -вовсе, либо попадало сочинённым заново. Теперь источников два, и оба называются в -записи. - -### Что из этого следует - -204. **Разделять работы стоит по моменту для человека, а не по роду работы.** - У разведки и решения он разный: варианты обсуждают до первого требования, - объяснение — после ревью дизайна. Всё остальное различие (пишем код или нет) - из этого уже следует. -205. **Точку входа не разделяют по признаку, который виден только внутри.** - Классификация, требующая прочитать запись, не может быть условием вызова: - человек либо ошибётся, либо прочитает запись сам — и тогда скилл ему не - нужен. -206. **Сценарий в справочнике дешевле скилла.** Скилл стоит описания, границ, - копии правил и своего места в графе вызовов; справочник наследует их у - хозяина. Заводить второй скилл имеет смысл, когда его зовут отдельно, а не - когда он просто длинный. -207. **Равные сценарии лежат одинаково.** Оставить один в теле скилла, а второй - вынести — значит назначить первому старшинство, которого в замысле нет. - Читатель это старшинство считывает, даже когда о нём не сказано ни слова. -208. **Работа без своего исхода вырождается в пролог.** Разведка, кончавшаяся - переходом к коду, не имела причины писать в документы: её ответ и так уезжал - в `design.md`. Дом для исхода — вот что делает работу работой. - -## 62. Вычитку зовёт тот, кто правил, а не тот, кто синкал (2026-08-11) - -Вычитка документов агентом `doc-wording` была привязана к **синку**: «позови его -последним шагом синка», «ничего не правивший синк агента не зовёт». Сценарий -разведки о себе говорит обратное — «правило принуждённого отрицания здесь не -действует, это не синк», — и при буквальном чтении вычитка не доставалась ему -вовсе: документы правились, а звать было некому. Гейт перед коммитом машинный, он -смотрит раскладку и битые ссылки, а не залог и неизвестный термин. - -**Условие вызова теперь — правка, а не обряд, внутри которого она случилась.** -Признак читается буквально: документы правились — зови, ничего не правил — не -зови. Синк остался самым частым вызывающим, но перестал быть единственным. - -**У разведки вычитка стала своим шагом, а не оговоркой внутри чужого.** Она стоит -между записью и гейтом, потому что раньше пачка не полна: разведка правит две -вещи сразу — документы канона и записи каталога задач, — и собирается пачка -только к концу пятого шага. После коммита вычитка правила бы уже закоммиченное. - -**Обе пачки судятся своими проходами.** Документы — `doc-wording`, записи задач — -`task-form`, затем `task-wording`; владеет каждым проходом его плагин, и разведка -их не зовёт напрямую, а просит владеющий скилл. - -**Запрет остался, но только на судей канона.** `doc-consistency` и -`doc-code-drift` идут на весь канон разом и стоят дорого — их момент выбирает -человек через `av-dev-docs:healthcheck`. Смешение этого запрета с вычиткой и было -второй половиной поломки: «агентов по документам на отдельной работе не зовут» -читалось как правило про всех троих. - -### Что из этого следует - -209. **Правило, привязанное к названию обряда, не срабатывает у того, кто себя - этим обрядом не считает.** Условие вызова формулируется через наблюдаемое - действие — «правил текст», — а не через имя фазы, внутри которой оно обычно - происходит. -210. **Дорогая проверка и дешёвая проверка не живут под одним запретом.** Довод - «не зови агентов сам» верен для судей на весь канон и обратен для вычитки - названной пачки; общая формулировка отменяет вторую вместе с первой. -211. **Шаг, собирающий пачку, стоит после последнего, кто в неё кладёт.** Вычитка - на шаге записи проверила бы половину написанного, а после коммита — уже - историю. - -## 63. Обслуживание — третий сценарий: у цикла SDD там нет входа (2026-08-13) - -Задача, не меняющая поведения — тулчейн и сборка, зависимости, гит-хуки, перенос, -чистка, — шла полным циклом решения: `propose`, разметка, ревью дизайна, -чекпоинт, `archive`. Все пять шагов стоят на дельта-спеках, а у типа `chore` -дельта-спек **нет по построению**: тип определён через «наблюдаемое поведение не -меняется». Цикл не урезается ради дешевизны — он остаётся без входа, и change, -заведённый под такую задачу, пуст, а разметчик по нему называет не те темы. - -**Признак сценария — связка из двух проверок, и обе обязательны.** Тип записи -предлагает (`chore`, реже `fix`, чьё исправление возвращает поведение к уже -записанному), отсутствие дельт подтверждает. Тип объявляет автор и может -ошибиться; отсутствие дельт — суждение исполнителя, и в одиночку оно -самообслуживающееся. Разошлись — стоп, а не выбор. - -**Размер признаком не стал намеренно.** «Мелкая задача — короткий путь» это -универсальная лазейка: скилл сам называет занижение метки и обход чекпоинта самым -дешёвым способом «ускориться». Однострочная правка, меняющая поведение, идёт -полным циклом; крупная чистка, не меняющая, — обслуживанием. - -**Планового стопа у сценария нет вовсе.** Чекпоинт объясняет человеку выбор, а -выбора здесь нет: что делать, сказано в записи, критерии приёмки дешёвые и -проверяются командой. Объяснение свелось бы к пересказу задачи её же автору. -Правило необратимого при этом действует полностью и срабатывает чаще, чем в двух -других сценариях: выкладка, токены, хуки и чужие данные — обычное содержимое -задач обслуживания. - -**Ревью идёт фиксированным планом, а разметчик не зовётся.** Обе его оси не -определены: размер он выводит из артефактов change, сложность — из формы решения, -а незнакомая форма ушла в разведку ещё на первом шаге. План — `autotests` -(запуск гейта) и `operations` (сверка), плюс `conventions` с техническим разбором, -когда дифф трогает код, а не только оснастку: `review-code` — единственный проход, -который вообще говорит «здесь ошибка в логике», и чистка без него проверена лишь -на то, что она собирается. `requirements` и `security` не смотрит никто, и это -строка границ покрытия, а не умолчание. - -**Найденная дельта — не поломка задачи, а обнаружение более широкого типа.** -Стоп поэтому устроен как три шага, а не как доклад об отказе: назвать тип, -которым задача оказалась (`fix` — расходится с заявленным, `feature` — снаружи -появляется то, чего не было), объяснить человеку простым языком, что нашлось, и -дать **два** решения — переформулировать запись и решать её процессом того типа -следующим прогоном либо прекратить работу. Третьего решения, «доделать как -обслуживание», нет: оно и есть молчаливое изменение поведения. Тип при этом -исполнитель **предлагает**, а меняет `av-dev-tasks:tasks` и только после ответа -— иначе исполнитель назначает себе другой процесс и другую глубину проверки сам. - -**Триггеры ADR у обслуживания работают стоп-признаком, а не поводом завести -запись.** Список источников ADR канон закрыл двумя — архивный `design.md` и -записка разведки, — и обслуживание не производит ни того ни другого. Значит -дорогой откат, намеренный отказ и пересмотр прежнего решения означают здесь одно: -сценарий выбран неверно, работа идёт разведкой, где решение проходит чекпоинт -вариантов и получает законный источник. Третьего источника заводить не -понадобилось. - -**Синк документации — главный шаг сценария, а не остаток.** Обслуживание не -меняет поведения, значит почти всё, что оно меняет, — документация: команды, шаги -гейта, зависимости, пути, имя ветки, место механизации правила. Ровно эти факты -`doc-code-drift` и сверяет с кодом. - -**Состав гейта сверяется отдельно от цвета, а чем именно — решает проект.** -Красный, ставший зелёным, виден; «проверок стало на две меньше, обе зелёные» не -виден ничем, а это единственное место конвейера, где инструмент проверяет сам -себя. Что считается составом, объявляет проект семантикой гейта в `CLAUDE.md`; -не объявил — строка доклада «сверен только цвет», а не догадка. - -**Своей capability тулчейн не получает, и своего документа канона тоже.** -Граница возможностей и сопровождения проходит по тому, кто наблюдает: гейт -наблюдаем мы, а не пользователь сервиса. Всё, что попало бы в `docs/toolchain.*`, -уже расписано по домам — `CLAUDE.md` (команды, семантика гейта, запреты, пути), -`architecture.*` (зависимости, окружение, выкладка), `conventions.*` -(механизированное), `ROADMAP.md` (работы). Проекту, которому этого мало, канон -уже даёт механизм и без новой строки в раскладке: список тем открытый, и свой -документ заводит свою тему. Цена такой темы названа — она попадает в план каждого -прогона и на большинстве задач молчит. - -### Что из этого следует - -212. **Короткий путь оправдан отсутствием входа, а не дешевизной.** «Тут можно - проще» — начало любой деградации; «этому шагу нечего обрабатывать» — - проверяемое утверждение, и проверяется оно тем же признаком, что и переход - между сценариями. -213. **Признак, объявляемый автором, и признак, выводимый исполнителем, держат - друг друга.** Первый один — ошибается в постановке; второй один — - самообслуживающийся. Разрешать расхождение в чью-то пользу нельзя: это - стоп. -214. **Сценарий без стопа для человека требует более жёсткого правила - необратимого, а не более мягкого.** Стопа, на котором «ой» заметили бы, там - нет. -215. **Инструмент, проверяющий сам себя, проверяется по составу, а не по - исходу.** Зелёный гейт после правки гейта не значит ничего. -216. **Место для нового документа ищется не по теме, а по бездомному факту.** - Тема «тулчейн» звучит убедительно, а фактов без дома за ней не оказалось — - значит документ был бы вторым домом четырёх чужих. -217. **Работа, переросшая свой тип, останавливается предложением, а не отказом.** - «Здесь нужно менять спеки» перекладывает классификацию на человека в момент, - когда весь материал для неё у исполнителя. Стоп обязан принести названный - тип, объяснение и закрытый список решений — иначе выбор делается вслепую или - не делается вовсе, и работа доезжает до коммита не тем процессом. - - -## 64. Три плагина слились в один: раскол платили, а не пользовались (2026-08-13) - -Плагинов было три — `av-dev-docs`, `av-dev-tasks`, `av-dev-code`, — и разрез -между ними шёл по признаку «ставится порознь» (решение 51). Признак был выбран -верно, но **посылка под ним не проверялась**: за всё время подмножество не -понадобилось ни разу, а платился раскол постоянно. - -Цена измерена, а не оценена: шестьдесят с лишним вызовов между скиллами при цикле -зависимостей `docs → code → docs`, язык проектных текстов четырьмя помеченными -копиями по 213 строк, словарь сопровождения двумя, правило границы семью, -дюжина веток «плагина нет» — и `copies.py`, заведённый ровно затем, чтобы это -не разъезжалось молча. - -**Довод «а вдруг понадобится» снят наблюдением владельца, а не спором.** Ждали -случая «документы и задачи без OpenSpec» — например, ansible-репозиторий. Он -уже покрыт: сценарий обслуживания в `resolve` OpenSpec не требует по -построению, а `docs.py` считает отсутствие `openspec/` неприменимостью, а не -отказом. То есть режим, ради которого держали раскол, работает и в слитом -плагине. - -**Слияние оказалось дешевле, чем выглядело, потому что граница была сделана -правильно.** Присутствие соседа узнавалось **следом в проекте** -(`.docs.json`, `.tasks.json`, `openspec/config.yaml`), а не перечнем -установленных плагинов. Значит мягкость поведения держалась на состоянии -проекта и пережила слияние без единой правки логики: сменилась упаковка, а не -механика. Дом правила переехал из `plugin-boundary.md` в `absence.md` и стал -говорить о том, чем он и был на деле, — о частях раскладки, которых может не -быть. - -**Версия стала одна и начинается с 1.** Две версии — канон 14 и формат задач 1 — -двигались порознь, потому что порознь ставились плагины; с одним плагином два -числа означали бы только вопрос, по какому журналу повышать. Прежние журналы -закрыты и не переписаны: адрес, верный на день записи, остаётся свидетельством. -Служебный файл один, `.av-dev.toml` в корне репозитория, и **формат выбран ради -комментариев** — файл живёт в чужом репозитории, и назначение числа должно -читаться из него самого, а не из документации плагина. Отсюда правило записи: -скрипт правит строку, а не переписывает файл. - -**Возможность расколоть обратно не потеряна.** Понадобится инфраструктурный -плагин — раскол будет переименованием пространства имён, а не переделкой: -граница по-прежнему держится на следе в проекте. Платить за эту возможность -копиями сегодня незачем. - -### Что из этого следует - -218. **Разрез, оправданный сценарием, обязан этот сценарий однажды увидеть.** - «Ставится порознь» — проверяемое утверждение, и проверяется оно не - рассуждением, а тем, поставил ли кто-нибудь половину. Пока не поставил, - разрез оплачивается копиями за случай, которого нет. -219. **Механика, привязанная к состоянию проекта, переживает перестановку - плагинов; привязанная к их составу — нет.** Это и есть практическая разница - между «узнаём следом» и «узнаём перечнем», и обнаруживается она только на - слиянии или расколе. -220. **Копия дословного текста — плата за неразрешимый путь, а не за важность - правила.** Путь разрешился — копия становится вторым домом без причины. - Остаётся она там, где текст обязан лежать **внутри промпта**: устав агента, - `SKILL.md` скилла и скелет, уезжающий в проект. Разрез проверяемый: файл, - который модель получает целиком, против файла, за которым она идёт - отдельным чтением. -221. **Формат служебного файла выбирается по тому, кто его читает.** Читает - человек в чужом репозитории через полгода — значит комментарии, значит - TOML, значит построчная правка вместо перезаписи. - - -## 65. Перечень осей получил дом; две оси жили без владельца (2026-08-13) - -Слияние плагинов не тронуло ни одной идеи процесса — типы записей, метки, три -сценария, категории документов, severity находок остались как были. Но оно -сделало дешёвым то, что раньше было дорого: правило, натянутое между задачами и -ревью, теперь имеет достижимый дом, а не помеченную копию через границу. - -**Ось — закрытый перечень значений, по которому что-то ветвится.** Признак -проверяемый, и он отсекает похожее: темы ревью и документы проекта — списки -**открытые**, их пополняет проект. Модель прохода — не ось, а цена прогона. -Осей по этому признаку девять, и дом теперь у каждой. - -**АЕАКЛ. Две оси были бездомными, и обе машинные.** Коды выхода объявлялись -«общим словарём» в **одиннадцати** местах, и каждое объявление перечисляло свой -набор соседей: «тот же, что у `tasks.py`», «тот же, что у `tasks.py`, `docs.py` и -`copies.py`». Ни одно не было домом — все списки по памяти, и машина их не -сверяла, потому что `copies.py` смотрит markdown, а перечни лежали в docstring'ах -скриптов. Режим прогона (с меткой · без метки) завёлся накануне слияния и разошёлся -по четырём файлам, ни в одном не будучи назван осью, — при том что в уставе -`review-basics` он задаёт **саму возможность запуска** прохода. - -**АЕАКМ. Слово «стадия» значило в одном файле две разные вещи.** «Стадия 1 — -Автотесты … Стадия 5 — Triage» — ступени внутри прогона кода, наружу не -выходящие; «метка правит обе стадии ревью» — дизайн и код, то есть членение, -которое видит вызывающий скилл. Разведено: ступени внутри, стадии снаружи. - -**АЕАКН. Дом перечня — не дом значений.** `shared/axes.md` держит только сами -оси, их адреса и **чего каждая не решает**. Механика остаётся у владельца: -второй пересказ разошёлся бы с первым, а вот перечень нужен целиком и в одном -месте — вопрос «а не задаёт ли это метку» задают из скилла, который метку не -ведёт. Целиком сюда переехали ровно две оси, у которых владельца нет. - -**Карта нашла ошибку в самой себе, и это её главный довод.** Первая редакция -объявила пустой клетку «категория документа × метка»: якобы проект вправе -завести тему, под которую ни одна метка не отряжает прохода. Проверка показала -обратное — `review-basics` приёмник проектных тем при **любой** метке. Пустой -оказалась соседняя клетка: на прогоне **без метки** план фиксирован сценарием, и -своих тем проекта в нём нет вовсе. Найти это можно было только сведя оси в одну -таблицу. - -### Что из этого следует - -222. **Словарь, объявленный «общим» в каждом потребителе, — это перечень по - памяти, а не дом.** Признак вырожденности проверяемый: каждое объявление - называет свой набор соседей, и ни одно не называет владельца. -223. **Копия в docstring'е скрипта машиной не сверяется, потому что `copies.py` - смотрит markdown.** Значит, прозе в коде дом нужнее, чем прозе в документах: - там расхождение ловит гейт, здесь — никто. -224. **Пустая клетка в таблице осей — находка, а не пробел оформления.** Она - называет случай, для которого процесс не сказал ничего, и до сведения осей - в таблицу такой случай неотличим от продуманного умолчания. -225. **Слово, занятое дважды в одном файле, дороже неточного слова.** Читатель, - пришедший за термином, получает два ответа и не знает, что их два. diff --git a/HISTORY.md b/HISTORY.md index ab53ac1..4c42a7d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -6,7 +6,7 @@ был написан. Остаётся то, чего в плагинах нет и быть не должно: **что отвергнуто и почему, и числа первого замера**. -Решения текущего круга разбора — [DECISIONS.md](DECISIONS.md). +Решения текущего круга разбора — [журнал решений](decisions/README.md). ## Что отвергнуто и почему diff --git a/README.md b/README.md index d082686..4cb9fb9 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Личный маркетплейс плагинов для разработки. Процесс имени не имеет — он и есть `av-dev`. -Что решено и почему — [DECISIONS.md](DECISIONS.md). Что осталось сделать — +Что решено и почему — [журнал решений](decisions/README.md). Что осталось сделать — [TODO.md](TODO.md) и [REMAINING.md](REMAINING.md). Как процесс дошёл до текущей формы — [HISTORY.md](HISTORY.md). @@ -12,8 +12,8 @@ Плагина два: **av-dev** — весь процесс, и **av-dev-git** — сообщения коммитов. Второй работает в любом репозитории, в том числе не приведённом к канону. До 13 августа 2026 процесс жил тремя плагинами (`av-dev-docs`, `av-dev-tasks`, `av-dev-code`); раскол делался под раздельную -установку, она не понадобилась ни разу, и плагины слились — тема 64 -[DECISIONS.md](DECISIONS.md). +установку, она не понадобилась ни разу, и плагины слились — +[тема 64](decisions/64-three-plugins-merged.md) журнала решений. Имя **скилла** несёт префикс прежнего плагина: `doc-`, `task-`, `code-`. Вызов выходит вида `/av-dev:<скилл>`. @@ -371,7 +371,7 @@ python3 scripts/frontmatter.py # 0 в порядке, 1 расхождени простого скаляра начинает вложенное отображение, так что «конвейер ревью: гейт, сверка…» — это не текст с двоеточием, а синтаксическая ошибка. Так было написано часть описаний плагинов, и читались они правильно — замер и разбор - в [DECISIONS.md](DECISIONS.md), решение III; + в [решении Р61](decisions/17-notes-tier-work-kind-roadmap.md) журнала; - **`name`, разошедшееся с именем каталога скилла или файла charter'а.** Вызов разрешается по каталогу, а сообщение о промахе говорит «нет такого скилла», а не «имя не то»; diff --git a/REMAINING.md b/REMAINING.md index 7f4895b..6f8e98d 100644 --- a/REMAINING.md +++ b/REMAINING.md @@ -2,9 +2,9 @@ Состояние пересобирается по ходу работы; счётчика тем и коммитов здесь нет намеренно — он протухает молча, а двигать его некому. Что и когда решено — -[DECISIONS.md](DECISIONS.md), записи датированы. +[журнал решений](decisions/README.md), записи датированы. -План работ — [TODO.md](TODO.md). Решения с причинами — [DECISIONS.md](DECISIONS.md). +План работ — [TODO.md](TODO.md). Решения с причинами — [decisions/](decisions/README.md). Здесь то, что **не** является работой из плана: незакрытые риски, честно принятые пределы и вопросы, у которых пока нет ответа. diff --git a/TODO.md b/TODO.md index fbfe1f9..1dcf720 100644 --- a/TODO.md +++ b/TODO.md @@ -3,14 +3,15 @@ **Здесь только работы и их порядок.** Чего здесь нет намеренно: - **риски, открытые вопросы и принятые пределы** — [REMAINING.md](REMAINING.md); -- **почему решено так** — [DECISIONS.md](DECISIONS.md), записи датированы; +- **почему решено так** — [журнал решений](decisions/README.md), записи + датированы; - **шаги повышения проекта с версии канона на версию** — журнал версий ([changelog.md](av-dev/skills/doc-canon/references/changelog.md)). Пересказ их сюда был бы вторым домом, и прежний план на этом уже разъезжался: он повторял записи версий 3, 4 и 5 построчно, и половина повторов протухла молча. Сделанное отсюда **удаляется, а не помечается галочкой**. След остаётся в -коммитах и в `DECISIONS.md`; список из двух сотен `[x]` перестают читать целиком, +коммитах и в журнале решений; список из двух сотен `[x]` перестают читать целиком, и живые пункты в нём теряются — прежний план умер именно так. ## Где мы сейчас @@ -18,7 +19,8 @@ Плагина два: `av-dev` — весь процесс девятью скиллами (`doc-*` — документы, `task-*` — учёт работ, `code-*` — работа по задачам), и `av-dev-git` — сообщения коммитов. Прежние три (`av-dev-docs`, `av-dev-tasks`, `av-dev-code`) -слились 13 августа 2026, тема 64 DECISIONS. Общее, что нужно нескольким скиллам, +слились 13 августа 2026, +[тема 64](decisions/64-three-plugins-merged.md) журнала решений. Общее, что нужно нескольким скиллам, живёт домом в `av-dev/shared/`. Раскладка — **версия 1**, одна на документы и на каталог задач, в @@ -27,7 +29,7 @@ 14, потом по записи 1 действующего. Бумажная часть закрыта аудитом четырёх плагинов и четырьмя пропусками правок -(DECISIONS, запись 59). Всё, что ниже, проверяется **только на живом коде**. +([тема 59](decisions/59-four-subagent-audit.md) журнала решений). Всё, что ниже, проверяется **только на живом коде**. ## 1. Живые проекты — вернуть в рабочее состояние diff --git a/decisions/01-openspec-status.md b/decisions/01-openspec-status.md new file mode 100644 index 0000000..d1bac2a --- /dev/null +++ b/decisions/01-openspec-status.md @@ -0,0 +1,86 @@ +# 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.** + +## Решено + +**Р1. OpenSpec — жёсткая предпосылка `av-dev-pipeline`.** Ветки деградации +удаляются, вместо них объявленная зависимость и проверка на старте. Зависимость +на уровне **плагина, а не процесса**: `av-dev-tasks`, `av-dev-git` и будущий +плагин документов от OpenSpec не зависят и работают на python/ansible-проектах. + +*Причина:* непроверенная ветка деградации хуже честной строки «требуется +OpenSpec» — она даёт ложную уверенность, что проект без спек поедет. + +**Р2. Нормативный дом поведения — `openspec/specs/`.** `architecture.md` +переопределяется как **обзор**: принципы, компоненты со ссылками на capability, +внешние форматы данных, раскладка, деплой, открытые вопросы. Поведения он не +описывает. + +*Причина:* `opsx:archive` вливает дельты именно в `openspec/specs/` — любой +другой нормативный дом обязан синхронизироваться руками и разойдётся. Форма +jellybit это уже подтвердила на 43 изменениях. + +**Р3. `openspec/config.yaml` → `context` держит только нужды генерации.** Язык, +правила именования capability, придирки валидатора RFC 2119 — и ссылки. Правило +ревью, пересказ конвенций и инварианты оттуда вычищаются: у них есть свои дома. + +*Причина:* блок «Ревью (процесс, не артефакт)» в обоих `config.yaml` дословно +повторяет шаги 4 и 7 `task-pipeline`. Это второй дом для правила, которым владеет +плагин, и он разойдётся на первой же правке. + +## Что из этого следует + +Из Р1: + +**С1.** Три места с веткой деградации переписываются на объявленную предпосылку +плюс проверку на старте (есть `openspec/`, разрешаются `opsx:*`) и внятный +отказ: `task-pipeline` предпосылки, `review-pipeline` предпосылки, `task-batch` +предпосылки. + +**С2.** Описание `av-dev-pipeline` в маркетплейсе получает строку «требует +OpenSpec». + +**С3. Факт для темы «объединять ли tasks и pipeline»:** объединение потянуло бы +зависимость от OpenSpec на управление задачами, которой там сейчас нет. + +Из Р2: + +**С4.** Правило «поведение — в спеку, устройство и границы — в архитектуру» +становится контрактом плагина документов и правилом шага «синк документации» в +`task-pipeline`. + +**С5.** healthlog чистится **не разом**: раздел вычищается той задачей, которая +его касается. Нужен способ не потерять остаток — иначе 950 строк «Хранилища» +останутся навсегда. + +**С6. Дыра, которую решение открывает:** «почему» после архивации. Сегодня +`CLAUDE.md` healthlog велит писать причину решения в `architecture.md` — а мы её +оттуда выселяем. Спеки нормативны и «почему» не держат; `design.md` живёт внутри +change и уезжает в архив. Либо ADR (как у jellybit), либо явное правило «почему +живёт в архивных change». **Первый вопрос следующей темы.** + +Из Р3: + +**С7.** `av-dev-pipeline` даёт образец `openspec/config.yaml` отдельным +reference — он владеет связью с OpenSpec. Заполняется при старте проекта и при +`adopt`. + +**С8.** У обоих проектов из `config.yaml` вычищается блок «Ревью (процесс, не +артефакт)», пересказ конвенций и инвариантов. diff --git a/decisions/02-project-doc-canon.md b/decisions/02-project-doc-canon.md new file mode 100644 index 0000000..1fe9b7a --- /dev/null +++ b/decisions/02-project-doc-canon.md @@ -0,0 +1,106 @@ +# 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 шагов) + и `/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. + +## Решено + +**Р4. «Почему» — ADR как промоут поверх архива.** Обоснование по-прежнему пишет +`design.md`; ADR — короткая запись, цитирующая решение и ссылающаяся на архивный +`design.md`. Заводит её **шаг «синк документации» пайплайна по названному +триггеру** (дорогой откат / намеренный отказ от очевидного / пересмотр прежнего +решения), а не человек по вдохновению. + +*Причина:* ручной ритуал эмпирически не выжил — 6 записей на 52 изменения. +Автоматический (`opsx:propose` пишет `design.md` всегда) работает и производит на +порядок больше. Чинить надо не дом, а индекс и критерий промоута. + +**Р5. `docs/plan.md` растворяется в `/PLAN.md`.** Файл удаляется, 11 +шагов становятся целями в «порядке», ссылки в `CLAUDE.md` и паспорте +переводятся. + +**Р6. Пути жёсткие, оба проекта приводятся к одному виду.** Плагин знает +раскладку поимённо; указателя вида `.docs.json` нет. + +*Причина (словами владельца):* «так проще ориентироваться во множестве проектов, +а не видеть слегка похожую, но разную структуру в каждом. Все проекты малого и +среднего размера, проще подогнать их под одну структуру. Кроме того, у OpenSpec +тоже структура строгая». Цена принята сознательно: плагин перестаёт быть +переносимым на чужой репозиторий, а `adopt` из «поправь указатели» превращается в +«перенеси файлы». + +**Р7. Конвенции и разведка — каталогами с README-индексом.** `docs/conventions/` +и `docs/research/`: путь жёсткий, нарезка внутри свободна. Схема хранилища — +**отдельный** `docs/database.md` (своя каденция: меняется миграцией, а не +архитектурным решением; гейт healthlog уже сверяет миграции с документацией). +Конвенции идентификаторов и именования — не схема, они в `conventions/`. + +**Р8. Слота для черновиков нет.** Идея → задача `[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//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](06-docs-upkeep.md) (поддержание):** точная +формулировка триггера промоута в ADR; нужен ли механический `check` раскладки +документов, раз пути жёсткие; как не потерять остаток при постепенной чистке +`architecture.md`. diff --git a/decisions/03-review-brief-is-canon.md b/decisions/03-review-brief-is-canon.md new file mode 100644 index 0000000..d9fffc5 --- /dev/null +++ b/decisions/03-review-brief-is-canon.md @@ -0,0 +1,115 @@ +# 3. Брифа ревью нет — бриф это и есть канон (2026-08-03) + +## Что было + +Контракт брифа — 413 строк, 13 разделов, отдельный файл `docs/review-brief.md`, +который каждый проход читает как истину. Заполнение на обоих проектах дало 841 и +734 строки, и `REMAINING.md` уже отметил, что часть разделов вырождается в +пересказ. + +Разбор по разделам после решения [Р6](02-project-doc-canon.md) (жёсткие пути) +показал: **посредник между агентом и файлом не нужен, когда путь известен**. +Восемь из тринадцати разделов дублируют канон или снимаются жёсткими путями. + +## Решено + +**Р9. Отдельного файла-брифа нет.** Проектную конкретику проходам дают документы +канона напрямую, по жёстким путям. Формулировка владельца: «артефакты в `docs` и +должны стать частями брифа, а для ревью достаточно дать ссылки на эти +артефакты». + +*Причина:* один факт — один дом. Бриф был вторым домом для паспорта, инвариантов +и карты, а разошедшийся бриф хуже отсутствующего: он выглядит актуальным. + +**Р10. Заводится `docs/security.md`.** Периметр **первой строкой** (целевой и +сегодняшний, если контур не развёрнут), недоверенный вход и его каналы, из чего +строятся пути и ключи, что разграничивает доступ, что чувствительнее чего, что +вне модели. Материал уже есть, но рассыпан: у healthlog — раздел +«Аутентификация» в `architecture.md` и строка про секреты в `CLAUDE.md`, у +jellybit — секреты в `conventions/config.md`. **Периметра нет ни у одного**, а +без него враждебный проход не выбирает между «открыт наружу» и «контур +доверенный». + +**Р11. `review-journal.md` → `docs/review.md`:** журнал дефектов плюс настройка +конвейера под проект. Туда садится остаток брифа, который фактом о проекте не +является — типовые узлы, типовые ложноположительные, вопросы к проходам, +недоступно проверке. + +*Причина:* все четыре — производные калибровки, и журнал им источник. `## +Вопросы к проходам` сам называет журнал главным источником; `### Перестали +проверять сознательно` требует ссылки на его запись. + +**Р12. Журнал расширяется до всех воспроизведённых дефектов** с пометкой +«проскочил / пойман ревью». Проверочный набор для калибровки — выборка по +пометке. + +*Причина:* пойманные дефекты с оракулом (768 МиБ пика, 5.019 с удержания +блокировки) сегодня не сохраняются нигде, кроме отчётов триажа в архиве change, а +они и есть лучшая опора для прохода — проектные, воспроизводимые, однажды +оказавшиеся правдой. + +**Р13. Семантика гейта — в `CLAUDE.md`, расширением раздела «Команды».** Чем +краснеет безусловно и почему, где логи, что означает исход, чего в гейте +намеренно нет, **кто и когда обязан гонять дорогое вне гейта**, что запускать +запрещено (с путями). Гейт краснеет не только в ревью — это факт о проекте. + +**Р14. 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//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](05-project-start-lifecycle.md), +требование [Т1](README.md)). + +**С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`. diff --git a/decisions/04-plugin-boundaries.md b/decisions/04-plugin-boundaries.md new file mode 100644 index 0000000..959123e --- /dev/null +++ b/decisions/04-plugin-boundaries.md @@ -0,0 +1,76 @@ +# 4. Границы плагинов (2026-08-03) + +## Что было + +Связь `tasks` ↔ `pipeline` уже сделана **ролями, а не именами**: скиллы говорят +«пайплайн проекта», «владелец спринта», «тот, кто ведёт задачи». Жёсткая ссылка +по имени ровно одна — `task-pipeline:112` на канонический текст правила про +остаток внутри `session`, и рядом обработан случай «плагин не подключён». + +Слоты `CLAUDE.md` при этом дублировались уже внутри одного плагина: шесть у +`tasks`, семь у `session`, три пары — одно и то же. Темы 2–3 растворили ещё +часть: «куда переезжает суть» отвечает канон, «оракулы» — семантика гейта +(решение [Р13](03-review-brief-is-canon.md)), «где живёт разбор процесса» — +`docs/review.md` (решение [Р11](03-review-brief-is-canon.md)). Из тринадцати +остаётся около четырёх. + +## Решено + +**Р15. Три плагина: `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`. + +**Р16. Граница «пайплайн не закрывает задачу» снимается.** Закрывает задачу и +двигает строки между `SPRINT.md` / `BACKLOG.md` / `REJECTED.md` **агент- +оркестратор** — `task-pipeline` и `task-batch`, а не сабагенты внутри них. Зовёт +он `tasks.py` через слот «Команда учёта задач» в `CLAUDE.md`. + +Слот, следовательно, **не исчезает, а становится мостом между плагинами** — и +заодно тем, чего в чужом проекте нет, отчего пайплайн там работает как прежде: +докладывает исход, записей учёта не трогает. + +**Р17. `av-dev-backlog` помечается устаревшим и остаётся** до перевода jellybit. +*(заменено на [тему 30](30-av-dev-backlog-removed.md): плагин удалён раньше +этого срока — условие пережило свою причину.)* Описание переписывается так, +чтобы не ловить триггер «добавь задачу в беклог» — иначе агент выбирает между +ним и `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//review/` (независимый артефакт, `task-batch` уже сверяет +полноту ревью по нему, а не по прозе исполнителя), **`SPRINT.md` под git** с +видимой историей и **`reopen --reason`** — закрытие не окончательно, +приёмка человеком на сессии его отменяет. Раздел обязан назвать их поимённо, +иначе обещает защиту, которой нет. + +**С20. Конфликт владения `docs/tasks/` снят** — канон и задачи теперь в одном +плагине. + +**С21. Скилл `adopt` из `av-dev-tasks` поглощается** скиллом адаптации проекта +уровня канона (требование [Т1](README.md)). Разбирается в [теме +5](05-project-start-lifecycle.md). + +**С22. Состав `av-dev-pm`:** `tasks`, `session` (есть), `docs` — ведение канона, +`project` — старт, adopt, check, upgrade ([тема +5](05-project-start-lifecycle.md)). diff --git a/decisions/05-project-start-lifecycle.md b/decisions/05-project-start-lifecycle.md new file mode 100644 index 0000000..2952482 --- /dev/null +++ b/decisions/05-project-start-lifecycle.md @@ -0,0 +1,89 @@ +# 5. Старт проекта и жизненный цикл под каноном (2026-08-03) + +## Что было + +Требование [Т1](README.md): прийти в любой старый проект и перевести на текущие +рельсы; канон сам меняется, значит уже приведённые проекты тоже повышаются. + +Существующий `adopt` (уровень задач) даёт готовую форму: **`scan` — только +чтение, карта → суждение человека → `apply` — запись одним проходом**, с отказом +до первой записи при неверной карте и с обязательным разделом «не разложилось» +поимённо. Форма переносится на уровень канона как есть. + +Четыре операции различаются не поровну: `adopt`, `check` и `upgrade` — одна +машина сравнения с разными исходами, а `init` — принципиально другой режим, +разговор, а не сверка. + +## Решено + +**Р18. Два скилла: `av-dev-pm:init` и `av-dev-pm:canon`.** `init` — интервью по +входному брифу для нового проекта. `canon` — привести к канону: `check`, +`adopt`, `upgrade` одной машиной. + +**Р19. Скелет канона заводится целиком, незаполненное называется пустым.** Все +файлы канона есть с первого дня, но незаполненный держит **одну честную +информативную строку**: «наблюдений на живых данных нет — внешний источник один, +формат документирован», «прецедентов не накоплено», «внешних зависимостей нет, +смотри на диск и на СУБД». + +*Причина:* это тот же принцип, что был в контракте брифа, поднятый на уровень +файлов. Проход читает такую строку **как факт**, а не как пробел, и не тратит +обязательный вопрос впустую. Отсутствие файла он прочитать не может никак. + +**Защита от вырождения в заглушки** берётся у `tasks.py`: пока на месте стоит +плейсхолдер шаблона, `check` о нём напоминает. Строка «TBD» — это не «пустое +названо пустым», и `check` обязан их различать. + +**Р20. Скрипт `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.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](06-docs-upkeep.md):** звать ли `docs.py check` из +гейта проекта. У healthlog `task gate` уже сверяет миграции с документацией, так +что место есть; но гейт принадлежит проекту, и плагин может только рекомендовать +строкой в отчёте. diff --git a/decisions/06-docs-upkeep.md b/decisions/06-docs-upkeep.md new file mode 100644 index 0000000..3dc14ff --- /dev/null +++ b/decisions/06-docs-upkeep.md @@ -0,0 +1,68 @@ +# 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`. + +## Решено + +**Р21. Принуждённое отрицание в докладе шага синка.** Шаг обязан назвать +**каждый** документ канона: обновлён — чем, либо «не требуется, потому что…». +Нетронутые группируются одной строкой с общей причиной. + +*Причина:* это тот же приём, что «границы покрытия» в отчёте ревью и «пустой +пункт называется пустым» в каноне — и единственный, о котором в этом репозитории +есть данные, что он работает. Отличить «не написал» от «написал, что не +требуется» можно только тогда, когда отрицание обязательно. + +**Триггер ADR, окончательная формулировка.** Запись заводится, когда верно одно +из трёх: **дорогой откат** (переделка стоит дороже переписывания одного файла); +**намеренный отказ** от очевидного подхода; **пересмотр прежнего решения** — тогда +у старой записи обязателен статус «заменено на». Не заводится для рутины и для +того, что видно из кода и `git log`. Источник — архивный `design.md`: ADR его +цитирует и на него ссылается, а не пересказывает. + +**Р22. `docs.py check` обязателен в гейте проекта.** Скилл `canon` при адаптации +добавляет шаг и печатает это в отчёте. + +*Причина:* гейт — единственный общий станок, который нельзя пропустить. Проверка, +которую зовёт агент, может быть не позвана; прецедент в самом healthlog уже есть. + +Сверка «миграция изменена — `database.md` нет» **обобщается**: путь миграций +проекта записывается в `docs/.pm.json` рядом с версией канона, и `docs.py` делает +эту проверку сам, а не каждый проект заново. + +**Р23. Остаток чистки помечается маркером и считается числом.** Неразобранный +раздел получает ``, +`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`. diff --git a/decisions/07-skills-layout-scripts.md b/decisions/07-skills-layout-scripts.md new file mode 100644 index 0000000..e2edcc5 --- /dev/null +++ b/decisions/07-skills-layout-scripts.md @@ -0,0 +1,83 @@ +# 7. Раскладка скиллов и доставка скриптов (2026-08-03) + +## Решено + +**Р24. Пять скиллов в `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`. + +**Р25. Скрипты не копируются — живут вместе со скиллами.** Три вызывающих, три +способа дотянуться: + +| Кто зовёт | Как | +| --- | --- | +| скиллы `tasks`, `canon`, `docs` | `$CLAUDE_PLUGIN_ROOT` — свой плагин, работает всегда | +| `task-pipeline`, `task-batch` | **вызов скилла** `av-dev-pm:tasks`, а не путь | +| гейт проекта | путь переменной с умолчанием на канонический путь маркетплейса; пишет `canon adopt`, внятный красный отказ, если не найден | + +**Слот «Команда учёта задач» всё равно исчезает** — но снимает его не копия, а +**вызов скилла через пространство имён**. Тот же приём, которым шаг синка зовёт +`av-dev-pm:docs` (решение Р24): чужой плагин зовёт скилл, скилл разрешает свой +`$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` в нём + отсутствовали вовсе. Довод «вендоринг создаёт вторую копию» был слабее, чем + подан. +- **Обновление маркетплейса — одна точка на все проекты.** При вендоринге каждый + проект повышается отдельно, и проекты расходятся друг с другом — ровно та +разнородность, против которой принято решение [Р6](02-project-doc-canon.md). + +**Р26. Имени у процесса нет — процесс это `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](08-rollout-order.md)), иначе проверять будет нечего. diff --git a/decisions/08-rollout-order.md b/decisions/08-rollout-order.md new file mode 100644 index 0000000..ebdefcb --- /dev/null +++ b/decisions/08-rollout-order.md @@ -0,0 +1,70 @@ +# 8. Порядок выката (2026-08-03) + +## Объём + +Ссылок на бриф — **168 строк в 19 файлах** `av-dev-pipeline`, из них ~48 уходят +вместе с удаляемыми `project-brief/SKILL.md`, `references/project-brief.md` и +`references/brief-template.md`. Остальное переписывается на пути канона. + +## Решено + +**Р27. Инструмент строится целиком, потом проверяется.** Не пилот руками. + +*Риск принят сознательно:* если замер покажет деградацию severity, чинить +придётся канон, зашитый к тому моменту в три скилла, скрипт и мигрированные файлы +healthlog. + +*Удешевление, которое обязано быть заложено сразу:* **определение канона живёт в +единственном reference-файле**, который читают `init`, `canon` и `docs`, а не +повторяется в каждом. Правка канона — одно место плюс запись в журнал версий. + +*Страховка порядка:* **замер ставится перед переездом jellybit**, а не после +всего, — он всё ещё блокирует то, что дороже всего откатывать. + +**Р28. Работа ведётся в `docs/tasks/` самого `dev-skills`.** Скилл `tasks` не +требует ни OpenSpec, ни языка — задачи для него просто каталог markdown. Цели — +крупные куски, задачи — следствия. Заодно первая боевая обкатка собственного +инструмента. + +**Р29. `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 жёстко. diff --git a/decisions/09-script-linters.md b/decisions/09-script-linters.md new file mode 100644 index 0000000..a023800 --- /dev/null +++ b/decisions/09-script-linters.md @@ -0,0 +1,67 @@ +# 9. Линтеры скриптов (2026-08-03) + +## Что было + +Три скрипта на python, 3600 строк, ни одной проверки. `tasks.py` — 2450 строк, +которые ходят по файловой системе, переименовывают и удаляют файлы задач. +Требование к самим скриптам прежнее и не обсуждается: **голый `python3` 3.12, +ноль внешних зависимостей** — они лежат рядом со скиллами и запускаются в +чужом проекте, где ничего ставить нельзя. + +## Решено + +**Р30. `pyproject.toml` в корне `dev-skills`, зависимости через `uv`.** Файл +живёт только здесь и не уезжает никуда: он держит **линтеры**, а не зависимости +скриптов. Скрипты остаются запускаемыми любым `python3` — это проверено прогоном +всех операций через `/usr/bin/python3`, а не через `.venv`. + +**Р31. Ноль зависимостей охраняется двумя способами, и главный — второй.** +`banned-api` у ruff ловит частые соблазны по имени (`requests`, `yaml`, +`pydantic`, `click`, `rich`) — список заведомо неполный. Настоящий страж — +pyrefly: в окружении нет ничего, кроме линтеров, поэтому **любой** сторонний +импорт у него не разрешается. Первый способ даёт понятное сообщение, второй — +полноту. + +**Р32. Версии линтеров прибиты точно** (`ruff==0.16.1`, `pyrefly==1.2.0`) плюс +`uv.lock` в git. Обновление линтера меняет набор находок, а находки правятся +руками в скриптах, которые уезжают в чужие проекты. Обновление обязано быть +отдельной осознанной правкой, а не побочным эффектом `uv sync`. + +**Р33. `RUF001`–`RUF003` выключены.** Весь текст скриптов русский: сообщения, +докстроки, комментарии. «Похожая на латиницу кириллица» здесь норма, а не +опечатка, и три этих правила давали **311 срабатываний из 338** — шум, в котором +тонут остальные 27. + +**Р34. `av-dev-backlog` исключён из проверки.** *(исчерпано [темой +30](30-av-dev-backlog-removed.md): плагин удалён, исключение снято из +`pyproject.toml` и `copies.py`.)* Плагин помечен устаревшим и живёт до перевода +последнего проекта, после чего удаляется целиком. Шесть его находок +косметические (`os.replace`, `l` как имя), а правка замороженного кода без +тестов — риск без выгоды. Исключение уходит вместе с плагином. + +**Р35. Голый `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. Заводить хук ради двух скриптов, которые +правятся раз в месяц, — плата ритуалом без выгоды. diff --git a/decisions/10-two-pass-plugin-review.md b/decisions/10-two-pass-plugin-review.md new file mode 100644 index 0000000..10a5b59 --- /dev/null +++ b/decisions/10-two-pass-plugin-review.md @@ -0,0 +1,51 @@ +# 10. Ревью готовых плагинов двумя проходами (2026-08-03) + +## Что было + +Два независимых сабагента `fable` — по одному на `av-dev-pm` и `av-dev-pipeline`. +**20 находок, из них две найдены обоими независимо.** Прошлые три круга ревью +шли по одному проходу на всё; два прохода с разными предметами дали и больший +урожай, и перекрёстное подтверждение самого дорогого дефекта. + +## Что оказалось сломано по существу + +**Р36. Перестановка закрытия за коммит (решение из [темы +8](08-rollout-order.md)) сломала `reopen` и батч — и это нашли оба прохода.** +`close --implemented` печатает «дорога назад: файл восстанавливается из git», а +`reopen` искал **коммит удаления**, которого в новом порядке ещё нет: шаг 11 +идёт последним, и учёт остаётся незакоммиченным. Проверено прогоном: `reopen` +отказывал кодом 2 на свежезакрытой задаче — то есть в самом вероятном своём +применении. Тем же грязным деревом ломался `task-batch`: `git rebase` и `git +worktree remove` отказывают, и **каждая успешно закрывшая задачу ветка** уезжала +бы в провалившиеся. + +Починено с обеих сторон: `reopen` берёт текст из `HEAD`, если коммита удаления +нет, а шаг 11 обязан **коммитить учёт вторым коммитом** — иначе закрытие не +доезжает до основной ветки и опора «`SPRINT.md` под git» остаётся словами. + +**Р37. Канонический пример `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. Два прохода по разным предметам дороже одного, но не вдвое.** Перекрытие +оказалось ровно в одной находке из двадцати — той самой, что подтвердилась +дважды. Практика остаётся: ревью на плагин, а не одно на репозиторий. diff --git a/decisions/11-plugin-dependencies.md b/decisions/11-plugin-dependencies.md new file mode 100644 index 0000000..95f81e9 --- /dev/null +++ b/decisions/11-plugin-dependencies.md @@ -0,0 +1,52 @@ +# 11. Зависимости между плагинами (2026-08-03) + +## Целевая картина, которую проверяли + +`av-dev-git` ни от чего не зависит. `av-dev-pipeline` сам по себе: задача +приходит **и обычным текстом**, и из `tasks`. `av-dev-pm` оперирует абстрактным +«сделать задачу» и не знает, чем она выполняется. + +## Что показала проверка + +**Р38. Первые две цели выполняются, третья в исходной формулировке недостижима — +и формулировку надо поправить, а не картину.** `av-dev-pm` **владеет +конфигурационным файлом конвейера**: `docs/review.md` держит «Вопросы к +проходам» и «Триггеры профиля», то есть перечисляет проходы поимённо, а скелет +`review.md` несёт форму журнала дефектов. Кто-то этим словарём владеть обязан — +канон и есть схема данных, которую конвейер читает. Честная формулировка цели: +**`av-dev-pm` не зовёт пайплайн и не требует его наличия**. Она выполняется. + +**Р39. Настоящая протечка была одна — необъявленная деградация опор приёмки.** +«Стимулы» в `session` и приёмка в `sprint.md` держались на «сохранённом отчёте +триажа» по конкретному OpenSpec-пути. В проекте без конвейера ревью защита от +занижения урожая исчезала **молча**: сверять не с чем, а текст об этом не +говорил. Теперь опора названа абстрактно («независимый отчёт ревью»), путь +`av-dev-pipeline` дан как частный случай, а отсутствие конвейера обязано +попадать строкой в доклад спринта. + +**Р40. Ветка деградации шага 9 была неисполнима — ровно в том случае, ради +которого написана.** «Плагина нет — открой +`av-dev-pm/skills/canon/references/canon.md`»: путь в дерево маркетплейса, из +проекта без установленного плагина не разрешается ниоткуда. Кросс-плагинные пути +в дерево маркетплейса теперь не используются вообще: пайплайн ходит в **свой** +`references/project-facts.md`, а ссылки в чужой плагин даются через `Skill +<плагин>:<скилл>`. + +## Что из этого следует + +**С47. Знаниевый цикл есть и он законен, но каждый его контракт обязан иметь +единственный дом.** Пайплайн описывает раскладку `pm`, `pm` описывает артефакты +пайплайна — пять симметричных контрактов, из них два уже разошлись: форма +журнала дефектов (шесть полей против пяти, «Причина» потеряна) и список +читателей `docs/research/` (`specs` выпал). Дома назначены: форма журнала — у +конвейера, список читателей — у канона; в обеих копиях стоит явное указание на +дом. + +**С48. Пайплайн больше не называет внутренние имена файлов `pm`.** +`items/.md` и `SPRINT.md` в его тексте были вторым домом для раскладки, +которую проект вправе переименовать через `docs/.pm.json`. + +**С49. Описания плагинов в манифестах врали умолчанием.** Ни `marketplace.json`, +ни `plugin.json` не говорили, что `av-dev-pm` для конвейера **опционален**, а +задача принимается текстом. Теперь говорят — это первое, что читает человек, +выбирая, что подключать. diff --git a/decisions/12-mechanical-copy-check.md b/decisions/12-mechanical-copy-check.md new file mode 100644 index 0000000..a99f11f --- /dev/null +++ b/decisions/12-mechanical-copy-check.md @@ -0,0 +1,53 @@ +# 12. Механическая проверка копий (2026-08-03) + +## Что было + +Разделение плагинов оставлено ([тема 11](11-plugin-dependencies.md)), но цена +его названа: пять симметричных контрактов в двух домах, два уже разошлись — +форма журнала дефектов потеряла в копии поле «Причина», список читателей +`docs/research/` потерял `specs`. Оба раза копия выглядела актуальной, и оба +раза расхождение прошло мимо трёх ревью подряд. + +## Решено + +**Р41. Копия допустима, но обязана быть дословной и помеченной.** Разметка — +HTML-комментарии, невидимые в отрендеренном markdown: `` … +`` и `` … ``. `scripts/copies.py` требует побайтового совпадения текста между маркерами. + +*Почему комментарии, а не манифест копий отдельным файлом:* маркер уезжает в +репозиторий проекта вместе со скелетом, и там он **полезен** — говорит читателю, +что у текста есть дом и правится он там. Манифест остался бы в маркетплейсе и +проекту ничего не сказал. + +**Р42. Идентификатор строгий — буквы, цифры, дефис — и повторяется в закрывающем +маркере.** Иначе документация о самом механизме объявляет дом и роняет проверку: +это случилось на первом же прогоне, `README.md` объявил дом примером. Теперь +пример пишется ``, угловые скобки под шаблон не подходят. + +**Р43. Ограда блока кода в сверку не входит.** В доме текст обрамлён своей ```, +а в скелете тот же текст лежит внутри чужой, объемлющей ограды. Сверяется +содержимое, а не разметка вокруг него. + +**Р44. Коды выхода — общий словарь** (0 сошлось, 1 расхождение, 2 разметка, 3 не +тот каталог, 4 сбой). Третий скрипт репозитория, и третий по тем же кодам. + +## Что из этого следует + +**С50. Помечены два контракта:** форма записи журнала дефектов (дом — конвейер +ревью, копия — скелет канона; это кросс-плагинная пара) и «когда заводить ADR» +(дом — канон, копия — его же скелет). Второй пришлось сперва **сделать** +дословным: копия говорила «обязателен статус», дом — «обязателен статус +„заменено на"», и это ровно тот класс, который и ищется. + +**С51. Чего проверка не ловит — копию, которую забыли пометить.** Помечать +остаётся решением человека, и это названо в `README.md` вслух: иначе зелёный +прогон читался бы как «копий больше нет». + +**С52. Дом без копий — расхождение, а не замечание.** Маркер, обещающий +дисциплину, за которой не за чем следить, — такая же ложная запись, как +разошедшаяся копия. + +**С53. Запись в журнал версий канона проверка не заменяет.** Она видит, что +копия отстала, но не видит, что проект уже унёс старую версию к себе. Это +остаётся на человеке и сказано в обоих домах. diff --git a/decisions/13-plan-sections-renamed.md b/decisions/13-plan-sections-renamed.md new file mode 100644 index 0000000..6a34eba --- /dev/null +++ b/decisions/13-plan-sections-renamed.md @@ -0,0 +1,31 @@ +# 13. Секции `PLAN.md` переименованы (2026-08-03) + +## Что было + +Секции назывались **«линия»** и **«кусты»** — метафора, требующая расшифровки +при каждом употреблении. В текстах она и расшифровывалась: «звено упорядоченной +линии продукта», «тематический куст — цель, в последовательность не встающая». +Если название приходится объяснять рядом с каждым употреблением, объясняет не +название. + +## Решено + +**Р45. «порядок» и «темы».** Заголовок называет ровно то свойство, которым +секции различаются: в первой очередь значима и обоснована прозой, во второй +порядка нет вовсе. Расшифровывать нечего — правило написано в самом имени. + +**Р46. Записи в журнал версий канона не требуется — канон этих имён не знает.** +`canon.md` называет файл `docs/tasks/PLAN.md` и ничего не говорит о его секциях: +их дом — заголовки `##` индекса, а умолчание живёт в `tasks.py`. Версия канона +поэтому не меняется, и проект вправе называть секции по-своему. Причина названа +вслух, потому что соблазн повысить версию «на всякий случай» здесь сильный, а +повышение обязало бы каждый проект что-то делать — при том что делать нечего. + +## Что из этого следует + +**С54. Умолчание одно и живёт в `DEFAULT_PLAN_SECTIONS`.** Имена секций +по-прежнему настраиваются `--plan-sections`, а домом остаются заголовки `##` +индекса — переименование не трогает механику, только умолчание и тексты. + +**С55. Метафора — плохое имя для секции индекса.** Секция читается человеком без +контекста, часто из вывода `list`, и второго шанса объяснить себя у неё нет. diff --git a/decisions/14-run-mode-defaults-flipped.md b/decisions/14-run-mode-defaults-flipped.md new file mode 100644 index 0000000..1915f96 --- /dev/null +++ b/decisions/14-run-mode-defaults-flipped.md @@ -0,0 +1,54 @@ +# 14. Умолчания режимов прогона перевёрнуты (2026-08-03) + +## Что было + +Оба скилла держали одно и то же умолчание — «по очереди», — хотя цена очереди у +них разная. `review-pipeline` гнал проходы последовательно и требовал для +параллельности **двух** условий (явная просьба **и** поимённо названный набор). +`task-batch`, наоборот, планировал волны параллельных задач с потолком 2–3 и +считал параллельность нормой прогона. + +Перепутаны оказались уровни. Проход ревью — чтение и рассуждение: он ничего не +поднимает, ни за что не дерётся и по построению не видит выводов соседа. Задача +батча — полный цикл пайплайна: гейт, поднятие сервиса вживую, вложенное ревью, +общие порты и рабочие каталоги. Дешёвое стояло в очереди, дорогое гонялось разом. + +## Решено + +**Р47. В ревью умолчание — параллельно.** Стадии по-прежнему идут по порядку, +параллельность касается только проходов внутри стадии. Последовательно гоняем по +трём особым причинам, и каждая называется в отчёте: сказал оператор; проходы +меряют; машина занята — причём занятость видит вызывающий, а не конвейер. +Просьба «гони последовательно» **набора не требует**: очередь ничего не портит, +она только дольше, и домысливать тут нечего — в отличие от прежнего правила, где +неназванный набор блокировал отступление. + +**Р48. Меряющая пара — правило стадии, а не решение прогона.** `adversary` и +`ops` идут по очереди всегда: оба доказывают находки числами и оба меряют одно +железо, а испорченный оракул хуже отсутствующего. Общее «гони параллельно» этого +не отменяет; отменяет только прямое слово оператора **про эту пару**, и тогда в +границы покрытия идёт строка про замеры под соседней нагрузкой. + +**Р49. В батче умолчание — по одной задаче, параллельность — по графу +зависимостей.** План собирается как граф (рёбра — жёсткие зависимости и +сериализуемые пересечения) и в умолчании линеаризуется в один порядок. Просьба +«гони параллельно» разрешает использовать **ширину графа**, а не гнать всё +разом: потолок 2–3, замеряющая задача — волной по одной. Прежние правила волн +сохранены целиком, они просто перестали быть умолчанием. + +## Что из этого следует + +**С56. Режим батча задаёт режим ревью внутри задачи, и его называет charter.** +Батч идёт по одной — машина свободна, сабагент гонит проходы параллельно; батч +идёт волнами — сабагенту предписан последовательный режим с этой самой причиной. +Сабагент своего соседа не видит, поэтому решать это ему нельзя. + +**С57. Ранний выход из ревью переехал на границу стадии.** Стадии идут по +порядку в любом режиме, так что остановиться между ними можно всегда; остановка +**внутри** стадии осталась побочной выгодой последовательного режима — но не +поводом его выбирать. + +**С58. Цена параллельного батча проверяется до первой волны.** Тесты, делящие +фиксированный порт или файл БД, и проект, умеющий поднимать один экземпляр, — +основание гнать по одной даже после просьбы, сказанное строкой: просьба была про +параллельность, а не про сломанные тесты. diff --git a/decisions/15-review-pass-order-graph.md b/decisions/15-review-pass-order-graph.md new file mode 100644 index 0000000..f4a182e --- /dev/null +++ b/decisions/15-review-pass-order-graph.md @@ -0,0 +1,90 @@ +# 15. Порядок проходов ревью — граф зависимостей (2026-08-03) + +## Что было + +Решение 14 перевернуло умолчание, но оставило порядок в прежней форме: «стадии +идут по порядку номеров, параллельность — только внутри стадии». Номер стадии при +этом ничего не означает: между стадиями 1–4 ни один проход не читает вывод +другого, так что очередь между ними была платой ни за что. А правило про замеры +держалось на **двух именах** — `adversary` и `ops`, — и рассыпалось бы в тот +день, когда мерить начнёт третий проход или проект добавит свой. + +## Решено + +**Р50. Порядок задаёт граф; стадии остаются единицей состава.** Профиль +по-прежнему набирается стадиями, но запускается всё, у чего закрыты входящие +рёбра. Рёбер три вида, и смешивать их нельзя: **зависимость** (гейт → все +проходы с мнением, все проходы → триаж), **конфликт за ресурс** (ненаправленный, +между теми, кто держит машину), **барьер стоимости** (только `deep`). + +**Р51. Сериализует ресурс, а не имена.** Пометка «держит машину» — таблицей в +скилле: `gate`, `adversary`, `ops`, `triage`; читают и рассуждают — `specs`, +`code`, `reimpl`, `architecture`, `rubric`. Проект вправе пометить свой проход в +`docs/review.md`; снимать пометку с перечисленных нельзя. Правило теперь +самораспространяется: начнёт проход мерить — попадёт в цепочку по факту, а не по +поправке. + +**Р52. Ранний выход заменён барьером стоимости.** Он стоит там, где ранний выход +зарабатывал: перед `reimpl` (пишет реализацию целиком) и `architecture`. В +`quick`/`standard` барьера нет — стадий 3–4 там не бывает; в `design` нет по +другой причине — предметом там и является форма, защищать нечего. + +**Р53. Ребро — это порядок, никогда не данные.** В обычном графе задач ребро +тянет за собой вывод предшественника; здесь это запрещено: проход, увидевший +чужие находки, соглашается с ними, и разведённость — вся ценность конвейера — +обнуляется. Сказано в самом правиле, потому что графовый словарь провоцирует +ровно эту ошибку. Исключение одно и оно же сток: триаж. + +**Р54. Диаграммы в скиллах — `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. diff --git a/decisions/16-directory-instead-of-file.md b/decisions/16-directory-instead-of-file.md new file mode 100644 index 0000000..7422f5c --- /dev/null +++ b/decisions/16-directory-instead-of-file.md @@ -0,0 +1,73 @@ +# 16. Каталог вместо файла в `docs/` — отложено до переезда healthlog (2026-08-04) + +## Что было + +Вопрос: разрешить документам в корне `docs/` быть не только файлом, но и +каталогом — когда документ описывает несколько принципиальных решений или +перерастает 400–500 строк. Паспорт остаётся файлом в любом случае: компактность +и есть его функция. + +Механизм в каноне уже работает — `conventions/`, `research/`, `adr/` каталоги с +обязательным `README.md`-индексом, — так что вопрос не «можно ли», а «от чего +лечим». + +## Решено + +**Р55. Порог в строках триггером не становится.** Замер по проектам: у порога +ровно один документ — `healthlog/docs/architecture.md`, 1662 строки. В нём +десять маркеров долга, а разделы — «Слои гранулярности» (211 строк), «Тренировки +и прочие секции» (222), «Условный запрос», «Свёртка и размер ответа», «Форма +ответа». Это **поведение**, чей нормативный дом `openspec/specs/`, где у проекта +уже лежат пять capability. Остальные документы 108–438 строк, `jellybit` — 169. +Порог сработал бы ровно там, где надо не разносить, а доводить переезд, и дал бы +долгу постоянное жильё: разложить 1662 строки по файлам дешевле, чем вынести их +в спеки, а после раскладки давление исчезнет и второй дом поведения останется +навсегда. + +**Р56. Шов выноса — другой читатель или другой срок жизни, а не размер.** По +этому критерию кандидатов два. `review.md` — сильнее прочих: у него уже записаны +два раздела с разными сроками жизни, настройка конвейера стабильна и читается +проходами, а журнал дефектов растёт неограниченно. `architecture.md` — по шву +«окружение, деплой, наблюдатель», у которого отдельный читатель `ops`. А вот +расщепление архитектуры **по принципиальным решениям отвергнуто**: у факта +«почему решено так» дом `adr/`, и вынесенные разделы немедленно станут его +вторым домом. + +**Р57. `security.md` и `passport.md` каталогом не становятся.** У `security.md` +ценность именно в цельности: периметр первой строкой и «что вне модели» читаются +враждебным проходом за один раз, а разнесённые — расходятся первыми. У +`database.md` механизм заводить не под что: 241 и 211 строк. + +**Р58. Если вводить — точка входа остаётся одна.** `docs/architecture.md` +упомянут в репозитории 66 раз: девять charter'ов, карта `project-facts.md`, +`docs.py`, скелеты. Развилка «файл или каталог» размножится на девять «прочитай +либо обойди». Поэтому форма жёсткая: каталог легален только при +`<имя>/README.md`, и он **и есть** прежний документ — обзор целиком со ссылками +на вынесенное, а не оглавление к нему. Каждый файл каталога обязан быть достижим +ссылкой из `README.md`; это проверяется сегодняшним механизмом ссылок `docs.py` +и ловит файл-сироту. Вынеся раздел, `README.md` на него **ссылается, а не +пересказывает** — тот же приём, которым в архитектуре уже описаны компоненты со +ссылкой на capability. + +**Р59. Решение отложено до конца переезда `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 "`, а требовал ``; нашлось это первой же попыткой ими воспользоваться. Пример в докстроке — +тот же образец, что плейсхолдер в схеме. diff --git a/decisions/29-doc-consistency-trial.md b/decisions/29-doc-consistency-trial.md new file mode 100644 index 0000000..01c9043 --- /dev/null +++ b/decisions/29-doc-consistency-trial.md @@ -0,0 +1,65 @@ +# 29. Обкатка `doc-consistency` на самом dev-skills (2026-08-05) + +Первый прогон агента — по репозиторию, который его же и содержит. Два прохода +(av-dev-pm; пайплайн плюс верхний уровень), 17 находок, все подтверждены по +файлам. + +**Р118. Агент нашёл ровно тот класс, ради которого заводился, и в свежей +работе.** Пять находок — остатки прежней модели типов в файлах, которые я не +дошёл поправить двумя коммитами раньше: `adopt.md` держал имена секций **канона +2**, `from-review.md` и `TODO.md` — упразднённый `[idea]`, `task-batch` в другом +плагине — «задачи-идеи». Правку модели я вёл от документов, которые менял, а не +от документов, которые на них ссылаются, — и обратный обход не сделал ни разу. + +**Р119. Самая дорогая находка была моей и свежей.** Таблица типов в `canon.md` +объявляла цель у `fix` запрещённой, а `tasks/SKILL.md` и `task-fix.md` — +необязательной; код на стороне вторых. Копия разошлась с домом **за один день** +— я написал обе половины в одном коммите. Это и есть цена второго дома в чистом +виде: не «когда-нибудь разойдётся», а «разошлось прежде, чем высохли чернила». + +Исход не «поправить значение», а **убрать причину**: `canon.md` дважды объявлял, +что фиксирует только словарь типов, — значит колонкам «разделы» и «цель» в нём +не место. Осталась таблица из двух колонок и ссылка на дом схемы. + +**Р120. Копии перечня «чем держат проект» разъехались втроём.** `canon.md`, +`tasks/SKILL.md` и `task-goal.md` пересказывали его своими словами: «метрики и +логи» против «мониторинга», «проверки» есть в двух из трёх. При этом +`tasks/SKILL.md` **ссылался на дом рядом с собственным пересказом** — ссылка не +мешает копии разойтись, если копия всё равно стоит. + +**Р121. Находка про коммиты снята как неверная, и это дефект самого агента.** Он +прочитал `av-dev-git/skills/commit/SKILL.md` («без `Co-Authored-By`») как +описание практики этого репозитория и предъявил 38 коммитов с трейлером. Но +dev-skills — **маркетплейс плагинов**: скилл коммита здесь продукт, уезжающий в +чужие проекты, а не правило, которому подчиняется сам репозиторий. Устав агента +не различает «документ описывает этот репозиторий» и «документ описывает то, что +репозиторий производит». + +**Р122. Счётчики в документах отменены как класс.** `REMAINING.md` держал «после +разбора двенадцати тем и 16 коммитов» (стало 28 и 52) и «три неизмеренных +изменения подряд» (стало больше). Оба числа обязан двигать человек, и оба +отстали молча. Заменены на формулировки, которые не надо поддерживать, и в шапку +записана причина. + +## Что из этого следует + +**С109. Правка модели идёт по обратным ссылкам, а не по изменённым файлам.** +Меняешь дом — обойди тех, кто на него ссылается: `grep` по упразднённому слову +дал бы все пять остатков за минуту. Это дешевле любого агента и должно идти до +него. + +**С110. Ссылка на дом не отменяет копию, стоящую рядом.** Проверять надо не +«есть ли ссылка», а «есть ли пересказ»; `tasks/SKILL.md` имел и то и другое. + +**С111. Копия расходится с домом в пределах одного коммита.** Прежняя оценка +(«разойдётся на первой правке») занижена: расхождение возникает при написании, +если оба места пишет один проход. + +**С112. Агент, читающий репозиторий-продукт, обязан различать «про нас» и «про +то, что мы производим».** Иначе он предъявляет продукту практику его +потребителя. Устав `doc-consistency` этого различения не содержит — остаток +записан в REMAINING. + +**С113. Число в документе — обязанность, которую никто не берёт.** Счётчик тем, +коммитов, правок протухает молча; формулировка без числа дешевле его +сопровождения. diff --git a/decisions/30-av-dev-backlog-removed.md b/decisions/30-av-dev-backlog-removed.md new file mode 100644 index 0000000..36d6009 --- /dev/null +++ b/decisions/30-av-dev-backlog-removed.md @@ -0,0 +1,41 @@ +# 30. `av-dev-backlog` удалён (2026-08-05) + +Плагин был помечен устаревшим решением [Р17](04-plugin-boundaries.md) и жил до +перевода jellybit. Удалён раньше этого срока. + +**Р123. Замороженный плагин стоит дороже, чем кажется.** Он не менялся, но +платил собой в каждой проверке репозитория: `exclude` в `pyproject.toml`, +`SKIP_DIRS` в `copies.py`, два абзаца README, оговорка в описании маркетплейса, +чтобы не ловить триггер «добавь задачу в беклог». Пять исключений ради кода, +который никто не читает, — и каждое надо было объяснять всякий раз, когда +кто-нибудь спрашивал, почему проверка обходит каталог. + +**Р124. Понимание старой раскладки уехало из плагина раньше самого плагина.** +`docs/backlog/` читает не `backlog.py`, а `av-dev-pm:tasks` — `adopt.md` и +адаптер в `tasks.py` держат ту же раскладку как **вход миграции**. Плагин +перестал быть единственным, кто её знает, ещё когда писался `adopt`; условие +«живёт до перевода последнего проекта» с тех пор охраняло пустоту. + +**Р125. Опасение про порядок снятия не подтвердилось.** Удаление опередило +снятие: на jellybit плагин оставался включённым, когда записи в маркетплейсе уже +не было, и ожидалась ручная чистка `enabledPlugins` и `installed_plugins.json`. +`claude plugin uninstall` отработал штатно — он идёт **по реестру, а не по +манифесту маркетплейса**, и отсутствие записи там ему безразлично. +Предупреждение из README снято, вместо него записан проверенный факт. + +## Что из этого следует + +**С114. Устаревшее удаляют, а не замораживают.** Заморозка выглядит бесплатной, +но растекается исключениями по конфигам и требует объяснения в каждом месте, +куда попала. Если удалять пока рано — назвать условие и срок; условие без срока +переживает свою причину. + +**С115. Условие «живёт до X» проверяют на живость, а не на X.** Здесь X (перевод +jellybit) не наступил, но причина условия отпала раньше: знание раскладки +переехало в `adopt`. Перепроверять надо основание, иначе условие держит само +себя. + +**С116. Порядок снятия и удаления из маркетплейса свободный.** `uninstall` живёт +реестром, манифест ему не нужен. Правило записано после проверки, а не из +осторожности, — и осторожность здесь стоила бы лишнего абзаца в README про +починку, которой не бывает. diff --git a/decisions/31-pm-coverage-product-review.md b/decisions/31-pm-coverage-product-review.md new file mode 100644 index 0000000..1c60905 --- /dev/null +++ b/decisions/31-pm-coverage-product-review.md @@ -0,0 +1,126 @@ +# 31. Ревизия покрытия `av-dev-pm` продакт-оптикой (2026-08-05) + +Сабагент в роли продакт-менеджера оценил покрытие жизненного цикла личного +проекта (один человек, недели-месяцы) скиллами и агентами `av-dev-pm`. Скоуп +сужен по ходу разбора: деплой и разбор инцидентов на проде делаются вручную, +скиллов под них не заводим. Осталось планирование, разработка и доработка. + +**Р126. Шаг 2 сессии требовал чисел, которых процесс отказался собирать +решением.** `cadence.md` делал обязанностью пересмотр «ориентира по размеру +спринта, прироста беклога на закрытую задачу, времени на задачу» и «сколько +заняли задачи **против ожидания**» — с обоснованием «иначе обязанность висит +ничья». Данных под это нет: у записи нет дат заведения, взятия и закрытия, +`close --implemented` удаляет файл, `sprint close` очищает `SPRINT.md`. Хуже +того, «против ожидания» и «время на задачу» требуют оценки и тайм-бокса, а +`session/SKILL.md` в «Почему не Scrum» их прямо не берёт: пункт противоречил +решению, стоящему через файл от него. + +Исход — **выкинуть, а не подпереть данными**. На практике числа не +пересматривались ни разу, и заводить под них учёт дат значило бы обслуживать +обязанность, которой никто не брал. Осталось качественное: что сломалось в +процессе, что оказалось дороже, чем выглядело при заведении, какие правила не +сработали. Шаг 3 пункт 9 переименован из «переоценки по измеренному» в +«переоценку по пройденному», судит человек по памяти о спринте. Рядом записано, +что замеров нет **намеренно** — иначе следующий читатель заведёт их обратно как +недостающие. + +**Р127. `doc-consistency` переехал с каждого синка на сессию, к +`doc-code-drift`.** Агент на `opus` зовётся шагом 9 пайплайна, то есть на каждой +задаче: 5–8 opus-проходов за спринт по документам, которые за спринт меняются на +несколько абзацев. Обоснование в каноне («сверка текста с текстом дёшева») верно +относительно второго агента, но не в абсолюте на одиночке. + +Довод сильнее денег: **расхождение между двумя документами по определению +требует двух документов**, а на большинстве задач синк правит один. И пачка, +отбираемая работой, не видит того, чего работа не касалась, — а расхождение живёт +ровно там: правка отменяет решение в одном документе, парный статус нужен в +другом. Это был открытый вопрос `REMAINING` про охват ADR при пересмотре; переезд +его закрыл. Цена — потеря привязки находки к задаче, которая её породила: по теме +29 именно эта привязка дала пять самых точных находок. Принято сознательно. + +**Р128. Отмена цели получила порядок, но не флаг.** `close` запрещал закрыть +цель с живыми задачами, а что делать с этими задачами, не говорил нигде: шаг 3 +сессии знал только «та ли цель», `task-goal.md` описывал одно достижение, а +`session/SKILL.md` вдобавок утверждал «цель постоянна». Человек получал отказ с +перечнем и никакой подсказки. + +Порядок записан: сперва задачи поштучно (`close --reason` своей причиной либо +`edit --goal` на другую цель), потом сама цель через `close --reason` в +`REJECTED.md`, а не в `Готово` — отменённая цель не умеет ничего. Флаг +`--cascade` отвергнут: отмена цели редка и дорога, и поштучный разбор здесь не +церемония, а единственный момент, когда видно, что из задач переживёт цель. +Каскад превратил бы его в один Enter. **Причина у каждой задачи своя**: «цель +отменена» это пересказ команды, в `REJECTED.md` от него нет пользы через квартал. + +Место процедуры — переоценка на сессии, а не отдельный заход: отмена цели **и +есть** разбор всех её задач, а разбор задач — шаг 3. + +**Р129. У брошенного спринта появился второй законный исход, без порога.** +`--dissolve` во всех текстах был привязан к блокеру, и скрипт отказывал словами +«роспуск объясняется блокером». Вернувшийся к набору, который стоял месяц, не +имел законного хода: двигать нельзя (заморозка), распускать не по чему. Теперь +роспуск объясняется блокером **или тем, что набор протух**. + +Порога в неделях сознательно нет — это тот же класс, что выкинутые числа шага 2: +счётчик простоя пришлось бы вести руками, а решает всё равно человек. Признак не +срок, а **что набор перестал быть твоим**: перечитываешь, зачем эти задачи вместе +— он протух. Туда же добавлена точка входа «вернулся, а спринт открыт»: `check`, +`SPRINT.md`, развилка продолжать/распустить. Середины у развилки нет намеренно — +«доделаю пару штук и решу» это работа по набору, которого ты не понимаешь. + +**Р130. Журнал канона прогоняется как есть, а проверка исхода поручена судьям.** +Схлопнуть записи 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](29-doc-consistency-trial.md). + +**С119. Частота вызова агента выводится из того, что он ищет.** Судья +расхождений **между** документами бессмысленен там, где документ один; значит +его место не на задаче, а на наборе задач. Цена вызова подтвердила вывод, но не +она его дала. + +**С120. Запрет обязан называть выход.** `close` верно не давал осиротить задачи, +но текст отказа перечислял препятствия и молчал о ходе. Проверка без названного +следующего шага — половина работы: она защищает данные и бросает человека. + +**С121. Признак вместо порога там, где счётчик пришлось бы вести руками.** +«Набор перестал быть твоим» проверяется в момент вопроса и ничего не требует +хранить; «прошло N недель» требует учёта, который никто не ведёт, и всё равно +кончается решением человека. + +**С122. Версионирование без единого переехавшего проекта — не журнал миграций, а +история правок.** Довод за схлопывание был верен по факту и отвергнут по +принципу: обкатка на живых проектах и проверяет, работает ли механизм. Схлопнуть +значило бы не прогнать его ни разу и оставить вопрос открытым. + +**С123. Проверка версии не есть проверка миграции.** Число в `.pm.json` двигает +тот же проход, что делал шаги, — и двигает независимо от того, все ли сделаны. +Механической проверки существа нет; там, где её нет, ставится судья, а не +отметка. diff --git a/decisions/32-vocabulary-sweep.md b/decisions/32-vocabulary-sweep.md new file mode 100644 index 0000000..6b95e36 --- /dev/null +++ b/decisions/32-vocabulary-sweep.md @@ -0,0 +1,54 @@ +# 32. Сквозной проход по словарю: пять слов сняты, девять закрыты списком (2026-08-05) + +Проход упрощения ([тема 31](31-pm-coverage-product-review.md)) уткнулся в один и +тот же класс у всех пяти агентов: слово, живущее в трёх-шести файлах разом. +Правка в одном месте развела бы словарь, правка во всех — уже не упрощение +текста скилла. Каждый агент честно остановился и записал слово в свой отчёт, и +одни и те же слова всплыли в разных отчётах. Разобрано отдельным проходом. + +**Р131. «Слово прижилось» не проверяется, поэтому заменено списком.** Оговорка в +`language.md` звучала так: не переводится «термин, у которого нет точного +русского эквивалента и который в команде уже прижился». Проверить это на глаз +нельзя — прижившимся выглядит любое слово, встреченное трижды, и ровно так пять +агентов подряд и рассудили. Оговорка заменена **закрытым списком из девяти +терминов** с колонкой «что называет»: интейк, триаж, провенанс, дедуп, чек-лист, +дифф, промпт, сущности OpenSpec, роды проходов ревью. Слово не из списка и не из +таблицы имён вещей — находка, а не принятый стиль. + +Список заведён домом `язык-словарь` в `language.md` и копией в уставе +`doc-wording`. Копия обязательна: агент работает в репозитории проекта, где +плагина может не быть, и без списка предъявил бы «интейк» как англицизм. + +**Р132. Пять слов сняты, и все пятеро выглядели словарём, не будучи им.** +`конфляция` → смешение (4 места), `декорреляция` → разведённость (6), +`непоймание` → почему не поймали (9), `эвал-сет` → проверочный набор (4), `гайд` +→ руководство (6). Латинизм или калька при живом русском слове в каждом случае. + +Разбор `декорреляции` показателен: проект **уже владел** нужным словом — «агенты +разведены по глубине», «разведены по охвату» — и держал рядом латинский синоним +того же понятия. Это не англицизм, а второй дом для слова. + +`непоймание` снято ещё и потому, что форма журнала дефектов, которую канон кладёт +в проекты, спрашивает «Почему не поймали» — а проза рядом называла это +«причиной непоймания». Скелет и проза о скелете говорили разными словами. + +**Р133. Снятое записано вместе с оставленным, в одном списке.** Иначе снятое +возвращается: слово уходит из текстов, но ничто не мешает следующему проходу +завести его заново — оно ведь короткое и точное. Пять слов названы поимённо с +заменой каждого. + +## Что из этого следует + +**С124. Escape hatch без перечня — это разрешение, а не исключение.** «Термин, +который прижился» освобождает от правила любое слово: проверка «прижился ли» +возвращает «да» всякий раз, когда слово встретилось. Исключение из правила +обязано быть списком, иначе оно съедает правило. + +**С125. Слово, от которого агент отказался править, — материал для отдельного +прохода, а не мусор отчёта.** Пять независимых агентов сошлись на одном наборе +слов, ни разу друг друга не видя. Список «что не тронул» оказался полезнее +списка правок именно этим. + +**С126. Снятое слово называется вместе с заменой и остаётся записанным.** Убрать +из текстов недостаточно: без записи «это снято и вот чем заменено» слово +возвращается первым же, кто найдёт его удачным. diff --git a/decisions/33-review-cost-cut.md b/decisions/33-review-cost-cut.md new file mode 100644 index 0000000..129d418 --- /dev/null +++ b/decisions/33-review-cost-cut.md @@ -0,0 +1,73 @@ +# 33. Стоимость ревью: снят самый дорогой проход и самая дорогая модель (2026-08-06) + +Прогоны стали долгими, а счёт в токенах — заметным. Разбор шёл не по находкам, а +по статьям расхода: что в конвейере стоит больше всего и что из этого окупается. +Две статьи названы прямо оператором. + +**Р134. Проход независимой реализации снят целиком, и с ним профиль `deep`.** +`reimpl` писал свою реализацию узла, не открывая существующую, и диффил по +решениям. Его счёт определялся **объёмом вывода** — он один писал код, а не +читал его, — и на прогоне это была самая большая строка расхода. Снят по решению +о стоимости. + +Профиль `deep` от этого не «похудел», а исчез: `reimpl` был **единственным**, чем +он отличался от `wide` (обоим оставалось бы 0, 1, 2, 4, 5). Держать два имени для +одного состава нельзя — ровно от этой болезни лечилась ступень `wide` (решение +JJJ): у профиля обязан быть один правильный ответ, иначе реестр состава нечем +проверять. Ступеней теперь три: `quick`, `standard`, `wide`. + +Вместе с профилем ушло всё, что обслуживало только его: + +- **барьер стоимости** — он существовал ровно затем, чтобы дорогой проход не + писал реализацию против кода, который через час перепишут. Дорогого прохода + нет, и граф стал плоским во всех профилях: от гейта до триажа. Рёбер осталось + два вида вместо трёх — зависимость и конфликт за ресурс; +- **тест «идентичность, слияние, разбор»** (решение из [темы +27](27-record-type-single-axis.md)) — он служил + единственной цели: выбрать `deep` не по ощущению. Выбирать больше нечего, и + полторы страницы теста сняты вместе с проектным перечнем мест в + `docs/review.md`; +- **стадии перенумерованы**: 0 гейт, 1 сверка, 2 враждебный и эксплуатационный, + 3 архитектурный, 4 триаж. Дыра на месте третьей читалась бы как пропущенная + стадия. + +**Р135. Снятие записано как сознательное сужение, а не как «класс оказался +пустым».** `calibration.md` требует замера на двух проектах перед удалением +прохода, и замера не было — было решение о цене. Значит и в «Честном пределе» +стоит честная строка: **«не знаю, чего не знаю» больше не достаёт никто.** +Остаток независимого взгляда дают профиль `design` (код пишется под его находки) +и `architecture` (второй способ, лишние слои), но альтернативной реализации, с +которой можно сдиффить решения, у конвейера нет. Класс уходит в границы покрытия +каждого прогона, а у проекта — в подраздел «перестали проверять сознательно». + +Без этой записи снятие через месяц читается как «проверено и признано лишним», +и вернуть проход было бы не на чем. + +**Р136. Самая дорогая модель снята со всех проходов.** На ней сидели трое: +`review-triage`, `review-architecture` и `doc-code-drift` из `av-dev-pm`. Все +трое переведены на `opus`. Основание для верхней модели — «ошибка +распространяется дальше самой находки» — никуда не делось, но оно объясняет, +почему эти двое **не опускаются до `sonnet`**, а не почему им нужна ступень выше +`opus`: разницы в пользу более дорогой модели не показал ни один прогон, а время +и счёт она множила. + +Палитра цветов схлопнулась до двух: `sonnet` → green, `opus` → yellow. Красного в +репозитории больше нет, и `frontmatter.py` теперь отвергнет модель вне этих двух — +раскладка проверяется механически, как и раньше. + +## Что из этого следует + +**С127. Профиль, у которого не осталось собственного прохода, — не профиль.** +Ступень стоимости определяется тем, что она **добавляет**; сняли добавку — сняли +ступень, а не оставили имя. Иначе два имени указывают на один прогон, и состав +снова нечем проверить. + +**С128. Удаление по цене и удаление по замеру записываются по-разному.** Первое +обязано назвать класс, который перестал проверяться, и оставить его в границах +покрытия. Второе — сослаться на замер. Смешение их даёт самый дорогой вид +тишины: пробел, выглядящий как решённый вопрос. + +**С129. Механика, обслуживающая один проход, снимается вместе с ним.** Барьер +стоимости, тест выбора верхней ступени и проектный перечень мест держались +только на `reimpl`. Оставшись, они выглядели бы работающими правилами и тратили +бы внимание на каждом прогоне. diff --git a/decisions/34-throughput-vs-depth.md b/decisions/34-throughput-vs-depth.md new file mode 100644 index 0000000..367384c --- /dev/null +++ b/decisions/34-throughput-vs-depth.md @@ -0,0 +1,87 @@ +# 34. Пропускная способность против глубины: тяжёлые проходы уехали в верхнюю ступень (2026-08-06) + +Тема 33 сняла самую большую разовую статью расхода, но не тронула главную — +**частоту**. Меряющая пара стояла в `standard`, то есть на большинстве задач, и +именно она делала прогон долгим: два прохода держат машину, идут цепочкой и +доказывают находки запуском. Разбор шёл от цели, названной прямо: **лучше +поправить в следующей задаче, чем держать одну два часа.** + +**Р137. `adversary` и `ops` переехали в `wide`, и это решение по цене, а не по +ценности.** Стадия осталась самой урожайной за всю историю замеров — пять из +семи выживших находок дозапуска и единственная находка про молчаливый старт +отката. Но её ценность оплачивается на **каждой** задаче, а получается на +немногих: оракул добывается запуском, запуск — это машина, цепочка и часы. +Ступень, которая раньше была умолчанием, стала исключением на 5–10% задач. + +**Р138. Заведён `review-basics` — мелкая осадка двух тяжёлых проходов, без +единого запуска.** Он стоит только в `standard` и берёт ту половину вопросов, на +которые отвечают **чтением**: таймаут и отказ соседа, идемпотентность и +одновременная запись, остановка на середине, частичный откат при двух версиях, +наблюдаемость и тишина, очевидный рост объёма — плюс два вопроса архитектурного: +второй способ мимо единой точки (грепом, не картой) и что отсюда удалить. +Потолок 4 находки, машину не держит, ничего не меряет. + +Отдельная его обязанность — **вопрос 4, частичный откат**. Без него правило +«миграция схемы не поднимает ступень» рассыпалось бы: раньше миграцию разбирал +`ops`, а он теперь в `wide`. Проход заведён не «до кучи», а затем, чтобы у +`standard` остался хоть один взгляд на ось времени. + +Модель у него верхняя, `opus`, и это не противоречит слову «средний»: усилие +режется **входом и потолком**, а не моделью. Дешёвая модель на проходе +с мнением платит триажем — это записанный замер, и отменять его без нового замера +нельзя. + +**Р139. Объём и незнакомость изменения вошли в правило выбора ступени.** Раньше +ступень выбиралась только по классу («вводит ли новое понятие»), и правило прямо +запрещало смотреть на размер. Теперь вопросов два: крупное или незнакомое +(трогает несколько узлов, переносит ответственность, форму решения нащупывают по +ходу) → `wide`; мелкое (один узел, форма очевидна заранее, откат — обратная +правка) → `quick`; всё остальное → `standard`. Причина смены: цена +разбирательства растёт именно с объёмом и неизвестностью, а не с классом +правила. + +Отрицательный тест `quick` сохранил прежнюю мудрость в новой рамке: **что после +мерджа не откатывается обратной правкой — не `quick`, каким бы маленьким ни был +дифф.** Три строки миграции идут в `standard`. + +**Р140. Спорный случай решается вниз, и асимметрия объяснена ценой.** Между +`standard` и `wide` — в пользу `standard`: ошибка сюда стоит находки на +следующей задаче, ошибка обратно стоит трёх тяжёлых проходов на каждой задаче, +выбранной неверно. Между `quick` и `standard` — тоже в пользу `standard`, но по +другой причине: там разница в один дешёвый проход, зато единственный, кто на +нижних ступенях смотрит на отказы. + +Доля `wide` 5–10% записана как **проверка правила, а не пожелание**: если ступень +уходит каждой третьей задаче, её выбирают по ощущению важности. + +**Р141. Сделка записана вместе с механизмом обратной связи, иначе это тихая +потеря качества.** На `quick` и `standard` не проверяется ничего, что требует +запуска: построенный путь, эксперимент против драйвера, любое число. Это самая +крупная граница покрытия конвейера, и она обязана идти строкой в каждом таком +прогоне поимённо. Обратная связь — журнал дефектов `docs/review.md`: класс, +который ловят только меряющие проходы, начал всплывать после мерджа — значит +ступень выбирают слишком низко. Плюс сам `basics` обязан сигналить строкой, если +видит, что ступень занижена: он единственный, кто смотрит на дифф целиком на +нижних ступенях. + +## Что из этого следует + +**С130. Стоимость прохода — это его цена, умноженная на частоту, и вторая +переменная важнее.** [Тема 33](33-review-cost-cut.md) убрала самый дорогой +проход, тема 34 — самый частый. Второе дало больше, хотя снятый проход был +дешевле каждого отдельного `reimpl`. + +**С131. Урожайность прохода не отвечает на вопрос, где ему стоять.** Меряющая +пара осталась самой ценной и всё равно уехала вверх: ценность оправдывает +существование прохода, но не его частоту. + +**С132. Замена тяжёлого прохода лёгким записывается как сужение, а не как +эквивалент.** `basics` задаёт те же вопросы чтением, и его ответы поэтому слабее +— условия вместо оракулов. Назвать это «покрыли то же дешевле» значит соврать +себе на первом же прогоне. + +**С133. Ступень, выбираемая по классу изменения, слепа к объёму.** Правило, +запрещавшее смотреть на размер, защищало от выбора по ощущению важности — и +заодно отправляло трёхстрочную правку и переборку пяти узлов в один профиль. +Признаков нужно два: класс отвечает за обратимость, объём — за цену +разбирательства. diff --git a/decisions/35-model-revision.md b/decisions/35-model-revision.md new file mode 100644 index 0000000..0050697 --- /dev/null +++ b/decisions/35-model-revision.md @@ -0,0 +1,75 @@ +# 35. Ревизия моделей: переведены двое из девяти, и критерий оказался не тот (2026-08-06) + +Сквозной проход по тринадцати уставам с одним вопросом: кого из девяти +`opus`-агентов можно опустить на `sonnet` без потери. Ответ — двоих, и по дороге +выяснилось, что критерий, которым конвейер до сих пор раздавал модели, отвечает +не на тот вопрос. + +**Р142. Модель выбирается по цене ошибки, а не по роду прохода.** Прежнее +деление — applicative против generative — раздаёт модели по тому, **откуда** +проход берёт критерий. Но платит проект не за происхождение критерия, а за +разбирательство с находкой. Рабочий признак: + +- находка приходит **со ссылкой на записанный источник** (строка спеки, цель в + манифесте, значение в конфиге, номер правила) — её опровержение стоит одного + открытия файла. Дешёвая модель ошибается здесь **проверяемо**; +- находка есть **суждение** («это второй способ», «этот оракул негоден», «эти два + документа противоречат») — опровержение стоит рассуждения, а рассуждение стоит + триажа или человека. + +Признак объясняет прежнюю раскладку лучше, чем она сама себя: `gate`, `code` и +`ops` не потому дёшевы, что применяют чек-лист, а потому, что каждая их находка +показывает пальцем на строку. + +**Р143. `doc-code-drift` → `sonnet`.** У него закрытый перечень из восьми +правил, и каждое — пара «факт в документе ↔ команда, которой он проверяется». +Устав прямо запрещает суждение («верность и полноту не проверяешь»), требует +формы «написано X, в коде Y, проверено командой Z» и правила «нечем проверить — +не находка». Ложная находка опровергается **той же командой, которая её +породила**. Это самый чистый случай признака за весь разбор. + +**Р144. `task-form` → `sonnet`.** Семь пронумерованных правил с таблицами форм и +поимённым перечнем подмен. Но решило не это, а потребитель: его находка — +готовая формулировка, которую человек читает и отклоняет командой, а не +оркестратор, который **молча реализует**. Довод, державший `triage` на верхней +модели, здесь не работает вовсе: ошибка стоит строки чтения. + +**Р145. `review-specs` рассмотрен и оставлен на `opus` — по причине, обратной +общей.** Он самый частый `opus`-проход конвейера (идёт и в `design`, и на коде, +то есть дважды за задачу), и по устройству он applicative: SKILL.md сам называет +стадию 1 «два applicative-прохода, оба дешёвые», хотя платит за одного `sonnet`, +а за другого `opus`. Расхождение разобрано и закрыто текстом: держит его наверху +направление `code → spec`, где надо заметить **отсутствие** — тихий фолбэк, +самодеятельный дефолт, проглоченную ошибку. Прочие держат `opus` из-за цены +ложных находок, этот — из-за цены пропущенных, а пропуск не оставляет следа +нигде: ни в отчёте, ни в границах покрытия. + +**Р146. Остальные шестеро оставлены, и у каждого своя причина.** `adversary` и +`rubric` порождают критерий по построению (второй — с запретом открывать код в +первой фазе). `architecture` — чистое суждение о структуре. `triage` — сток, его +ошибка становится кодом. `doc-consistency` ошибается ровно в ту сторону, которую +дороже всего опровергать: путает «упомянуто в двух местах» с «оба утверждают». +`basics` заведён час назад, половина его вопросов — суждение, и модель у него +выбрана решением оператора в этой же сессии. + +**Р147. Это разбор уставов, а не замер, и так и записано.** `calibration.md` +двигает модель инъекцией дефекта; здесь инъекции не было. Двое переведены +потому, что их ошибка **обнаруживается той же проверкой, что породила находку**, +— то есть цена ошибки ограничена сверху независимо от модели. Для остальных +такой границы нет, и трогать их без замера нельзя. + +## Что из этого следует + +**С134. Дешёвая модель безопасна там, где её ошибку опровергает та же команда, +что породила находку.** Не «где критерий записан» — записанный критерий бывает и +у суждения, и у сверки, а разница между ними в том, чем кончается спор. + +**С135. Ошибка бывает двух родов, и модель защищает от разных.** Ложная находка +стоит триажа и видна; пропущенная не стоит ничего сегодня и не видна вовсе. +Проход, у которого дороже второе, держится на верхней модели даже будучи +applicative. + +**С136. Потребитель находки — часть её цены.** Одна и та же ошибка стоит строки +чтения, если её читает человек, и разросшегося кода, если её молча реализует +оркестратор. Модель раздаётся с оглядкой на это, а не только на устройство +прохода. diff --git a/decisions/36-review-topics-project-docs.md b/decisions/36-review-topics-project-docs.md new file mode 100644 index 0000000..2e9de09 --- /dev/null +++ b/decisions/36-review-topics-project-docs.md @@ -0,0 +1,106 @@ +# 36. Темы ревью: документ проекта стал направлением проверки (2026-08-06) + +Замечено при сверке документов канона с составом ступеней: **три документа +остались без читателя ниже `wide`** — `security.md`, `database.md` и `adr/`. +Проект поддерживал их, а на 90% задач их не открывал никто. Причина оказалась не +в переезде проходов, а в том, как описан состав прогона. + +**Р148. Тема первична, проход вторичен, и это правило 0 конвейера.** Список тем +нигде не был записан: он существовал побочным продуктом списка проходов. Проход +уезжал в верхнюю ступень — и тема уезжала с ним **беззвучно**: отчёт честно +говорил «`ops` не запускался» и не говорил «эксплуатацию не смотрел никто», а +нужно второе. Теперь прогон описывается таблицей «тема → дом → глубина → кто +закрывает», и таблица есть в каждом отчёте. + +**Р149. Тема есть документ, и список тем открытый.** Всё, что проект кладёт в +`docs/`, становится темой ревью; запретить нельзя, разрешения не надо. Не темы +ровно две: `docs/tasks/` и `docs/review.*` (настройка самого конвейера — слой +над темами). Отсюда главное следствие: **`docs/` перестал быть документацией и +стал конфигурацией конвейера.** Проект настраивает проверку тем, что пишет о +себе, а не отдельным файлом настроек, который разошёлся бы с документами. + +Ядро — шесть тем: `requirements`, `autotests`, `conventions`, `architecture`, +`security`, `operations`. Их дома канон обещает. Всё сверх — темы проекта, и их +разбирает `basics`: именных проходов конечное число, а тем столько, сколько +заведёт проект, поэтому приёмник обязателен. + +**Р150. Тема живёт файлом или каталогом, на выбор проекта.** `docs/security.md` +и `docs/security/` — одно и то же. Прежде форма была задана поимённо +(`conventions`, `research`, `adr` — каталоги, остальные — файлы), и обосновать +это было нечем; заодно в TODO висел открытый вопрос «а если `architecture.md` +разрастётся». Теперь ответ механический: разросся — стал каталогом с +`README.md`, и это не смена версии канона. Обе формы сразу — ошибка, и `docs.py` +её ловит: два дома для одного факта расходятся молча. + +**Р151. Ступень выбирает разметчик, а не автор.** Заведён `review-scope` +(`sonnet`), стадия 0, до гейта: находит документы, выводит темы, назначает +глубины, выбирает ступень с обоснованием. Довод сильнее, чем синхронизация +документов: **до сих пор профиль называл тот же оркестратор, который написал +код** — то есть в точке выбора глубины проверки разведённости с автором не было +вовсе, и решала она под давлением «я почти закончил». Вызывающий пайплайн +профиль больше не передаёт. + +Право у разметчика симметричное — поднять и понизить, — но обоснование +обязательно всегда, а не только при отступлении от умолчания. + +**Р152. Разметчик передаёт адреса, а не пересказ.** Проект однажды уже держал +файл-посредник между документами и проходами (`review-brief.md`) и убрал его: +второй дом расходится с первым и выглядит актуальным. Пересказ в задании — тот +же посредник, живущий один прогон. Исключение одно: **отсутствие дома** — этого +проход сам дёшево не выяснит. + +`sonnet` ему хватает потому, что вывод устроен как **список**: каждый файл в +`docs/` обязан попасть в план темой или строкой «не тема, потому что», и план +сверяется с `ls docs/` за секунду. Выбор ступени — суждение, но у него три +независимых корректора: отрицательный тест `quick`, правило «спорный случай +вниз» и сигнал `basics` о заниженной ступени. + +**Р153. `quick` и `standard` совпали составом и разошлись глубиной.** Требование +«нижние ступени закрывают все темы, просто не так глубоко» иначе не выполняется: +темы одни и те же, а различать ступени больше нечем. Глубин три и они про способ +доказательства, а не про старательность: **сверка** (открыть дом, открыть дифф, +сравнить), **разбор** (построить сценарий рассуждением), **доказательство** +(прогнать, померить, построить путь). Третья есть только в `wide` — она одна и +требует машины. + +Цена принята: это единственное место конвейера, где профиль не выводится из +списка проходов, поэтому глубина объявляется в отчёте наравне со ступенью. + +**Р154. `review-code` переписан: технический разбор плюс конвенции.** Обнаружено +по ходу: **никто не читал код как код.** `specs` сверял с требованиями, `basics` +— с отказами окружения, `architecture` — с устройством, а `code` был проходом +только по прозаическим конвенциям и прямо объявлял, что дефекты рантайма и +логики не его. «Здесь ошибка в логике» не говорил никто, и это была самая +крупная дыра конвейера — крупнее любой недосмотренной темы. + +Теперь у прохода две половины: девять классов технического дефекта +(необработанная ветка отказа, пустое и нулевое, граница диапазона, перепутанный +операнд, неосвобождённый ресурс, изменение под итерацией, неверно применённый +интерфейс библиотеки, недостижимая ветка, «сделано соседнее») и прежняя сверка с +конвенциями. Модель поднята до `opus` по признаку [темы +35](35-model-revision.md): цена **пропущенной** находки — дефект в проде, и она +не оставляет следа ни в отчёте, ни в границах покрытия. + +**Р155. Вопросы проекта переадресованы темам.** В `docs/review.*` было «Вопросы +к проходам» в форме `ops: <вопрос>` — и когда `ops` уехал в `wide`, вопрос +перестал задаваться молча. Стало «Вопросы по темам». Туда же «Недоступно +проверке» — по темам, обоими подразделами. + +## Что из этого следует + +**С137. Состав, описанный исполнителями, теряет предмет при перестановке +исполнителей.** Список проходов отвечает «кто работал», а нужен ответ «что +проверено». Первое выглядит полным ровно тогда, когда второе неверно. + +**С138. Открытый список нуждается в приёмнике, иначе он обещание.** Разрешить +проекту завести свою тему и не назначить, кто её разбирает, — то же, что не +разрешать. + +**С139. Регулятор глубины проверки нельзя оставлять в руках автора.** Не потому +что он злонамерен, а потому что давление «я почти закончил» действует всегда и в +одну сторону. + +**С140. Дыру в покрытии находят не там, где ищут находки.** Три осиротевших +документа нашлись сверкой канона с составом ступеней, а отсутствие технического +ревью кода — сверкой оптик проходов между собой. Ни то ни другое не всплыло бы +на прогоне: прогон честно сообщал, что все запущенные проходы отработали. diff --git a/decisions/37-gate-and-autotests-one-name.md b/decisions/37-gate-and-autotests-one-name.md new file mode 100644 index 0000000..c9b0000 --- /dev/null +++ b/decisions/37-gate-and-autotests-one-name.md @@ -0,0 +1,35 @@ +# 37. `gate` и `autotests` сведены к одному имени (2026-08-07) + +Тема звалась `autotests`, закрывающий её проход — `gate`, и на всех трёх +ступенях это была одна и та же клетка таблицы. Одна сущность под двумя именами — +та же ошибка, что и два разных под одним, только тише: она не путает, а +**теряет**. Вопрос проекта в `docs/review.*` адресуется теме; адресованный +проходу — не приезжает никуда, и ровно этот отказ уже случился однажды с `ops` +(тема 36, [Р155](36-review-topics-project-docs.md)). + +**Р156. Победило имя темы, а не имя прохода.** Три довода, по убыванию веса: + +1. **Тема первична (правило 0), а имена тем — это имена документов.** + `docs/autotests.md` проект напишет: что покрыто, что нарочно нет, где + `testdata`. `docs/gate.md` не напишет никто — гейт это команда, а не предмет. +2. **Слово «гейт» уже занято дважды** — команда проекта и ребро графа («пока гейт + красный, проходы с мнением не идут»). Третье значение сделало бы отчёт нечитаемым: + «гейт красный» и «гейт нашёл» — про разное. +3. **Тема шире гейта.** «Хватает ли проверок» и «чего в гейте намеренно нет» за + пределы красного/зелёного выходят. Назвать целое именем инструмента — тихо его + сузить. + +Цена названа честно: `autotests` звучит уже своего содержимого — линт, типы, +сканер уязвимостей тестами не являются. Гасится строкой в уставе: тема — это +«проверено ли машиной», а не «есть ли тесты», и гейт в ней инструмент, а не +граница. + +## Что из этого следует + +**С141. Тема и проход, совпадающие один в один на всех ступенях, обязаны носить +одно имя.** Пока имён два, у сущности два адреса, а адресуют её по одному — и +какой из двух окажется живым, решает случай. + +**С142. Слово, уже значащее что-то в предметной области проекта, нельзя брать +именем роли конвейера.** «Гейт» принадлежит проекту раньше, чем ревью, и спор за +него ревью проигрывает. diff --git a/decisions/38-plugin-seam-no-pass-names.md b/decisions/38-plugin-seam-no-pass-names.md new file mode 100644 index 0000000..f0bcbeb --- /dev/null +++ b/decisions/38-plugin-seam-no-pass-names.md @@ -0,0 +1,35 @@ +# 38. Шов между плагинами: канон не называет имён проходов (2026-08-07) + +Замечено при сведении тем документации с ревьюверами: `av-dev-pm` в шести местах +называл конвейер поимённо — от прозы канона до **вывода `docs.py` пользователю** +(«свои темы проекта: … — их разбирает `review-basics`»). Плагины при этом +раздельные: `av-dev-pm` работает без конвейера, `av-dev-pipeline` — без канона, +поразрядно деградируя. + +**Р157. Общий словарь — имена тем и имена ступеней, и только они.** Ими проект +настраивает ревью: вопросы по темам и триггеры профиля. Имён проходов канон не +называет нигде. Направление зависимости при этом несимметрично и это верно: +**конвейер называет документы канона поимённо, потому что он их читатель**, а +обратной ссылки быть не может — документ живёт дольше, чем раскладка проходов. + +Заодно вычищены описательные адресации того же класса: «архитектурный проход +судит», «враждебный проход выдумает», «там идут враждебный, эксплуатационный и +архитектурный проходы». Последняя — худшая из них: это утверждение о **составе +ступени**, живущее на стороне, которая о составе не знает. + +**Р158. Пример в правиле не должен нарушать само правило.** Объяснение, почему +вопросы адресуются темам, звучало так: «вопрос, адресованный `ops`, перестал +задаваться в тот день, когда `ops` уехал в верхнюю ступень». Правило про +нестабильность имён, иллюстрированное именем. Стало «адресованный проходу» — и +работает даже после того, как проход переименуют. + +## Что из этого следует + +**С143. Ссылка из вывода скрипта дороже ссылки из прозы.** Устаревшую строку в +документе чинит тот, кто её читает; устаревшее имя в сообщении `docs.py` +доезжает до чужого проекта и там объясняется недоумением. + +**С144. Список, который никто не ведёт, честнее списка, который ведут двое.** +Читателей документа не перечисляет ни одна сторона — читатель назначается планом +прогона. Прежняя ссылка на «таблицу читателей» пережила саму таблицу и обещала +то, чего нет, — с той самой правки, которая таблицу и убрала. diff --git a/decisions/39-sprint-without-goal.md b/decisions/39-sprint-without-goal.md new file mode 100644 index 0000000..03f20b1 --- /dev/null +++ b/decisions/39-sprint-without-goal.md @@ -0,0 +1,47 @@ +# 39. Спринт без цели — законный случай (2026-08-07) + +Цель была обязательной: `sprint start --goal` требовал слаг, `check` считал +ошибкой набор без названной цели, `sprint take` отказывал задаче под чужой +целью. Модель описывала только спринт развития — а спринт бывает под багфикс, +под техдолг, под здоровье проекта. Такой набор собран **по работоспособности, а +не по направлению**, и цели у него нет не по недосмотру. + +Обходной путь существовал и был хуже прямого: завести цель-пустышку («Здоровье +проекта») и вешать под неё `fix`-и. Тогда `ROADMAP.md` — документ про то, что +приложение умеет, — обрастает строками про то, что оно не ломается, а тег +`goal:` перестаёт значить направление. + +**Р159. Цель у спринта необязательна, но её отсутствие — ответ, а не молчание.** +`sprint start` принимает `--goal <слаг>` **или** `--no-goal`, и голое отсутствие +обоих — отказ с объяснением. Причина в стимуле: цель называет человек, и это +единственный продуктовый вопрос всей сессии. Разреши мы заводить спринт просто +без флага — забытый флаг, лень спросить и осознанное решение стали бы неотличимы +на выходе, а дешевле всего из трёх агенту именно не спрашивать. + +**Р160. В спринте без цели цель не проверяется вовсе.** Набор берёт что угодно +готовое к взятию, включая задачи под разными целями: сверять не с чем. Правило +«набор служит одной цели» не ослаблено, оно просто не применяется — целей в +таком наборе не больше одной, их ноль. Взамен машинной проверки остаётся показ +набора человеку до заморозки: у бесцельного спринта это **единственная** +проверка состава, и в скилле это сказано прямо. + +**Р161. Признак «спринт идёт» — слаг, а не цель.** Прежде код спрашивал цель и +получал заодно ответ про то, открыт ли спринт; теперь эти вопросы разошлись. +Слаг подходит на роль признака лучше цели по существу: он есть у любого спринта, +потому что без него нечем проставить `sprint:<слаг>`, то есть нечем собрать +урожай. Поле «Цель» в шапке остаётся на месте и у бесцельного набора — пишется +прозой без ссылки: **«цели нет» и «цель потерялась» обязаны различаться**. + +## Что из этого следует + +**С145. Необязательное поле, которое всё же решают, заводится парой «значение +или явный отказ».** Умолчанием тут был бы не выбор, а его отсутствие — и +отличить его от забывчивости уже не смог бы никто, включая автора. + +**С146. Признак «сущность существует» нельзя вешать на её необязательное поле.** +Пока цель была обязательной, `sprint_goal()` отвечал сразу на два вопроса, и это +работало ровно до тех пор, пока второй ответ не понадобился отдельно. + +**С147. Снятая проверка называет, что осталось вместо неё.** Цель не проверяется +— значит, за состав отвечают показ человеку и строка доклада; иначе послабление +читается как «здесь можно не думать». diff --git a/decisions/40-three-doc-categories.md b/decisions/40-three-doc-categories.md new file mode 100644 index 0000000..7f02e52 --- /dev/null +++ b/decisions/40-three-doc-categories.md @@ -0,0 +1,60 @@ +# 40. Три категории документов: не всякий документ — тема ревью (2026-08-07) + +Решение 36 объявило: **каждый документ проекта — тема ревью**. Правило дало +открытый список тем и сделало `docs/` конфигурацией конвейера — это работает и +остаётся. Но оно же оказалось неверным ровно наполовину, и потому вредным +целиком. + +Паспорт и схему хранилища ревью читает, но темами они не являются: по ним нельзя +сказать «в этом изменении сделано не так», они задают границу, по которой судит +**чужая** тема. Журнал решений и журнал наблюдений ревью изменения не нужны +вовсе: ADR объясняет прошлое решение, а не предъявляет требование к изменению. + +Ломалось это механически. Разметчик, применявший правило буквально, обязан был +либо завести фантомные темы `passport`, `adr`, `database`, `research` и +продублировать ими работу тем `architecture` и `operations`, либо потерять четыре +документа молча. Обе ветки случались; в собственном образце плана разметчика +`docs/passport.md` не попадал ни строкой, а его же обязательная арифметика +покрытия («документов найдено N, все N разнесены») при этом не сходилась. + +**Р162. Разрез один и проверяемый: можно ли по документу сказать «в этом +изменении сделано не так».** Отсюда три категории. **Тема** — да, прямо +(`conventions`, `security`, `architecture`, свои документы проекта). **Источник +темы** — нет, но он задаёт границу для чужой темы (`passport`, `database`, +`CLAUDE.md`, `openspec/specs/`). **Процессный документ** — нет, он про то, как +мы работаем (`tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json`). + +**Р163. Открыта одна категория из трёх.** `источник` и `процессный` перечислены +поимённо и проектом не пополняются; открыта только `тема`. Прежняя формулировка +«не темы ровно две» противоречила собственной раскладке канона — `.pm.json` был +третьим, и правило-исправление жило в чужом плагине, в коде `docs.py`. Теперь +документ, которого нет в раскладке, — однозначно своя тема проекта, и решать +нечего. + +**Р164. «Не судит по нему» и «не открывает» — разные вещи.** `docs/review.*` +проходы читают на каждом прогоне: там вопросы по темам, журнал дефектов, типовые +узлы, типовые ложноположительные. Это чтение конвейером **своей обвязки**, а не +критерия. `adr/`, `research/` и `tasks/` не открывает никто. + +**Р165. Цена решения записана, а не подразумевается.** Расхождение изменения с +записанным решением прогоном больше не ловится — это работа сверки документации +между спринтами. Измеренные числа проекта из ревью тоже ушли: проход, +опирающийся на число, обязан **снять его сам, на этом прогоне**, и приложить +команду замера. Обе потери идут обязательными строками в границы покрытия +каждого прогона, и пишет их триаж — не проход, потому что проход о том, чего в +конвейере нет, пожаловаться не может. + +## Что из этого следует + +**С148. Плоское правило, верное наполовину, хуже двух правил.** Оно не даёт +половине случаев легального ответа, и исполнитель выбирает между двумя плохими +ветками — фантомной сущностью и молчащей потерей. Заметно это становится не на +определении, а на первом же образце вывода. + +**С149. Открытым делается одно множество, а не все.** Открытый список ценен тем, +что в него попадает незнакомое; если открыты все категории, незнакомое попадает +в произвольную. + +**С150. Отказ читать документ — тоже граница покрытия, и её пишет сток.** Строку +«этого не смотрел никто» некому подать снизу: проход, которого нет, отчёта не +присылает. diff --git a/decisions/41-task-sizing-once.md b/decisions/41-task-sizing-once.md new file mode 100644 index 0000000..ffb03f9 --- /dev/null +++ b/decisions/41-task-sizing-once.md @@ -0,0 +1,40 @@ +# 41. Разметка задачи: одна величина, посчитанная один раз (2026-08-07) + +Разметка была стадией 0 **ревью кода** и платилась на каждом прогоне. Перед ревью +дизайна ту же самую величину — «крупное или незнакомое?» — называл сам пайплайн +задачи, то есть оркестратор, который только что довёл предложение до `propose`. +Одно и то же измерялось дважды, и один из двух раз без разведённости с автором — +ровно в той точке, ради которой разметчик и заведён. + +**Р166. Разметка идёт один раз на задачу, сразу после `propose`.** Её план +обслуживает обе стадии ревью: состав ревью дизайна и таблицу тем для ревью кода. +Диффа она не видит — кода ещё нет; размер оценивается по дельта-спекам и перечню +границ задачи. + +**Р167. Осей две, ступень — максимум по ним.** **Размер** (малое, среднее, +крупное) — про объём; **сложность** (знакомое, незнакомое) — про то, известна ли +форма решения заранее. Раньше обе были склеены в один вопрос «крупное **или** +незнакомое?»: ответ получался тот же, но разметка не могла сказать «среднее, но +совершенно знакомое» — а это и есть рабочее умолчание. + +**Р168. Ступень после кода не пересматривается.** Дифф может выйти крупнее +ожидания — ступень не двинется. Пересмотр означал бы либо второй запуск +разметчика (то, ради устранения чего он и переехал), либо машинный порог, +который на нетипичной задаче срабатывает не туда. Расхождение факта с разметкой +ловит журнал дефектов, постфактум, — так же, как и всякую другую ошибку выбора +ступени. + +**Р169. План на диск не пишется.** Файл-план стал бы четвёртым артефактом рядом +с `proposal.md`, `tasks.md` и `design.md`, пережил бы задачу и разошёлся бы с +ней молча. Прервался пайплайн — разметка повторяется; это самый дешёвый его +проход. + +## Что из этого следует + +**С151. Величина, из которой выводится состав, считается один раз и одним +агентом.** Два места, считающие одно и то же, расходятся; расходятся они молча, +и побеждает то, у которого меньше разведённости с автором. + +**С152. Разведённость — свойство момента, а не роли.** Тот же агент, спрошенный +до написания кода и после, даёт разные ответы; переезд по времени сделал больше, +чем сделал бы любой запрет. diff --git a/decisions/42-quick-cheaper-than-standard.md b/decisions/42-quick-cheaper-than-standard.md new file mode 100644 index 0000000..4d4bcf7 --- /dev/null +++ b/decisions/42-quick-cheaper-than-standard.md @@ -0,0 +1,52 @@ +# 42. `quick` стал дешевле `standard` тремя способами (2026-08-07) + +`quick` и `standard` совпадали составом (шесть проходов) и различались глубиной +трёх тем: сверка против разбора. На практике это означало один проход, задающий +на один вопрос меньше, и потолок 4 вместо 2. Нижняя ступень не экономила почти +ничего и называлась отдельной ступенью зря. + +Отдельно выяснилось, что дешевизна конвейера держалась на двух заявленных +рычагах — узкий вход и потолок находок, — и **оба применялись к одному проходу +из шести**. У `specs` и `code` потолка не было вовсе, а вход `code` включал +чтение дома конвенций «весь и целиком» на каждой задаче. + +**Р170. `quick` теряет приёмник тем.** Темы `security`, `operations` и +`architecture` на этой ступени закрывает `code` сверкой с **записанными +инвариантами** `CLAUDE.md`, потолком 1 находка на все три. Это не «глубина ниже» +— это **другой дом темы**, куда более узкий, и в плане он так и называется. + +**Р171. Приёмник тем запускается тогда и только тогда, когда ему есть что +принимать.** Правило было в `wide` («нет своих тем проекта — не запускается») и +теперь распространено на `quick`. Совпадение неслучайное: темы ядра `basics` +держит ровно на одной ступени из трёх, а приёмником проектных тем работает на +всех. + +**Р172. Вход и потолок применены к каждому проходу с мнением.** На `quick` +`specs` читает только дельта-спеку, `code` — только индекс конвенций. Потолки +напечатаны и раздельны по половинам `code`: 3 технических, 2 конвенционных, 1 по +инвариантам. Раздельность обязательна — конвенционных находок больше по +построению, и в общем списке они вытеснили бы техническую половину, чей пропуск +дороже. + +**Р173. Сработавший потолок объявляется.** Проход, срезавший находки, говорит +строкой, сколько осталось за срезом и какого рода. Молчащий срез неотличим от +«больше не нашлось» — тот же класс молчащего пропуска, против которого написан +весь конвейер. + +**Р174. Отрицательный тест `quick` стал жёстче, а не мягче.** Вопросы «обратима +ли миграция» и «что с записями новой версии после отката» задавал приёмник тем; +на `quick` его нет. Значит изменение, которое не откатывается обратной правкой, +на `quick` не идёт вовсе — каким бы малым оно ни было. + +## Что из этого следует + +**С153. Ступень, не дающая экономии, не нужна.** Две ступени, различающиеся +одним вопросом одного прохода, — это одна ступень с шумом в отчёте. + +**С154. Рычаг, применённый к одному исполнителю, — не рычаг, а исключение.** +Заявленный механизм экономии проверяется перечислением: к кому он применён и к +кому нет. + +**С155. Проход без потолка выдаёт столько находок, сколько нашёл поверхностей.** +Ровно из-за этого был снят проход независимой реализации; тот же механизм +работал у `code` и `specs` и не был замечен, потому что счёт никто не считал. diff --git a/decisions/43-design-review-tiers.md b/decisions/43-design-review-tiers.md new file mode 100644 index 0000000..12557bc --- /dev/null +++ b/decisions/43-design-review-tiers.md @@ -0,0 +1,31 @@ +# 43. Ревью дизайна тоже растёт ступенями (2026-08-07) + +Состав ревью дизайна включался одним условием: `specs` всегда, `rubric` и +`architecture` — вместе, «при крупном или незнакомом». Значит `standard` получал +на предложении ровно один проход, то есть не отличался от `quick` ничем. + +**Р175. Три ступени вместо двух: `quick` — `specs`; `standard` — плюс `rubric`; +`wide` — плюс `architecture` и вопрос автору о трёх формах решения.** + +**Р176. Рубрика съехала вниз, архитектура осталась наверху, и это не +симметричная правка.** Они зарабатывают на разном. Рубрика порождает **свойства +узла** и окупается уже на среднем изменении: её выход уезжает приёмочными +критериями в `tasks.md` и работает потом на всей задаче. Архитектура отвечает на +вопрос «не появился ли второй способ», а он на среднем знакомом изменении +отвечается «нет» ещё до запуска — держать её ниже `wide` значит платить за +предсказуемый ответ на каждой задаче. + +**Р177. Тривиальность задачи больше не решает состав ревью.** Раньше она решала, +звать ли ревью предложения вовсе; теперь глубину обеих стадий называет ступень, +а тривиальная задача просто получает `quick`. «Пропустить ревью дизайна» и +«пройти его одним самым дешёвым проходом» — разные вещи: сверка дельта-спек +стоит меньше, чем разбор того, что она поймала бы на готовом коде. + +## Что из этого следует + +**С156. Проходы, включаемые одним условием, стоит разводить по тому, на чём они +зарабатывают.** Общее условие — признак того, что их не сравнивали между собой, +а не того, что они равноценны. + +**С157. Средняя ступень обязана отличаться от нижней на обеих стадиях.** Иначе +«рабочее умолчание» отличается от исключения только именем. diff --git a/decisions/44-task-label-single-value.md b/decisions/44-task-label-single-value.md new file mode 100644 index 0000000..19c00ec --- /dev/null +++ b/decisions/44-task-label-single-value.md @@ -0,0 +1,50 @@ +# 44. Метка задачи: одно значение, по которому выбираются все ревьюверы (2026-08-07) + +Решения 41–43 развели классификацию на две оси и свели состав обеих стадий ревью +к их максимуму. Значения этого максимума назывались `quick`, `standard`, `wide`, +а сам он — «ступень». Оба имени описывали **ревью**: как глубоко смотрим, на +какой ступеньке идём. Классифицируется же при этом **задача**, и результат +классификации принадлежит ей, а не прогону. + +Расхождение не косметическое. Пока величина называлась свойством ревью, её было +естественно пересчитать на каждом прогоне — что конвейер и делал, пока разметка +не переехала к `propose`. Имя тянуло назад к устройству, из которого её только +что вынули. + +**Р178. Классификация выдаёт задаче метку: `small`, `medium` или `large`.** +Метка принадлежит задаче, ставится один раз при разметке и дальше только +читается. Все проходы обеих стадий получают её в задании и обязаны напечатать в +границах покрытия. + +**Р179. Метка — единственный вход выбора исполнителей.** Ни класс задачи, ни её +тип, ни тривиальность, ни ощущение важности состав больше не определяют. У +конвейера один переключатель, и он напечатан в каждом отчёте. + +**Р180. Слово «ступень» удалено, а не оставлено синонимом.** Два имени одной +вещи расходятся — это ровно решение [темы 37](37-gate-and-autotests-one-name.md) +про тему и проход. Метка ordered: `small` < `medium` < `large`, и там, где нужен +порядок, говорится «младшая» и «старшая метка», а не вводится второе +существительное. + +**Р181. Метка — не синоним размера, и это записано там, где ошибиться легче +всего.** Совпадают они в одном углу таблицы из трёх: малое **незнакомое** +изменение получает `large`, трогая один узел. Поэтому план печатает три строки — +размер, сложность, метка, — каждую со своим обоснованием, и выводить одну из +другой запрещено. Проход, определивший объём диффа по метке, ошибётся именно на +том случае, ради которого верхняя метка и заведена. + +## Что из этого следует + +**С158. Имя величины должно называть её носителя, а не потребителя.** «Ступень +ревью» звала пересчитывать себя на каждом прогоне ревью; «метка задачи» +считается там же, где живёт задача. + +**С159. Переключатель состава должен быть один и печатный.** Пока их два — +тривиальность и ступень, — состав выводится из пересечения, а пересечение нигде +не напечатано целиком. + +**С160. Русские слова для осей, английские для значения.** Оси — суждение и +читаются прозой (`малое`, `знакомое`); метка — идентификатор, который проходы +сравнивают, и потому она английская. Тот же разрез, что «имена файлов +английские, текст русский» в каноне, и он же снимает путаницу «крупное» против +`large`. diff --git a/decisions/45-label-corrector-small-share.md b/decisions/45-label-corrector-small-share.md new file mode 100644 index 0000000..dd211ca --- /dev/null +++ b/decisions/45-label-corrector-small-share.md @@ -0,0 +1,63 @@ +# 45. Корректор метки, доля `small` и корпус оценки (2026-08-07) + +Три правки по следам тем +[41](41-task-sizing-once.md)–[44](44-task-label-single-value.md), и все три +закрывают дыры, которые эти решения и открыли. + +**Р182. Сигнал о заниженной метке переехал в `review-code`.** Он жил в +`review-basics` — единственном месте. А `basics` с меткой `small` не +запускается, если у проекта нет своих тем: значит на типичном проекте задача с +меткой `small` шла **без рантайм-проверки** того, что метка верна. Дыра +появилась ровно вместе с удешевлением `small` и попала в самую вероятную точку +ошибки: занижают туда, где дешевле, а цена занижения там же и выросла — три темы +ядра смотрятся только против инвариантов. + +`code` подходит по построению: он идёт при **любой** метке, видит дифф целиком, а +на `small` уже читает инварианты — то есть держит в руках весь материал, из +которого сигнал выводится. У `basics` сигнал остаётся вторым, подтверждающим: он +смотрит оптикой тем и видит то, чего не видно из кода как кода, — что вопросов, +отложенных до `large`, накопилось слишком много. Триаж теперь обязан сказать и +когда сигнала **нет**: «корректор отработал, возражений нет» и «корректор не +запускался» по молчанию неразличимы. + +**Р183. У `small` появилась доля, и она сформулирована сравнением, а не +числом.** `small` не должен обгонять `medium`; ориентир — до трети задач. +Проверка нужна именно теперь: пока `quick` и `standard` совпадали составом, +дрейф между ними не стоил ничего, и её не было. Сейчас он стоит трёх тем ядра. У +дрейфа вниз есть стимул, и он назван: метку выбирает не автор, но по описанию, +написанному автором, — занижённое описание даёт занижённую метку без чьего-либо +умысла. + +**Р184. Размер оценивается по корпусу из пяти источников, а не по +дельта-спекам.** Разметчик читал `proposal.md` и `tasks.md`, но `design.md` не +открывал вовсе, а метод был описан одной фразой «размер считается по +дельта-спекам». Дельты описывают заказанное **поведение** и молчат об объёме +работы: шесть шагов в двух узлах видны в `tasks.md`, а факт, что форму решения +выбирали из нескольких, — только в `design.md`. Каждый источник получил свою +строку по каждой оси, и каждая цифра в обосновании обязана быть привязана к +источнику поимённо. + +Отсюда два правила, которых раньше не было. **Расхождение источников по объёму +разрешается в пользу большего** — и это не «спорное решается вниз»: то правило +разрешает ничью при равных данных, а здесь один источник просто видел больше. +**Само расхождение — довод за `незнакомое`:** если о задаче написано так, что +источники не сходятся в объёме, форму решения по ней не знают. Отсутствие +`design.md` у нетривиальной задачи читается так же — «форму знали заранее» ничем +не подтверждено. + +## Что из этого следует + +**С161. Корректор обязан идти чаще, чем корректируемое.** Проверяющий, который +запускается реже проверяемого, оставляет дыру именно там, где выбор был самым +дешёвым, — то есть там, где ошибаются. + +**С162. Отсутствие сигнала — тоже сигнал, и его надо печатать.** Молчание +корректора неотличимо от его отсутствия, а решения по ним разные. + +**С163. Проверка доли формулируется сравнением, а не порогом.** «Меньше, чем +`medium`» считается по любому журналу и не требует спорить о числе; порог «не +больше 30%» спорен ровно настолько, насколько несопоставимы задачи. + +**С164. Оценка по одному источнику — оценка по остатку.** Источники о задаче +отвечают на разные вопросы; пропущенный не ухудшает точность понемногу, а +оставляет ось без данных. diff --git a/decisions/46-label-rule-own-document.md b/decisions/46-label-rule-own-document.md new file mode 100644 index 0000000..5bcc082 --- /dev/null +++ b/decisions/46-label-rule-own-document.md @@ -0,0 +1,38 @@ +# 46. Правило выбора метки съехало из скилла в отдельный документ (2026-08-07) + +**Р185. У правила выбора метки теперь свой дом — `references/review-levels.md`, +а в скилле остался диспетчер.** `SKILL.md` конвейера дорос до 1168 строк, и +двести с лишним из них отвечали на вопрос, который на обычной задаче не задаётся +вовсе: **как** выбирается метка. Метку называет `review-scope` один раз, до +обеих стадий; всем остальным нужна не она, а состав по уже названной метке — три +строки таблицы. Переехали правило двух осей, «спорное решается вниз», «максимум +по поверхности», разбор того, чем `small` дешевле `medium`, и обе проверки +долей. Остались таблица состава, схема процесса и раздача тем. + +**Форма выбрана одна на все метки, а не по документу на метку.** Предлагался +разрез по образцу типов задач в `av-dev-pm:tasks`, где у `fix`, `feature` и +`chore` по своему файлу. Аналогия не переносится, и по двум причинам. Типы задач +**разъединены** — общее вынесено в `task-format.md`, а в файле типа лежит только +своё; метки же **вложены**: `medium` это `small` плюс два прохода, `large` — +`medium` плюс доказательство. Три файла повторяли бы костяк трижды, а `copies.py` +такое не ловит: он сверяет дословные копии по маркерам, тогда как здесь вышли бы +почти-копии с намеренными мелкими отличиями — расхождение, неотличимое от +задуманного. Вторая причина сильнее первой: ценность этого текста **в +сравнении**. Читателю нужно не «что делает `small`», а «чем `small` отличается от +`medium`» — на этот вопрос отвечают и выбор метки, и «спорное вниз», и корректор. +Сравнение, разложенное по трём файлам, не читается. + +**Механика рычагов осталась в скилле, а не уехала с меткой.** Непуск, вход и +потолок общие для всех проходов и всех меток, их дом — раздел «Модель по +проходу». В переехавшем тексте от них только то, что они делают с `small`, и +ссылка на дом; точные потолки не продублированы. + +## Что из этого следует + +**С165. Дом правила — там, где правило выбирают, а не там, где его применяют.** +Применяют состав на каждой задаче, выбирают метку один раз; текст, обслуживающий +выбор, в потоке применения лежит мёртвым грузом. + +**С166. Вложенные вещи не режутся по файлу на вещь.** Разъединённое (типы задач) +режется, вложенное (метки) — нет: разрез вложенного даёт дублирование общей +части, а дублирование намеренно неточное машина не сверит. diff --git a/decisions/47-openspec-setup-skill.md b/decisions/47-openspec-setup-skill.md new file mode 100644 index 0000000..c3cee4e --- /dev/null +++ b/decisions/47-openspec-setup-skill.md @@ -0,0 +1,51 @@ +# 47. OpenSpec заводится скиллом, а его конфиг — часть канона (2026-08-07) + +**Р186. `init` заводит OpenSpec сам, а не оставляет это человеку.** Каталог +`openspec/` был предпосылкой, о которой канон говорил, но за которой не следил: +`openspec/specs/` объявлен домом темы `requirements`, `config.yaml` описан +абзацем — а заводилось всё руками, и не проверялось ничего. Новый проект выходил +из `init` с полным каноном документов и без каталога, без которого не работают +ни `opsx:propose`, ни ревью дизайна, ни сверка требований. Команда названа +поимённо (`openspec init --tools claude`) в трёх местах — скилле, каноне и +отказе скрипта: отказ без команды заставляет искать её в другом месте. + +**Р187. Файл из коробки хуже отсутствующего, и потому проверяется машиной.** +`openspec init` кладёт `config.yaml`, где `context` и `rules` — +закомментированный пример на английском. Такой файл читается как настроенный: он +есть, он валиден, имя правильное. Работает он как пустой, и узнаётся это по уже +написанному предложению — на другом языке, с capability по имени пакета, без +единого `SHALL`. `docs.py` проверяет четыре вещи, и каждая про молчащий пробел: +каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об +этом не сообщает); `context` и `rules.specs` не остались примером, а правила +называют `SHALL`; `context` называет `passport` и `CLAUDE.md`. + +**Р188. Форма конфига — маршрутизатор, и это разрез, а не пожелание.** +Утверждение, которое можно опровергнуть, открыв другой файл проекта, — пересказ; +строка, которая говорит, какой файл открыть, — ссылка. `context` читается при +порождении **каждого** артефакта, туда удобно дописать «чтобы агент знал», и +именно поэтому в нём заводятся вторые дома инвариантов, конвенций, гейта и +правил ревью. Машина этот разрез не проверяет — отличить ссылку от пересказа она +не умеет, — и он отдан `doc-consistency` отдельным абзацем правила «один факт — +один дом», с `config.yaml`, добавленным ему во вход. + +**Обязательными сделаны ровно два адреса — паспорт и `CLAUDE.md`.** Причина в +порядке работы: предложение пишется **до** того, как кто-либо откроет `docs/`, и +без этих двух строк его пишут, не зная ни границы домена, ни инвариантов. +Длинный список адресов превратил бы `context` во второй дом ровно тем способом, +против которого правило и заведено. + +**Образец конфига лёг в канон, а не в конвейер**, как планировалось решением +[Р3](01-openspec-status.md). Форма документа принадлежит тому, кто владеет +каноном документов; конвейер её читатель. Иначе `av-dev-pipeline` завёл бы у +себя описание файла, который заводит и проверяет `av-dev-pm`, — тот же шов, что +разбирали, убирая имена проходов из канона. + +## Что из этого следует + +**С167. Предпосылка, за которой никто не следит, — не предпосылка, а +пожелание.** Если условие названо обязательным, его должен кто-то заводить и +кто-то проверять; иначе оно живёт ровно до первого проекта, где о нём забыли. + +**С168. Заполненная форма и заполненный смысл — разные вещи, и первая маскирует +вторую.** Файл на месте, валиден, с правильным именем — и пуст по существу: это +худший вид пробела, потому что выглядит он как его отсутствие. diff --git a/decisions/48-foreign-tool-form-by-query.md b/decisions/48-foreign-tool-form-by-query.md new file mode 100644 index 0000000..49ae754 --- /dev/null +++ b/decisions/48-foreign-tool-form-by-query.md @@ -0,0 +1,43 @@ +# 48. Форма чужого инструмента держится опросом инструмента, а не памятью (2026-08-07) + +**Р189. Схема и артефакты OpenSpec записаны в скрипте как слепок, и у слепка +есть сторож.** Проверка формы `config.yaml` знает имя схемы и перечень +артефактов (`proposal`, `specs`, `design`, `tasks`). Это не наше решение, а +состояние чужого инструмента: OpenSpec переименует артефакт — правила под +прежним именем перестанут применяться, конфиг останется выглядеть написанным, а +канон продолжит требовать прежнее. Все три стороны при этом молчат. + +Сторожем сделано сравнение версий: `check` спрашивает `openspec --version` +(десятые доли секунды) и сравнивает `major.minor` с той, на которой форма +сверялась. Разошлось — **замечание**, не отказ, с именем команды, которая +перепроверяет. Патч-версия в сравнение не берётся намеренно: формы она не +меняет, а нагоняй на каждый багфикс приучает пролистывать весь блок. + +**Р190. Перепроверка спрашивает инструмент, а не нас.** `docs.py openspec-form` +берёт `openspec templates --json` — перечень артефактов текущей схемы — и +печатает, что разошлось с константами. Дорогой вызов вынесен из `check` +сознательно: он стоит втрое дороже опроса версии, а ответ меняется только вместе +с версией. Дешёвая проверка служит **воротами** дорогой, и дорогая не ржавеет, +потому что зовут её не по памяти. + +**Чинится расхождение в плагине, а не в проекте, и это сказано в трёх местах.** +Проект в такой ситуации не виноват и починить ничего не может: у него нет ни +констант скрипта, ни скелета, ни журнала версий канона. Замечание поэтому +адресовано владельцу плагина, а команда печатает три адреса правки списком. + +**Заодно поймано ложное срабатывание на живом конфиге.** Первый вариант искал +ключи `rules` отступом по всему файлу и нашёл их внутри литерального блока +`context: |`: строки «Language: Russian» и «av-dev-pm:review-pipeline» выглядят +ключами. Проверка теперь идёт от строки `rules:` до следующего ключа нулевой +колонки. Правило, краснеющее на правде, хуже отсутствующего — его перестают +читать целиком. + +## Что из этого следует + +**С169. Знание о чужом инструменте, записанное у себя, — это слепок с датой.** +Он законен, пока рядом стоит тот, кто заметит, что дата протухла; без сторожа он +превращается в уверенное враньё. + +**С170. Дешёвая проверка как ворота дорогой.** Опрос версии стоит копейки и +точно говорит, могла ли измениться дорогая величина. Так дорогая проверка +остаётся редкой и при этом не забытой. diff --git a/decisions/49-shared-rule-home-outside-plugin.md b/decisions/49-shared-rule-home-outside-plugin.md new file mode 100644 index 0000000..7179470 --- /dev/null +++ b/decisions/49-shared-rule-home-outside-plugin.md @@ -0,0 +1,38 @@ +# 49. Дом общего правила вышел из плагина (2026-08-09) + +**Р191. Язык уехал в `shared/`, потому что общее правило не может принадлежать +половине.** Дом языка лежал в `av-dev-pm/skills/canon/references/language.md` — +внутри одного скилла одного плагина. Пока плагин был один, это читалось как «дом +рядом с главным потребителем». Разделение на самодостаточные `docs` и `tasks` +превращает то же место в утверждение, что язык принадлежит канону: плагин задач, +поставленный без канона, потерял бы правила письма вместе с ним. Дом переехал в +`shared/` и не принадлежит ни одному плагину, а плагины везут дословные копии. +Самодостаточность держится **копией, а не ссылкой**: `shared/` нужен этому +репозиторию, а не установленному плагину. + +**Р192. Устав вычитки стал копией целиком, а не четырьмя таблицами из десяти.** +`doc-wording` копировал из дома англицизмы, словарь, жаргон и порог правки — +четыре блока; девять правил он излагал своими словами, и эти слова с домом никто +не сверял. Там дрейф и копился молча: в доме правило «одна мысль — одно +предложение» требовало выносить придаточное, в уставе — не резать причинную +связь, и каждая версия выглядела полной. Теперь блок один, `язык-правила`, и +берётся он целиком. Условие переезда: текст правил написан безлично, а всё, +обращённое к проходу («пиши так-то», «про это молчи»), вынесено из блока в свой +раздел устава. **Правило принадлежит дому, способ доложить о нём — уставу.** + +**Р193. `порог-правки` остался отдельным блоком, и это следствие разметки, а не +вкуса.** Его берёт `task-form`, который правил языка не проверяет вовсе. +Вложенных блоков `copies.py` не знает — лежи порог внутри `язык-правила`, +забрать его отдельно было бы нечем, и `task-form` вёз бы весь устав чужого +прохода. Разрез домов идёт **по потребителю, а не по теме**: три блока вместо +одного стоят двух лишних маркеров и снимают ложную зависимость. + +## Что из этого следует + +**С171. Общее правило не хранится внутри одного из тех, кто им пользуется.** +Пока пользователь один, дом рядом с ним выглядит удобством; со вторым +пользователем то же место начинает утверждать, что правило принадлежит первому. + +**С172. Пересказ своими словами — это копия, которую никто не сверяет.** Блок, +взятый целиком, читается дороже, но расхождение в нём ловит машина; сокращённое +изложение экономит строки и платит молчаливым дрейфом. diff --git a/decisions/50-wording-split-by-plugin.md b/decisions/50-wording-split-by-plugin.md new file mode 100644 index 0000000..8236473 --- /dev/null +++ b/decisions/50-wording-split-by-plugin.md @@ -0,0 +1,39 @@ +# 50. Вычитка раздвоилась по плагину, а не по правилу (2026-08-09) + +**Р194. Решение ППП отменено, и отменено не по своей оси.** ППП говорило: агент +называется `doc-wording`, а не `task-wording`, потому что правила языка +относятся ко всем проектным текстам — документам канона, решениям ADR, запискам +разведки, — а не к одним задачам. Утверждение верно и сегодня; оно и есть +причина, по которой правила уехали в `shared/`. Но из общности **правила** не +следует общность **прохода**: `docs` и `tasks` расходятся самодостаточными +плагинами, а самодостаточный плагин не может зависеть от агента соседа. Проходов +теперь два, `doc-wording` и `task-wording`, и разведены они **по охвату** — +впервые в этом репозитории: и `task-form` против вычитки, и `doc-consistency` +против `doc-code-drift` разведены по глубине. + +**Р195. Разрез по охвату дублирует устав, и потому весь общий текст стал +домом.** Два прохода судят по одним и тем же девяти правилам; отличаются они +входом, соседями по границе и тем, чем подставляется находка — командой `edit` у +задач, редактором у документов. Написать уставы порознь значило бы завести ровно +тот дрейф, который днём раньше нашёлся внутри самого `doc-wording`. Общими +домами стали `язык-правила`, `порог-правки` и новый `вычитка-доклад` — форма +находки и границы покрытия. Копий в каждом уставе 151 строка, своего непустого +текста — 61 у `doc-wording` и 75 у `task-wording`, и это ровно то, чем проходы +отличаются: вход, соседи, машинная проверка, способ подстановки. + +**Р196. `вычитка-доклад` — контракт прохода, а не правило языка, и лежит он всё +равно в `shared/language.md`.** Заводить под пятнадцать строк отдельный файл +дороже, чем назвать раздел честно. Признак дома здесь не тема, а **число +потребителей больше одного при обязательной дословности**: разойдись два прохода +формой доклада, зовущий скилл разбирал бы два формата вместо одного. + +## Что из этого следует + +**С173. Общность правила и общность исполнителя — разные оси.** Правило бывает +одно на всех и при этом требует по исполнителю на упаковку: правило принадлежит +предметной области, исполнитель — тому, кто его поставляет. + +**С174. Разрез по охвату обязан быть оплачен домом.** Разделение по глубине даёт +два разных текста и держится само; разделение по охвату даёт два одинаковых, и +без помеченной копии они разъезжаются — тем вернее, что каждый по отдельности +выглядит осмысленным. diff --git a/decisions/51-av-dev-pm-split.md b/decisions/51-av-dev-pm-split.md new file mode 100644 index 0000000..79feb8e --- /dev/null +++ b/decisions/51-av-dev-pm-split.md @@ -0,0 +1,40 @@ +# 51. av-dev-pm расколот: владение пошло по тому, что ставится порознь (2026-08-09) + +**Р197. Один плагин владел двумя вещами, и это мешало обеим.** `av-dev-pm` +держал документацию проекта и учёт работ. Пока владелец был один, сцепки +выглядели удобством: `docs.py` требовал `docs/tasks/` и звал внутрь `tasks.py` +подпроцессом, настройки задач лежали ключом в `docs/.pm.json`, язык проектных +текстов — внутри скилла `canon`. Каждая из трёх на расколе оказалась не +удобством, а утверждением, что половина принадлежит другой половине. Теперь +плагина два, `av-dev-docs` и `av-dev-tasks`, и каждый ставится сам по себе. + +**Р198. Самодостаточность держится копией, а не ссылкой.** Ссылка в дерево +соседнего плагина работает ровно до того момента, когда сосед не установлен, — а +это и есть тот случай, ради которого раскол делался. Поэтому все относительные +ссылки, пересекшие границу, сняты: вместо них имя скилла через пространство имён +и оговорка, что вызов может не разрешиться. То, что нужно обоим **дословно**, +стало общим домом в `shared/`: язык проектных текстов и словарь «Сопровождение и +эксплуатация». Второй выбран не по теме, а по числу владельцев — его делят +роадмап, `architecture.md` и тема ревью `operations`, то есть три плагина, и ни +один им не владеет. Три перечня «чем держат проект» уже разъезжались молча. + +**Р199. OpenSpec отдан тому, кто им работает, а не тому, кто о нём написал.** +Версия 7 канона объявила `openspec/` своим слотом, и разрез вышел не по +владению: без каталога не запускается конвейер, а не канон. Заведение и форма +файла уехали в скилл `av-dev-pipeline:openspec`, отсутствие каталога стало для +`docs.py` неприменимостью вместо отказа. **Остаток назван, а не замолчан:** +проверка формы и сторож версии пока остались в скрипте канона, потому что своего +скрипта у конвейера нет ни одного, — то есть у файла сейчас два плагина, один +заводит, другой проверяет. Это записано и в журнале версий как временное +состояние. + +## Что из этого следует + +**С175. Сцепка внутри одного владельца не видна, пока владелец один.** Она +выглядит удобством ровно до раскола и обнаруживается не рассуждением, а попыткой +поставить половину отдельно. Отсюда и порядок работ: сперва разнести, потом +чинить то, что перестало сходиться. + +**С176. Разрез владения идёт по тому, кто инструментом пользуется, а не по тому, +кто о нём написал.** Канон описывал OpenSpec подробнее всех и потому казался его +владельцем; работает по нему конвейер, и слот принадлежит конвейеру. diff --git a/decisions/52-validator-follows-file.md b/decisions/52-validator-follows-file.md new file mode 100644 index 0000000..ec94965 --- /dev/null +++ b/decisions/52-validator-follows-file.md @@ -0,0 +1,25 @@ +# 52. Валидатор поехал за файлом: у конвейера появился свой скрипт (2026-08-09) + +**Р200. Названный остаток закрыт, и закрыт он ценой первого скрипта в +конвейере.** Решение 51 отдало OpenSpec конвейеру и честно оставило хвост: +проверка формы `config.yaml` и сторож версии остались в `docs.py`, потому что +своего скрипта у пайплайна не было ни одного. Хвост оказался не косметическим — +это ровно то состояние, против которого написан весь канон: **у файла два +владельца, один заводит, другой проверяет**, и разойтись они могут молча. 252 +строки переехали в `av-dev-pipeline/skills/openspec/scripts/openspec.py`; в +`docs.py` от темы не осталось ни константы. + +**Переезд оплатился сразу, и не тем, чего ждали.** Прежняя проверка требовала, +чтобы `context` называл `docs/passport.md` и `CLAUDE.md`, **безусловно** — то есть +на проекте без канона документов требовала ссылку на несуществующий файл. Пока +проверка жила в скрипте канона, допущение «канон есть» было незаметным: скрипт +канона запускают там, где канон есть. В скрипте конвейера то же допущение стало +видно на первом же прогоне. Теперь адрес требуется только к документу, который в +проекте есть, а его отсутствие идёт строкой «не проверялось» с названной ценой. + +## Что из этого следует + +**С177. Неявное допущение видно из другого дома, а не изнутри своего.** «Канон +есть» было верно всюду, где код лежал, и потому не читалось как допущение вовсе. +Переезд — самый дешёвый способ его обнаружить: не разбор, а смена места, из +которого на код смотрят. diff --git a/decisions/53-canon-and-docs-two-skills.md b/decisions/53-canon-and-docs-two-skills.md new file mode 100644 index 0000000..f230570 --- /dev/null +++ b/decisions/53-canon-and-docs-two-skills.md @@ -0,0 +1,24 @@ +# 53. `canon` и `docs` остаются двумя скиллами (2026-08-09) + +**Р201. Слияние отклонено, и довод у него не про объём.** Оба скилла лежат в +одном плагине, и слить их казалось естественным завершением раскола. Мешает +`description`: это не аннотация, а **триггер** — по нему загрузчик решает, звать +ли скилл вообще, и ровно ради его сохранности заведён `frontmatter.py`. Моменты +вызова у этих двух разные. `canon` срабатывает на «проверь документацию», +«переведи на канон», «пришёл в старый проект»; `docs` — на «задача сделана, +обнови документацию», «заведи ADR», «запиши наблюдение». Одно описание покрывает +оба хуже, чем два покрывают каждое своё, и потеря здесь не в читаемости, а в +том, что скилл перестаёт находиться. + +Второй довод — тот же разрез, что репозиторий подтверждал уже трижды: +**раскладка против содержимого**, «где лежит» против «что внутри». Он по +глубине, а такой разрез, в отличие от разреза по охвату ([тема +50](50-wording-split-by-plugin.md)), даёт два разных текста и держится сам, без +помеченных копий. + +## Что из этого следует + +**С178. Границу между скиллами держит не тема, а момент вызова.** Два текста об +одном предмете живут порознь законно, если зовут их в разные минуты; и наоборот +— один предмет, разрезанный так, что оба куска нужны одновременно, разрезан +неверно. diff --git a/decisions/54-plugin-seam-rule-home.md b/decisions/54-plugin-seam-rule-home.md new file mode 100644 index 0000000..9cd84d4 --- /dev/null +++ b/decisions/54-plugin-seam-rule-home.md @@ -0,0 +1,96 @@ +# 54. Стык плагинов: правило получило дом, адреса остались у владельцев (2026-08-09) + +**Р202. Вопрос пришёл с другой стороны: ревью опирается на документы проекта, но +не должно жёстко предполагать, где файл лежит; напрашивалось оглавление адресов +и сводка возможностей скиллов в `CLAUDE.md` проекта.** Отклонено и то и другое, +но не потому, что проблемы нет. + +**Оглавление адресов — второй дом раскладки.** Канон жёсток намеренно: пути +фиксированы, проект подгоняется под них, и цена этого записана в самом каноне. +Указатель в `CLAUDE.md` отменяет ровно эту цену — раскладка получает второе +описание, и разойдутся они молча. Здесь молчание особенно дорогое: прогон ревью +умеет **честно деградировать**, и протухший адрес попадает прямо в эту машинерию — +файл не открылся, в границах покрытия появляется строка «документа в проекте +нет», и отчёт выглядит добросовестным. Прямой путь в той же ситуации ломается +громче. + +**Сводка возможностей — второй дом описаний.** `description` во фронтматтере это +триггер, по нему скилл и выбирается; переписанная руками сводка тех же описаний +не сверяется ничем. + +**Настоящий пробел был в другом, и он измерен.** Правило обращения к соседнему +плагину стояло в пяти местах в пяти редакциях: + +| Где стояло | Довод | Ветка «не разрешился» | +| --- | --- | --- | +| `task-pipeline` | устаревшая проектная копия | нет | +| `task-batch` | то же | нет | +| `review-pipeline` | вшито в пункт про удаление проектных копий | нет | +| `openspec` | путём в чужое дерево — никогда | есть | +| `canon` | — | есть | + +Два разных довода, и ни в одном месте не было обоих; три места из пяти молчали о +том, что делать при неразрешившемся вызове, — то есть о единственном, ради чего +правило написано. Плюс невысказанный инвариант: `$CLAUDE_PLUGIN_ROOT` ведёт +только в свой плагин, употреблён двадцать раз и нигде не оговорён — а именно он +соблазняет дописать `/../av-dev-docs/`. + +**Сделано:** дом `shared/plugin-boundary.md`, блок `граница-плагинов`, семь +помеченных копий — четыре скилла конвейера и три скилла канона. В дом вошли +полное имя, запрет пути в чужое дерево, ветка «не разрешился» с обязанностью +доклада и признак присутствия по заведённому соседом файлу. + +**Разрез, по которому дом наполнялся: правило общее, последствие местное.** «Нет +`av-dev-tasks` — учёт остаётся владельцу» знает только конвейер; «нет конвейера — +`docs.py` о каталоге `openspec/` молчит» знает только канон. Держи дом +последствия — он знал бы наперечёт всех своих потребителей и стал бы вторым +каноном. + +**Адреса при этом наружу не поехали.** У них владелец есть: раскладку `docs/` +держит канон, каталог задач — плагин задач. `shared/` заводится **только для +фактов без владельца**; чужое с владельцем остаётся дома, а сходимость упоминаний +в чужих деревьях проверяет машина — `scripts/addresses.py`, тем же заходом. + +**Что выяснилось при написании чекера: судить незнакомое нельзя.** Первый прогон +дал шесть находок, и три из них были не дрейфом, а свойством канона: `docs/**` — +шаблон, а `docs/accessibility.md` в двух местах — пример **своей темы проекта**, +которую канон разрешает заводить произвольно. Список тем открытый, значит +незнакомое имя опровергнуть нечем, и проверка «есть ли такой документ у +владельца» ловила бы законное. Переименование при этом ловится точно и по другому +основанию: канон, убирая слот, кладёт его в карту переездов `RETIRED` — она и +есть перечень запрещённого. Рядом одна догадка: имя, почти совпавшее с +каноническим, читается как опечатка. Порог замерен по репозиторию — законные +имена дают до 0.64, опечатки от 0.91, и между ними пусто. + +Четвёртая находка оказалась настоящей: `REMAINING.md` иллюстрировал смысловой +дубль адресом `docs/specs/recognition.md` — слотом, упразднённым в версии 1 +канона, то есть при десяти нынешних. +Пример, который сам протух, — ровно то, ради чего чекер и писался. + +## Что из этого следует + +**С179. Механизм честной деградации превращает протухший адрес в правдоподобный +доклад.** Там, где отсутствие источника — законный исход с названной ценой, +ошибка адреса неотличима от этого исхода. Значит адрес в таком месте обязан +сверяться машиной, а не аккуратностью: единственная альтернатива — ломаться +громко, а именно её деградация и убирает. + +**С180. `shared/` — для фактов без владельца, и только.** У адресов владелец +есть, и вынести их наружу значило бы отобрать у него его же предмет. Признак +верного дома не «нужно нескольким», а «никому из них не принадлежит». + +**С181. Общее правило и его последствия живут порознь.** Правило можно вынести в +дом, последствие — нет: оно знает про место, а место про правило знать не +обязано. Дом, вобравший последствия, становится реестром потребителей и +устаревает быстрее их всех. + +**С182. Проверять надо запрещённое, а не незнакомое, когда словарь открыт.** +Открытый список делает «нет такого имени» неопровержимым, и проверка на +принадлежность перечню начинает ловить законное. Ловится ровно то, что владелец +объявил упразднённым: карта переездов — не побочный артефакт миграции, а +перечень запрещённого, и стоит она ровно там, где нужна. + +**С183. Замер порога записывается рядом с порогом.** Число, выбранное на глаз, +через месяц неотличимо от подогнанного под один случай. Обе стороны разрыва +названы (0.64 и 0.91) — и видно не только, что порог верен, но и насколько он не +на грани. diff --git a/decisions/55-task-pipeline-becomes-resolve.md b/decisions/55-task-pipeline-becomes-resolve.md new file mode 100644 index 0000000..7aa4467 --- /dev/null +++ b/decisions/55-task-pipeline-becomes-resolve.md @@ -0,0 +1,71 @@ +# 55. `task-pipeline` стал `resolve`: два плановых стопа вместо полной автономии (2026-08-09) + +**Р203. Автоматическое решение задач агентом признано утопией — «работает, но +работает плохо», — и хуже того, автор перестал ориентироваться в собственном +процессе.** Отсюда разворот: задачи решаются по одной, а в цикл возвращается +человек. `task-batch` удалён целиком; `task-pipeline` переписан в `resolve`. + +**Прежняя доктрина звучала «умолчание — делать, а не спрашивать», и она не +отменена, а ограничена.** Полностью автономный прогон плох не тем, что ошибается, +а тем, что ошибку видно на готовом коде: развилка, стоившая бы абзаца до +`propose`, стоит переписывания после `apply`. Постоянное же согласование +возвращает ту цену, ради ухода от которой пайплайн и писался. Разрез поэтому по +**месту**, а не по важности решения: развилка, найденная до ближайшего чекпоинта, +копится в него; найденная после последнего — по-прежнему уходит вопросом в запись, +и задача доводится в объявленных границах. + +**Чекпоинтов два, и второй обязателен всегда.** + +- **«варианты»** — у исследовательской задачи, до первого требования. Признак + ветки не объём работы, а **отсутствие одного очевидного способа решения**: + обсуждать варианты после `propose` поздно, предложение уже воплотило один из + них, и разговор пойдёт не о выборе, а о переделке. Форма ограничена сверху — + 2–4 варианта: больше четырёх человек не сравнивает, а признаёт неспособность + сравнить и просит рекомендацию. +- **«объяснение»** — у всякой задачи, **после** ревью дизайна. Порядок обоснован: + человек читает то, что уже просеяла машина, и не тратит внимание на выловимое + `review-specs`. Внимание здесь самый дорогой ресурс процесса. + +**Объяснение не стало новым артефактом, и это главная правка первоначального +замысла.** Задумывалось отдельным разделом в `design.md`; при разборе оказалось, +что оно там было бы **третьим домом** одного и того же: в `proposal.md` уже есть +`## Why` («в чём проблема»), в `design.md` — рассмотренные варианты. Поэтому +объяснение **собирается из двух существующих артефактов**, а требование к их +форме уехало в `openspec/config.yaml` — `rules.proposal` и `rules.design`. Это +единственное место, применяющееся **в момент написания**, а не после. +Побочная выгода: `design.md` с названными причинами отказа — половина будущего +ADR, а промоут ADR читает именно архивный `design.md`. + +**Закрыт вопрос, висевший в плане открытым: что делает автоматический участок, +когда ревью кода спорит с одобренным дизайном.** Признак проверяемый — +**меняются ли дельта-спеки**. Не меняются: находка внутри дизайна, дожимается +сама. Меняются: решение стало другим, а одобрено было прежнее — разметка +пересчитывается (правило уже было) и **чекпоинт повторяется**. Чекпоинт, который +можно обойти находкой ревью, не значит ничего, и хуже того — человек уверен, что +одобрил именно то, что уехало в коммит. + +**Удаление `task-batch` обошлось дороже своего каталога.** На нём держались: +третий режим `review-specs` (стык после слияния) вместе с исключением «живого +change нет — берём источником актуальные спеки»; единственное исключение из +правила `review-triage` «плана нет — не запускаюсь»; и обоснование имени основной +ветки в каноне — «в неё вливает батч». Первые два — послабления, существовавшие +только ради батча, и с ним они исчезли, сделав оба правила строже. + +## Что из этого следует + +**С184. Автономность ограничивается местом, а не важностью решения.** +«Спрашивать о важном» неисполнимо: важность оценивает тот же, кто хочет +закончить. «Копить до ближайшего планового стопа» проверяемо и не требует +суждения. + +**С185. Чекпоинт ставится после машинной проверки, а не до неё.** Внимание +человека тратится только на то, чего машина не ловит; порядок наоборот сжигает +его на выловимом и обесценивает саму остановку. + +**С186. Объяснение для человека не заводит своего артефакта.** Если оно +собирается из уже существующих, оно не может с ними разойтись; отдельный текст +«то же, но понятнее» — третий дом, и расходится он молча. + +**С187. Послабление, введённое ради одного потребителя, уходит вместе с ним.** +Исключение переживает своего заказчика и выглядит общим правилом; удаляя +потребителя, ищи его исключения — они и есть настоящий хвост. diff --git a/decisions/56-plugin-and-skill-renames.md b/decisions/56-plugin-and-skill-renames.md new file mode 100644 index 0000000..d359c60 --- /dev/null +++ b/decisions/56-plugin-and-skill-renames.md @@ -0,0 +1,36 @@ +# 56. `av-dev-pipeline` → `av-dev-code`, `review-pipeline` → `review` (2026-08-09) + +**Р204. Имя описывало устройство, а не предмет.** «Пайплайн» говорит, что внутри +конвейер, — а плагин занят кодом по задачам, и после появления чекпоинтов он уже +не конвейер в чистом виде: между остановками автоматика, на остановках разговор. + +**Набор имён стал параллельным, и это довод сам по себе:** `docs` / `tasks` / +`code` / `git` — каждое называет **материал**, которым плагин занят. Прежнее имя +выбивалось: три существительных и одна метафора устройства. По той же причине +отвергнут `av-dev-solve` — глагол в ряду существительных, плюс заикание в главном +вызове (`solve:resolve`), — и `av-dev-work`: «работы» в этом репозитории уже +значат конкретное (цели и задачи роадмапа, секция «Сопровождение»), и имя начало +бы спорить со словарём. + +Заодно `review-pipeline` стал `review`: слово «пайплайн» ушло из плагина целиком, +а не наполовину, и скиллы выровнялись — `resolve` / `review` / `openspec`. + +**Журнал версий канона переписан вместе со всеми, и это не нарушение правила «не +переписываем задним числом».** Разрез проходит не по типу файла, а по типу +высказывания. Наблюдение и причина — неприкосновенны: их правка есть +фальсификация. **Предписание и адрес обязаны оставаться исполнимыми**: запись +версии 10 велит «проверить, что плагин `av-dev-pipeline` установлен», и проект, +дошедший до неё, выполнит невыполнимое. Журнал решений при этом не тронут — в нём +нет предписаний проекту, только записи о принятых решениях; там прежнее имя +верно, потому что описывает состояние на дату записи. + +## Что из этого следует + +**С188. Имя плагина называет материал, а не устройство.** Устройство меняется — +конвейер обзавёлся остановками, — а материал остаётся. Имя по устройству +протухает первым и при этом выглядит осмысленным. + +**С189. Журнал не переписывается в наблюдениях и обязан оставаться исполнимым в +предписаниях.** Правило «не задним числом» защищает от подделки фактов, а не от +починки инструкций: инструкция, ссылающаяся на несуществующее, — не +свидетельство эпохи, а поломка с отложенным сроком. diff --git a/decisions/57-sprints-cancelled-groom.md b/decisions/57-sprints-cancelled-groom.md new file mode 100644 index 0000000..60cdfdd --- /dev/null +++ b/decisions/57-sprints-cancelled-groom.md @@ -0,0 +1,58 @@ +# 57. Спринты отменены: приоритет стал порядком строк, `session` стал `groom` (2026-08-09) + +**Р205. Спринт отвечал на вопрос «что делать дальше» замороженным набором, а +между наборами на этот вопрос не отвечал никто.** Процесс идёт задача за +задачей, и набор перестал что-либо удерживать: он не синхронизировал (некого), +не ограничивал по времени (тайм-бокс не брали) и не защищал от врывания +(врывалось ровно два класса, оба назывались правилом). Осталась цена — +обязанность собрать, показать, заморозить и распустить. + +**Приоритет вернулся, и вернулся туда, где ему место.** Прежнее правило «порядка +нет, есть цель» было обосновано **набором спринта**, и с ним потеряло опору. +Приоритет — свойство очереди, а не задачи, поэтому его дом **индекс**: то же +исключение из правила «файл — источник истины», что уже было у «в каком индексе +лежит запись». Числом в файле он быть не мог — два соседних файла смогли бы +утверждать одно место, а строка индекса противоречить обоим. + +**Гейт готовности стоял на `sprint take` и чуть не исчез вместе с ним.** Это было +единственное место, где запись судили целиком: тип, цель у `feature`, пустой +раздел вопросов, схема типа. Без спринта момента не осталось бы вовсе, а узнают +о недописанной задаче на приёмке, когда сверять уже не с чем. Момент назвали +заново — команда `tasks.py ready <слаг>`, и зовёт её тот, кто берёт задачу. +Отказ там **код 1, а не 2**: запись не дописана — это рабочая ситуация, а не +ошибка употребления. + +**`session` стал `groom`, и предмет сузился до двух вопросов** — что сейчас +самое важное и что перестало быть важным. Из четырёх шагов прежней сессии выжили +два (вопросы, переоценка порциями), один заменился (расстановка очереди вместо +набора спринта), два выпали: + +- **приёмка закрытых задач** — грумингу не по предмету. Ритуала у неё больше нет, + остаётся `reopen` по требованию. Цена названа прямо: приёмка происходит только + тогда, когда что-то уже бросилось в глаза; +- **разбор процесса** — его якорем был прошедший спринт. Вместе с ним из скилла + ушёл прямой вызов агентов `doc-consistency` и `doc-code-drift`, и это **не + потеря, а починка**: агенты принадлежат `av-dev-docs`, и груминг звал их мимо + правила обращения к соседу, без ветки «плагина нет». Груминг теперь только + **называет повод** сверить канон, а когда их звать — решает их владелец. + +**Побочно найдено:** `canon.md` — дом определения канона — объявлял себя версией +7, когда скрипт шёл на 11. Пять версий дом врал о себе, и не заметил никто: +машина сверяет версию проекта с константой скрипта, а прозу в заголовке не +читает. + +## Что из этого следует + +**С190. Правило, обоснованное механикой, умирает вместе с ней — и надо +проверять, что вопрос умер тоже.** «Порядка нет» держалось на наборе спринта; +набор ушёл, а вопрос «что делать дальше» остался и повис без ответа. Снимая +механику, ищи не только то, что на ней стояло, но и то, на что она отвечала. + +**С191. Гейт живёт в моменте, а не в команде.** Проверка готовности была +свойством `sprint take` — и была бы потеряна как деталь удаляемой команды. +Момент «запись впервые судят целиком» существует независимо от того, чем он +назван, и переезжает вместе с процессом. + +**С192. Версия в прозе, которую не читает машина, протухает молча.** Дом канона +назвал себя версией 7 при текущей 11: сверка шла по константе скрипта, а +заголовок документа не сверял никто. diff --git a/decisions/58-doc-judges-healthcheck.md b/decisions/58-doc-judges-healthcheck.md new file mode 100644 index 0000000..f509b94 --- /dev/null +++ b/decisions/58-doc-judges-healthcheck.md @@ -0,0 +1,39 @@ +# 58. Судьи документов получили свой скилл — `healthcheck` (2026-08-09) + +**Р206. Момент вызова был свойством чужого ритуала и исчез вместе с ним.** +`doc-consistency` и `doc-code-drift` звались шагом сессии между спринтами. +Сессия стала грумингом, груминг судит задачи, а не документы, и звать чужих +агентов он не вправе — они живут в `av-dev-docs`. На живом проекте их не звал бы +**никто**, кроме разовых `adopt` и `upgrade`. + +Чинить это возвратом вызова в груминг было нельзя: это ровно то нарушение +границы, которое там и обнаружилось (вызов агента чужого плагина по имени, без +ветки «плагина нет»). Момент нужно было назвать **у владельца** — и оказалось, +что владельца-то у них и нет: `canon` их звал, но владел раскладкой, а не +суждением. + +**Скилл `av-dev-docs:healthcheck`.** Предмет — то, чего машина не видит: +разошлись ли документы между собой и с кодом. Разрез с `canon check` проверяемый: +**машина сверяет форму, healthcheck — утверждения.** «Раздел есть» проверит +скрипт; «написано, что зависимость одна, а в манифесте их три» — суждение. + +**Почему скилл, а не просто описание агентов.** Триггер у агента и так есть — его +`description`. Но двоим нужна **оркестровка**: позвать обоих на весь канон разом, +передать `doc-code-drift` раздел запретов, разобрать урожай порциями, назвать +границы покрытия и то, кого именно позвал. Этого агент о себе не знает. + +**`doc-wording` внутрь не взят, и это разрез, а не забывчивость.** Ему +оркестровка не нужна: он один и работает по названному списку документов. И ритм +другой — он нужен там, где текст только что писали, а не там, где он год лежал. +Скилл, собравший всех троих «потому что все про документы», склеил бы разные +вопросы под одним вызовом. + +## Что из этого следует + +**С193. Момент вызова — такая же собственность, как сам инструмент.** Агент, чей +момент назначен чужим ритуалом, теряет его вместе с ритуалом и замолкает +беззвучно: он исправен, его просто никто не зовёт. + +**С194. Оркестровка — вот что отличает скилл от агента.** Одному исполнителю с +ясным входом скилл не нужен, его находит описание. Скилл заводят там, где надо +решить, кого звать, что передать, в каком объёме и что делать с результатом. diff --git a/decisions/59-four-subagent-audit.md b/decisions/59-four-subagent-audit.md new file mode 100644 index 0000000..517a78c --- /dev/null +++ b/decisions/59-four-subagent-audit.md @@ -0,0 +1,58 @@ +# 59. Аудит четырьмя сабагентами: описания отстают от механики молча (2026-08-09) + +Реорганизация была объявлена законченной «на бумаге», и я запустил по аудитору на +плагин — консистентность, самостоятельность, интегрируемость. Гейт при этом был +зелёным и остался честен: он проверяет ровно то, что умеет. + +Нашлось около полусотни расхождений, и они **одного рода**. Каждый раз я правил +механику — вырезал спринт из скрипта, переименовал скиллы, перенёс судей — и +каждый раз не правил то, что механику **описывает вовне**: скелеты документов, +докстринги скрипта, уставы агентов, манифесты плагинов, README. + +Самое дорогое: **скелет `CLAUDE.md` уносил слоты спринта в каждый новый проект** +через две недели после отмены спринтов. Скелет не описывает, а порождает: его +отставание не читается, оно исполняется. + +**Механизм приоритета не запускался ни разу.** `--section` у `move` был +обязательным, а все три места, где груминг предписывает расстановку, дают команду +без него — usage error. Скилл написан, прогнан не был, и разницы между рабочим и +бумажным процессом не видно, пока его не запустят. + +**Правило границы я же и нарушал.** Восемь дословных копий «путь в дерево чужого +плагина не пишется никогда» — и пять мест, где путь написан, одно из них строкой +выше собственного «пути туда конвейер не выносит». + +Отдельно: **у описания плагина было два дома**, и три из четырёх разошлись. Класс +закрыт не дисциплиной, а машиной — `frontmatter.py` теперь сверяет `plugin.json` с +`marketplace.json`, а гейт разбужен на `*.json`. + +Правки разобраны четырьмя пропусками по одному сабагенту на пропуск, с проверкой +результата каждого: скелеты и канон, исполнимость учёта задач, границы и стыки, +словарь и манифесты. Скриптовые правки проверены поведением на фикстурах, включая +настоящий git-репозиторий для `reopen`. + +**Чего аудит не даёт.** Это было чтение. Ни один скилл по-прежнему не исполнялся +на живом проекте, и находки вроде «чекпоинт вырождается в ритуал» такой проверкой +не берутся по построению. + +## Что из этого следует + +**С195. Механика проверяется прогоном, описание — только чтением.** Поэтому +после каждой правки механики отстают именно описания, и отстают молча. Меняя +механику, ищи её отражения поимённо: скелеты, докстринги, уставы агентов, +манифесты, README. + +**С196. Скелет дороже документа: он не описывает, а порождает.** Отставший +документ врёт одному читателю; отставший скелет уезжает в каждый новый проект и +становится там обязательным. + +**С197. Копия правила не заставляет его исполнять.** Правило исполняется там, +где его проверяет машина или чужой глаз; восемь копий на видном месте не +помешали автору нарушить его пятью строками. + +**С198. Бумажный процесс неотличим от рабочего, пока его не запустили.** +Команда, которую никто не набрал, может не существовать вовсе — и именно так и +было. + +**С199. Два дома у факта расходятся не когда-нибудь, а сразу.** Из четырёх пар +описаний плагина совпала одна — та, которую с момента заведения не правили. diff --git a/decisions/60-service-file-named-by-owner.md b/decisions/60-service-file-named-by-owner.md new file mode 100644 index 0000000..b39a35d --- /dev/null +++ b/decisions/60-service-file-named-by-owner.md @@ -0,0 +1,68 @@ +# 60. Служебный файл зовётся по плагину-владельцу; у задач появилась своя версия формата (2026-08-11) + +Файл версии канона звался `docs/.pm.json` — по плагину `av-dev-pm`, который +распался на четыре ещё в [теме 56](56-plugin-and-skill-renames.md) и которого +больше нет. Имя пережило владельца на два месяца и указывало в пустоту: читающий +его искал плагин, о котором в репозитории не осталось ни строки. Переименован в +`docs/.docs.json` записью 13 журнала канона. + +**Р207. Правило, которое из этого вынуто и теперь держит все три файла:** имя +служебного файла — имя плагина, который его завёл. `.docs.json` — канон, +`.tasks.json` — задачи, `openspec/config.yaml` — конвейер. По этому же следу +скиллы узнают, что сосед в проекте работал, и правило перестало быть просто +перечнем — оно выводимо. + +**Р208. Прежнее имя `docs.py` не читает.** Соблазн «прочитать оба и не мешать +людям» здесь стоит дороже, чем везде: по этому числу `upgrade` решает, какие +записи журнала применять, и два дома для него разъехались бы молча в том самом +месте, где расхождение и вредно. Вместо совместимости — узнавание: `check` видит +файл под старым именем и печатает готовую команду `git mv`. + +**Р209. У каталога задач появилась своя версия формата** — ключ `tasks` в +`<каталог задач>/.tasks.json` и свой журнал версий в скилле +`av-dev-tasks:tasks`. До сих пор её не было вовсе, хотя `docs.py` в комментарии +уверенно ссылался на «свою версию формата» соседа: описание опережало механику +ровно так, как описано в следствии [С195](59-four-subagent-audit.md). Формат +задач при этом менялся — записями 8, 11 и 12 чужого журнала. + +**Р210. Число именно своё, а не копия канонического.** Плагин ставится в +одиночку: проект, взявший учёт работ без канона документов, каталога `docs/` не +имеет вовсе, а значит не имеет и версии канона — сверять было бы не с чем. Копия +чужого числа в `tasks.py` была бы вторым домом одной версии и разъехалась бы при +первом же обновлении одного плагина без другого. + +**Р211. Переезды, случившиеся до появления числа, задним числом в новый журнал +не переписаны.** Версия 1 — это формат на день её появления; что проекту нужно +было пройти до неё, названо шагом «догнать формат по журналу канона» с +поимёнными признаками отставания (каталог в `docs/tasks/`, живой `SPRINT.md`). +Второй перечень тех же шагов разошёлся бы с первым — это ровно та ошибка, из-за +которой план однажды повторял записи версий 3, 4 и 5 построчно. + +**Р212. Конфиг задач стал обязательным.** Раньше он заводился только ради имён, +отличных от умолчания, и проект с умолчаниями жил без файла вовсе. Версия — не +настройка, от которой можно отказаться, поэтому `init` и `adopt apply` пишут его +всегда, а `check` требует числа. + +Отдельно стоит сказать, чтобы не спутали при чтении журнала: `.docs.json` +однажды уже был отвергнут — решением [Р6](02-project-doc-canon.md), но **как +указатель путей**. Отвергнут был указатель, а не имя; сегодняшний файл путями +проекта не распоряжается, он объявляет версию и называет то немногое, чего из +раскладки не вывести. + +## Что из этого следует + +**С200. Имя служебного файла — часть границы плагинов, а не деталь.** Оно +называет владельца, и по нему же владельца узнают. Пережившее владельца имя врёт +дважды: указывает на несуществующее и прячет того, кто файл ведёт на самом деле. + +**С201. Версия нужна каждому формату, который живёт в чужом репозитории.** Без +числа «приведён ли проект» не имеет определённого ответа, и отставший каталог +выглядит здоровым до первой команды, которая об него споткнётся. + +**С202. Своя версия — у своего плагина, всегда.** Общее число на два плагина +переживает ровно до первого проекта, где поставлен один из них. + +**С203. Версию двигают руками, и это не слабость проверки.** Число отвечает на +вопрос «по какой записи повышать», а не «сделаны ли шаги по существу». Машина, +приписывающая недостающее число сама, объявляет проект приведённым к формату, +которого никто не проходил. diff --git a/decisions/61-research-and-solve-two-scenarios.md b/decisions/61-research-and-solve-two-scenarios.md new file mode 100644 index 0000000..4fe2029 --- /dev/null +++ b/decisions/61-research-and-solve-two-scenarios.md @@ -0,0 +1,68 @@ +# 61. Разведка и решение — два сценария одного скилла, а не два скилла (2026-08-11) + +Скилл `resolve` вёл обе работы одной цепочкой: у исследовательской задачи были +свои три шага и свой чекпоинт вариантов, после которого она **вливалась в общую +ветку** и продолжалась кодом. Разведка тем самым была не работой со своим +исходом, а прологом к коду: её ответ оседал в `design.md` будущего change, и +разведка, кончившаяся знанием, документов проекта не касалась вовсе. + +Сперва я развёл их на два скилла — `resolve` и `research`, с исходом и стопом с +обеих сторон. Через час работы стало видно, чем это плохо: **классифицировать +задачу приходится человеку до вызова**, а «есть ли у неё очевидный способ +решения» видно только после чтения записи. Разделение переносило самое трудное +суждение туда, где для него меньше всего данных. + +**Р213. Точка входа одна, сценария два, выбирает сценарий скилл.** Оба +сценария живут справочниками — `references/solve.md` и `references/research.md`, +— а в `SKILL.md` остались вход, развилка и правила, не зависящие от сценария. +Тем же приёмом сложен скилл задач: общая часть в `SKILL.md`, алгоритм каждого +типа в `references/task-*.md`. + +**Р214. Порознь и одинаково — это отдельное решение.** Сперва разведка уехала в +справочник, а решение осталось в теле скилла: так вышло само, потому что решение +там уже лежало. Асимметрия читается как старшинство — сценарий в теле выглядит +основным, а сценарий в справочнике оговоркой, — и удерживает шестисотстрочный +файл, который грузится целиком даже ради разведки. + +**Р215. Что у разведки появилось своего.** Исход — знание, а не пролог: ответ +уезжает в документы канона (`av-dev-docs:docs`), задачи заводятся и уточняются +(`av-dev-tasks:tasks`), написанное коммитится, запись закрывается. Кода сценарий +не пишет вовсе. OpenSpec ему не нужен — это единственное место скилла, где тот +не предпосылка. + +**Р216. Переход между сценариями — событие с названным исходом.** Решение, +упёршееся в незнание способа, останавливается; разведка, выбравшая способ, +доводится до конца и **не переходит в код тем же прогоном** — следующий +запускает человек. Причина не в церемонии: разведка только что переписала +постановку, и брать её в работу тем же заходом значит решать за человека, стоит +ли делать это сейчас, — а это приоритет. + +**Р217. Канон пришлось тронуть, и это версия 14.** ADR цитировал только архивный +`design.md`. У решения, принятого разведкой, `design.md` нет по построению — +change по нему не будет никогда, — и такое решение либо не попадало в `adr/` +вовсе, либо попадало сочинённым заново. Теперь источников два, и оба называются +в записи. + +## Что из этого следует + +**С204. Разделять работы стоит по моменту для человека, а не по роду работы.** У +разведки и решения он разный: варианты обсуждают до первого требования, +объяснение — после ревью дизайна. Всё остальное различие (пишем код или нет) из +этого уже следует. + +**С205. Точку входа не разделяют по признаку, который виден только внутри.** +Классификация, требующая прочитать запись, не может быть условием вызова: +человек либо ошибётся, либо прочитает запись сам — и тогда скилл ему не нужен. + +**С206. Сценарий в справочнике дешевле скилла.** Скилл стоит описания, границ, +копии правил и своего места в графе вызовов; справочник наследует их у хозяина. +Заводить второй скилл имеет смысл, когда его зовут отдельно, а не когда он +просто длинный. + +**С207. Равные сценарии лежат одинаково.** Оставить один в теле скилла, а второй +вынести — значит назначить первому старшинство, которого в замысле нет. Читатель +это старшинство считывает, даже когда о нём не сказано ни слова. + +**С208. Работа без своего исхода вырождается в пролог.** Разведка, кончавшаяся +переходом к коду, не имела причины писать в документы: её ответ и так уезжал в +`design.md`. Дом для исхода — вот что делает работу работой. diff --git a/decisions/62-wording-called-by-editor.md b/decisions/62-wording-called-by-editor.md new file mode 100644 index 0000000..766b8b1 --- /dev/null +++ b/decisions/62-wording-called-by-editor.md @@ -0,0 +1,44 @@ +# 62. Вычитку зовёт тот, кто правил, а не тот, кто синкал (2026-08-11) + +Вычитка документов агентом `doc-wording` была привязана к **синку**: «позови его +последним шагом синка», «ничего не правивший синк агента не зовёт». Сценарий +разведки о себе говорит обратное — «правило принуждённого отрицания здесь не +действует, это не синк», — и при буквальном чтении вычитка не доставалась ему +вовсе: документы правились, а звать было некому. Гейт перед коммитом машинный, он +смотрит раскладку и битые ссылки, а не залог и неизвестный термин. + +**Р218. Условие вызова теперь — правка, а не обряд, внутри которого она +случилась.** Признак читается буквально: документы правились — зови, ничего не +правил — не зови. Синк остался самым частым вызывающим, но перестал быть +единственным. + +**Р219. У разведки вычитка стала своим шагом, а не оговоркой внутри чужого.** +Она стоит между записью и гейтом, потому что раньше пачка не полна: разведка +правит две вещи сразу — документы канона и записи каталога задач, — и собирается +пачка только к концу пятого шага. После коммита вычитка правила бы уже +закоммиченное. + +**Р220. Обе пачки судятся своими проходами.** Документы — `doc-wording`, записи +задач — `task-form`, затем `task-wording`; владеет каждым проходом его плагин, и +разведка их не зовёт напрямую, а просит владеющий скилл. + +**Р221. Запрет остался, но только на судей канона.** `doc-consistency` и +`doc-code-drift` идут на весь канон разом и стоят дорого — их момент выбирает +человек через `av-dev-docs:healthcheck`. Смешение этого запрета с вычиткой и +было второй половиной поломки: «агентов по документам на отдельной работе не +зовут» читалось как правило про всех троих. + +## Что из этого следует + +**С209. Правило, привязанное к названию обряда, не срабатывает у того, кто себя +этим обрядом не считает.** Условие вызова формулируется через наблюдаемое +действие — «правил текст», — а не через имя фазы, внутри которой оно обычно +происходит. + +**С210. Дорогая проверка и дешёвая проверка не живут под одним запретом.** Довод +«не зови агентов сам» верен для судей на весь канон и обратен для вычитки +названной пачки; общая формулировка отменяет вторую вместе с первой. + +**С211. Шаг, собирающий пачку, стоит после последнего, кто в неё кладёт.** +Вычитка на шаге записи проверила бы половину написанного, а после коммита — уже +историю. diff --git a/decisions/63-maintenance-third-scenario.md b/decisions/63-maintenance-third-scenario.md new file mode 100644 index 0000000..938b740 --- /dev/null +++ b/decisions/63-maintenance-third-scenario.md @@ -0,0 +1,101 @@ +# 63. Обслуживание — третий сценарий: у цикла SDD там нет входа (2026-08-13) + +Задача, не меняющая поведения — тулчейн и сборка, зависимости, гит-хуки, перенос, +чистка, — шла полным циклом решения: `propose`, разметка, ревью дизайна, +чекпоинт, `archive`. Все пять шагов стоят на дельта-спеках, а у типа `chore` +дельта-спек **нет по построению**: тип определён через «наблюдаемое поведение не +меняется». Цикл не урезается ради дешевизны — он остаётся без входа, и change, +заведённый под такую задачу, пуст, а разметчик по нему называет не те темы. + +**Р222. Признак сценария — связка из двух проверок, и обе обязательны.** Тип +записи предлагает (`chore`, реже `fix`, чьё исправление возвращает поведение к +уже записанному), отсутствие дельт подтверждает. Тип объявляет автор и может +ошибиться; отсутствие дельт — суждение исполнителя, и в одиночку оно +самообслуживающееся. Разошлись — стоп, а не выбор. + +**Р223. Размер признаком не стал намеренно.** «Мелкая задача — короткий путь» +это универсальная лазейка: скилл сам называет занижение метки и обход чекпоинта +самым дешёвым способом «ускориться». Однострочная правка, меняющая поведение, +идёт полным циклом; крупная чистка, не меняющая, — обслуживанием. + +**Р224. Планового стопа у сценария нет вовсе.** Чекпоинт объясняет человеку +выбор, а выбора здесь нет: что делать, сказано в записи, критерии приёмки +дешёвые и проверяются командой. Объяснение свелось бы к пересказу задачи её же +автору. Правило необратимого при этом действует полностью и срабатывает чаще, +чем в двух других сценариях: выкладка, токены, хуки и чужие данные — обычное +содержимое задач обслуживания. + +**Р225. Ревью идёт фиксированным планом, а разметчик не зовётся.** Обе его оси +не определены: размер он выводит из артефактов change, сложность — из формы +решения, а незнакомая форма ушла в разведку ещё на первом шаге. План — +`autotests` (запуск гейта) и `operations` (сверка), плюс `conventions` с +техническим разбором, когда дифф трогает код, а не только оснастку: +`review-code` — единственный проход, который вообще говорит «здесь ошибка в +логике», и чистка без него проверена лишь на то, что она собирается. +`requirements` и `security` не смотрит никто, и это строка границ покрытия, а не +умолчание. + +**Р226. Найденная дельта — не поломка задачи, а обнаружение более широкого +типа.** Стоп поэтому устроен как три шага, а не как доклад об отказе: назвать +тип, которым задача оказалась (`fix` — расходится с заявленным, `feature` — +снаружи появляется то, чего не было), объяснить человеку простым языком, что +нашлось, и дать **два** решения — переформулировать запись и решать её процессом +того типа следующим прогоном либо прекратить работу. Третьего решения, «доделать +как обслуживание», нет: оно и есть молчаливое изменение поведения. Тип при этом +исполнитель **предлагает**, а меняет `av-dev-tasks:tasks` и только после ответа +— иначе исполнитель назначает себе другой процесс и другую глубину проверки сам. + +**Р227. Триггеры ADR у обслуживания работают стоп-признаком, а не поводом +завести запись.** Список источников ADR канон закрыл двумя — архивный +`design.md` и записка разведки, — и обслуживание не производит ни того ни +другого. Значит дорогой откат, намеренный отказ и пересмотр прежнего решения +означают здесь одно: сценарий выбран неверно, работа идёт разведкой, где решение +проходит чекпоинт вариантов и получает законный источник. Третьего источника +заводить не понадобилось. + +**Р228. Синк документации — главный шаг сценария, а не остаток.** Обслуживание +не меняет поведения, значит почти всё, что оно меняет, — документация: команды, +шаги гейта, зависимости, пути, имя ветки, место механизации правила. Ровно эти +факты `doc-code-drift` и сверяет с кодом. + +**Р229. Состав гейта сверяется отдельно от цвета, а чем именно — решает +проект.** Красный, ставший зелёным, виден; «проверок стало на две меньше, обе +зелёные» не виден ничем, а это единственное место конвейера, где инструмент +проверяет сам себя. Что считается составом, объявляет проект семантикой гейта в +`CLAUDE.md`; не объявил — строка доклада «сверен только цвет», а не догадка. + +**Р230. Своей capability тулчейн не получает, и своего документа канона тоже.** +Граница возможностей и сопровождения проходит по тому, кто наблюдает: гейт +наблюдаем мы, а не пользователь сервиса. Всё, что попало бы в +`docs/toolchain.*`, уже расписано по домам — `CLAUDE.md` (команды, семантика +гейта, запреты, пути), `architecture.*` (зависимости, окружение, выкладка), +`conventions.*` (механизированное), `ROADMAP.md` (работы). Проекту, которому +этого мало, канон уже даёт механизм и без новой строки в раскладке: список тем +открытый, и свой документ заводит свою тему. Цена такой темы названа — она +попадает в план каждого прогона и на большинстве задач молчит. + +## Что из этого следует + +**С212. Короткий путь оправдан отсутствием входа, а не дешевизной.** «Тут можно +проще» — начало любой деградации; «этому шагу нечего обрабатывать» — проверяемое +утверждение, и проверяется оно тем же признаком, что и переход между сценариями. + +**С213. Признак, объявляемый автором, и признак, выводимый исполнителем, держат +друг друга.** Первый один — ошибается в постановке; второй один — +самообслуживающийся. Разрешать расхождение в чью-то пользу нельзя: это стоп. + +**С214. Сценарий без стопа для человека требует более жёсткого правила +необратимого, а не более мягкого.** Стопа, на котором «ой» заметили бы, там нет. + +**С215. Инструмент, проверяющий сам себя, проверяется по составу, а не по +исходу.** Зелёный гейт после правки гейта не значит ничего. + +**С216. Место для нового документа ищется не по теме, а по бездомному факту.** +Тема «тулчейн» звучит убедительно, а фактов без дома за ней не оказалось — +значит документ был бы вторым домом четырёх чужих. + +**С217. Работа, переросшая свой тип, останавливается предложением, а не +отказом.** «Здесь нужно менять спеки» перекладывает классификацию на человека в +момент, когда весь материал для неё у исполнителя. Стоп обязан принести +названный тип, объяснение и закрытый список решений — иначе выбор делается +вслепую или не делается вовсе, и работа доезжает до коммита не тем процессом. diff --git a/decisions/64-three-plugins-merged.md b/decisions/64-three-plugins-merged.md new file mode 100644 index 0000000..30425bf --- /dev/null +++ b/decisions/64-three-plugins-merged.md @@ -0,0 +1,65 @@ +# 64. Три плагина слились в один: раскол платили, а не пользовались (2026-08-13) + +Плагинов было три — `av-dev-docs`, `av-dev-tasks`, `av-dev-code`, — и разрез +между ними шёл по признаку «ставится порознь» ([тема +51](51-av-dev-pm-split.md)). Признак был выбран верно, но **посылка под ним не +проверялась**: за всё время подмножество не понадобилось ни разу, а платился +раскол постоянно. + +Цена измерена, а не оценена: шестьдесят с лишним вызовов между скиллами при цикле +зависимостей `docs → code → docs`, язык проектных текстов четырьмя помеченными +копиями по 213 строк, словарь сопровождения двумя, правило границы семью, +дюжина веток «плагина нет» — и `copies.py`, заведённый ровно затем, чтобы это +не разъезжалось молча. + +**Довод «а вдруг понадобится» снят наблюдением владельца, а не спором.** Ждали +случая «документы и задачи без OpenSpec» — например, ansible-репозиторий. Он +уже покрыт: сценарий обслуживания в `resolve` OpenSpec не требует по +построению, а `docs.py` считает отсутствие `openspec/` неприменимостью, а не +отказом. То есть режим, ради которого держали раскол, работает и в слитом +плагине. + +**Слияние оказалось дешевле, чем выглядело, потому что граница была сделана +правильно.** Присутствие соседа узнавалось **следом в проекте** +(`.docs.json`, `.tasks.json`, `openspec/config.yaml`), а не перечнем +установленных плагинов. Значит мягкость поведения держалась на состоянии +проекта и пережила слияние без единой правки логики: сменилась упаковка, а не +механика. Дом правила переехал из `plugin-boundary.md` в `absence.md` и стал +говорить о том, чем он и был на деле, — о частях раскладки, которых может не +быть. + +**Р231. Версия стала одна и начинается с 1.** Две версии — канон 14 и формат +задач 1 — двигались порознь, потому что порознь ставились плагины; с одним +плагином два числа означали бы только вопрос, по какому журналу повышать. +Прежние журналы закрыты и не переписаны: адрес, верный на день записи, остаётся +свидетельством. Служебный файл один, `.av-dev.toml` в корне репозитория, и +**формат выбран ради комментариев** — файл живёт в чужом репозитории, и +назначение числа должно читаться из него самого, а не из документации плагина. +Отсюда правило записи: скрипт правит строку, а не переписывает файл. + +**Возможность расколоть обратно не потеряна.** Понадобится инфраструктурный +плагин — раскол будет переименованием пространства имён, а не переделкой: +граница по-прежнему держится на следе в проекте. Платить за эту возможность +копиями сегодня незачем. + +## Что из этого следует + +**С218. Разрез, оправданный сценарием, обязан этот сценарий однажды увидеть.** +«Ставится порознь» — проверяемое утверждение, и проверяется оно не рассуждением, +а тем, поставил ли кто-нибудь половину. Пока не поставил, разрез оплачивается +копиями за случай, которого нет. + +**С219. Механика, привязанная к состоянию проекта, переживает перестановку +плагинов; привязанная к их составу — нет.** Это и есть практическая разница +между «узнаём следом» и «узнаём перечнем», и обнаруживается она только на +слиянии или расколе. + +**С220. Копия дословного текста — плата за неразрешимый путь, а не за важность +правила.** Путь разрешился — копия становится вторым домом без причины. Остаётся +она там, где текст обязан лежать **внутри промпта**: устав агента, `SKILL.md` +скилла и скелет, уезжающий в проект. Разрез проверяемый: файл, который модель +получает целиком, против файла, за которым она идёт отдельным чтением. + +**С221. Формат служебного файла выбирается по тому, кто его читает.** Читает +человек в чужом репозитории через полгода — значит комментарии, значит TOML, +значит построчная правка вместо перезаписи. diff --git a/decisions/65-axes-registry-home.md b/decisions/65-axes-registry-home.md new file mode 100644 index 0000000..8833340 --- /dev/null +++ b/decisions/65-axes-registry-home.md @@ -0,0 +1,56 @@ +# 65. Перечень осей получил дом; две оси жили без владельца (2026-08-13) + +Слияние плагинов не тронуло ни одной идеи процесса — типы записей, метки, три +сценария, категории документов, severity находок остались как были. Но оно +сделало дешёвым то, что раньше было дорого: правило, натянутое между задачами и +ревью, теперь имеет достижимый дом, а не помеченную копию через границу. + +**Ось — закрытый перечень значений, по которому что-то ветвится.** Признак +проверяемый, и он отсекает похожее: темы ревью и документы проекта — списки +**открытые**, их пополняет проект. Модель прохода — не ось, а цена прогона. +Осей по этому признаку девять, и дом теперь у каждой. + +**Р232. Две оси были бездомными, и обе машинные.** Коды выхода объявлялись +«общим словарём» в **одиннадцати** местах, и каждое объявление перечисляло свой +набор соседей: «тот же, что у `tasks.py`», «тот же, что у `tasks.py`, `docs.py` +и `copies.py`». Ни одно не было домом — все списки по памяти, и машина их не +сверяла, потому что `copies.py` смотрит markdown, а перечни лежали в +docstring'ах скриптов. Режим прогона (с меткой · без метки) завёлся накануне +слияния и разошёлся по четырём файлам, ни в одном не будучи назван осью, — при +том что в уставе `review-basics` он задаёт **саму возможность запуска** прохода. + +**Р233. Слово «стадия» значило в одном файле две разные вещи.** «Стадия 1 — +Автотесты … Стадия 5 — Triage» — ступени внутри прогона кода, наружу не +выходящие; «метка правит обе стадии ревью» — дизайн и код, то есть членение, +которое видит вызывающий скилл. Разведено: ступени внутри, стадии снаружи. + +**Р234. Дом перечня — не дом значений.** `shared/axes.md` держит только сами +оси, их адреса и **чего каждая не решает**. Механика остаётся у владельца: +второй пересказ разошёлся бы с первым, а вот перечень нужен целиком и в одном +месте — вопрос «а не задаёт ли это метку» задают из скилла, который метку не +ведёт. Целиком сюда переехали ровно две оси, у которых владельца нет. + +**Карта нашла ошибку в самой себе, и это её главный довод.** Первая редакция +объявила пустой клетку «категория документа × метка»: якобы проект вправе +завести тему, под которую ни одна метка не отряжает прохода. Проверка показала +обратное — `review-basics` приёмник проектных тем при **любой** метке. Пустой +оказалась соседняя клетка: на прогоне **без метки** план фиксирован сценарием, и +своих тем проекта в нём нет вовсе. Найти это можно было только сведя оси в одну +таблицу. + +## Что из этого следует + +**С222. Словарь, объявленный «общим» в каждом потребителе, — это перечень по +памяти, а не дом.** Признак вырожденности проверяемый: каждое объявление +называет свой набор соседей, и ни одно не называет владельца. + +**С223. Копия в docstring'е скрипта машиной не сверяется, потому что `copies.py` +смотрит markdown.** Значит, прозе в коде дом нужнее, чем прозе в документах: там +расхождение ловит гейт, здесь — никто. + +**С224. Пустая клетка в таблице осей — находка, а не пробел оформления.** Она +называет случай, для которого процесс не сказал ничего, и до сведения осей в +таблицу такой случай неотличим от продуманного умолчания. + +**С225. Слово, занятое дважды в одном файле, дороже неточного слова.** Читатель, +пришедший за термином, получает два ответа и не знает, что их два. diff --git a/decisions/README.md b/decisions/README.md new file mode 100644 index 0000000..5c6c0ad --- /dev/null +++ b/decisions/README.md @@ -0,0 +1,121 @@ +# Решения по устройству процесса + +Журнал согласований: что решено, почему и что из этого следует. Пишется по ходу +разбора тем, **одна тема — один файл**; этот файл — только указатель. + +Три сквозные нумерации, и они не пересекаются: + +- **Т** — требование: вход, который обязан быть удовлетворён. Живут здесь, ниже. +- **Р** — решение: что согласовано и почему. Р1–Р234 по темам в порядке журнала. +- **С** — следствие: что из решения вытекает. С1–С225, тоже сквозным счётом. + +Номер закреплён за записью навсегда: журнал описывает прошлые состояния и задним +числом не переписывается. Отсюда и разнобой формы — ранние темы держат решения +под заголовком «Решено», поздние ведут их прозой. + +Незакрытые остатки прошлого захода — [REMAINING.md](../REMAINING.md). + +## Требования, зафиксированные по ходу + +Не решения — вход, который обязан быть удовлетворён и разбирается в названной +теме. + +**Т1. Адаптация и проверка проекта под канон — обязательный скилл.** Нужно уметь +прийти в **любой** старый проект и перевести его на текущие рельсы. Канон при +этом сам будет меняться, поэтому уже приведённые проекты тоже должны повышаться +до новых версий. *Разбирается в [теме 5](05-project-start-lifecycle.md) (старт и +жизненный цикл проекта).* + +Следствия, которые из этого уже видны: + +- **У канона обязана быть версия, а у проекта — отметка, под какую он + приведён.** Иначе «соответствует канону» не имеет определённого ответа: + сравнение идёт с тем, что модель помнит сейчас, а это и есть дрейф. +- **Журнал изменений канона — как миграции.** Каждое повышение версии несёт + запись «что добавилось, что переехало, что удалено, что сделать проекту». Без + него адаптация переизобретается на каждом проекте. +- **Отметка версии машиночитаема.** `.docs.json` отвергнут как *указатель + путей* (решение [Р6](02-project-doc-canon.md)), но отметка версии — другое: + её читает скрипт, и разбирать прозу `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](07-skills-layout-scripts.md).* + +## Темы + +| # | Тема | Дата | +|---|------|------| +| 1 | [Статус OpenSpec](01-openspec-status.md) | 2026-08-03 | +| 2 | [Канон документов проекта](02-project-doc-canon.md) | 2026-08-03 | +| 3 | [Брифа ревью нет — бриф это и есть канон](03-review-brief-is-canon.md) | 2026-08-03 | +| 4 | [Границы плагинов](04-plugin-boundaries.md) | 2026-08-03 | +| 5 | [Старт проекта и жизненный цикл под каноном](05-project-start-lifecycle.md) | 2026-08-03 | +| 6 | [Поддержание документов по ходу разработки](06-docs-upkeep.md) | 2026-08-03 | +| 7 | [Раскладка скиллов и доставка скриптов](07-skills-layout-scripts.md) | 2026-08-03 | +| 8 | [Порядок выката](08-rollout-order.md) | 2026-08-03 | +| 9 | [Линтеры скриптов](09-script-linters.md) | 2026-08-03 | +| 10 | [Ревью готовых плагинов двумя проходами](10-two-pass-plugin-review.md) | 2026-08-03 | +| 11 | [Зависимости между плагинами](11-plugin-dependencies.md) | 2026-08-03 | +| 12 | [Механическая проверка копий](12-mechanical-copy-check.md) | 2026-08-03 | +| 13 | [Секции `PLAN.md` переименованы](13-plan-sections-renamed.md) | 2026-08-03 | +| 14 | [Умолчания режимов прогона перевёрнуты](14-run-mode-defaults-flipped.md) | 2026-08-03 | +| 15 | [Порядок проходов ревью — граф зависимостей](15-review-pass-order-graph.md) | 2026-08-03 | +| 16 | [Каталог вместо файла в `docs/` — отложено до переезда healthlog](16-directory-instead-of-file.md) | 2026-08-04 | +| 17 | [Разбор заметок: ступень ревью, род работы, роадмап](17-notes-tier-work-kind-roadmap.md) | 2026-08-04 | +| 18 | [Ступень поднимает проход, а не риск](18-tier-raises-pass-not-risk.md) | 2026-08-04 | +| 19 | [Роадмап — состояние проекта, а не очередь работ](19-roadmap-is-state-not-queue.md) | 2026-08-04 | +| 20 | [Форма записи: заголовок, секции, вычитка](20-record-form-heading-sections.md) | 2026-08-04 | +| 21 | [Язык проектных текстов — информационный стиль](21-project-text-language.md) | 2026-08-04 | +| 22 | [Обкатка агента вычитки: имя, охват и «так везде»](22-wording-agent-trial.md) | 2026-08-04 | +| 23 | [Вычитка разделена на два прохода](23-wording-split-two-passes.md) | 2026-08-04 | +| 24 | [Обкатка двух проходов: два дефекта в собственных правилах](24-two-pass-trial-defects.md) | 2026-08-04 | +| 25 | [Секция `Сопровождение` и общий словарь трёх мест](25-maintenance-section-shared-vocab.md) | 2026-08-04 | +| 26 | [Канон 4: правка задним числом отменена](26-canon-4-retroactive-edit-cancelled.md) | 2026-08-04 | +| 27 | [Тип записи стал единственной осью и задаёт схему](27-record-type-single-axis.md) | 2026-08-05 | +| 28 | [Слаг подкреплён проверкой, обещанный судья заведён](28-slug-check-and-judge.md) | 2026-08-05 | +| 29 | [Обкатка `doc-consistency` на самом dev-skills](29-doc-consistency-trial.md) | 2026-08-05 | +| 30 | [`av-dev-backlog` удалён](30-av-dev-backlog-removed.md) | 2026-08-05 | +| 31 | [Ревизия покрытия `av-dev-pm` продакт-оптикой](31-pm-coverage-product-review.md) | 2026-08-05 | +| 32 | [Сквозной проход по словарю: пять слов сняты, девять закрыты списком](32-vocabulary-sweep.md) | 2026-08-05 | +| 33 | [Стоимость ревью: снят самый дорогой проход и самая дорогая модель](33-review-cost-cut.md) | 2026-08-06 | +| 34 | [Пропускная способность против глубины: тяжёлые проходы уехали в верхнюю ступень](34-throughput-vs-depth.md) | 2026-08-06 | +| 35 | [Ревизия моделей: переведены двое из девяти, и критерий оказался не тот](35-model-revision.md) | 2026-08-06 | +| 36 | [Темы ревью: документ проекта стал направлением проверки](36-review-topics-project-docs.md) | 2026-08-06 | +| 37 | [`gate` и `autotests` сведены к одному имени](37-gate-and-autotests-one-name.md) | 2026-08-07 | +| 38 | [Шов между плагинами: канон не называет имён проходов](38-plugin-seam-no-pass-names.md) | 2026-08-07 | +| 39 | [Спринт без цели — законный случай](39-sprint-without-goal.md) | 2026-08-07 | +| 40 | [Три категории документов: не всякий документ — тема ревью](40-three-doc-categories.md) | 2026-08-07 | +| 41 | [Разметка задачи: одна величина, посчитанная один раз](41-task-sizing-once.md) | 2026-08-07 | +| 42 | [`quick` стал дешевле `standard` тремя способами](42-quick-cheaper-than-standard.md) | 2026-08-07 | +| 43 | [Ревью дизайна тоже растёт ступенями](43-design-review-tiers.md) | 2026-08-07 | +| 44 | [Метка задачи: одно значение, по которому выбираются все ревьюверы](44-task-label-single-value.md) | 2026-08-07 | +| 45 | [Корректор метки, доля `small` и корпус оценки](45-label-corrector-small-share.md) | 2026-08-07 | +| 46 | [Правило выбора метки съехало из скилла в отдельный документ](46-label-rule-own-document.md) | 2026-08-07 | +| 47 | [OpenSpec заводится скиллом, а его конфиг — часть канона](47-openspec-setup-skill.md) | 2026-08-07 | +| 48 | [Форма чужого инструмента держится опросом инструмента, а не памятью](48-foreign-tool-form-by-query.md) | 2026-08-07 | +| 49 | [Дом общего правила вышел из плагина](49-shared-rule-home-outside-plugin.md) | 2026-08-09 | +| 50 | [Вычитка раздвоилась по плагину, а не по правилу](50-wording-split-by-plugin.md) | 2026-08-09 | +| 51 | [av-dev-pm расколот: владение пошло по тому, что ставится порознь](51-av-dev-pm-split.md) | 2026-08-09 | +| 52 | [Валидатор поехал за файлом: у конвейера появился свой скрипт](52-validator-follows-file.md) | 2026-08-09 | +| 53 | [`canon` и `docs` остаются двумя скиллами](53-canon-and-docs-two-skills.md) | 2026-08-09 | +| 54 | [Стык плагинов: правило получило дом, адреса остались у владельцев](54-plugin-seam-rule-home.md) | 2026-08-09 | +| 55 | [`task-pipeline` стал `resolve`: два плановых стопа вместо полной автономии](55-task-pipeline-becomes-resolve.md) | 2026-08-09 | +| 56 | [`av-dev-pipeline` → `av-dev-code`, `review-pipeline` → `review`](56-plugin-and-skill-renames.md) | 2026-08-09 | +| 57 | [Спринты отменены: приоритет стал порядком строк, `session` стал `groom`](57-sprints-cancelled-groom.md) | 2026-08-09 | +| 58 | [Судьи документов получили свой скилл — `healthcheck`](58-doc-judges-healthcheck.md) | 2026-08-09 | +| 59 | [Аудит четырьмя сабагентами: описания отстают от механики молча](59-four-subagent-audit.md) | 2026-08-09 | +| 60 | [Служебный файл зовётся по плагину-владельцу; у задач появилась своя версия формата](60-service-file-named-by-owner.md) | 2026-08-11 | +| 61 | [Разведка и решение — два сценария одного скилла, а не два скилла](61-research-and-solve-two-scenarios.md) | 2026-08-11 | +| 62 | [Вычитку зовёт тот, кто правил, а не тот, кто синкал](62-wording-called-by-editor.md) | 2026-08-11 | +| 63 | [Обслуживание — третий сценарий: у цикла SDD там нет входа](63-maintenance-third-scenario.md) | 2026-08-13 | +| 64 | [Три плагина слились в один: раскол платили, а не пользовались](64-three-plugins-merged.md) | 2026-08-13 | +| 65 | [Перечень осей получил дом; две оси жили без владельца](65-axes-registry-home.md) | 2026-08-13 | diff --git a/scripts/addresses.py b/scripts/addresses.py index aa6a717..48cb6e7 100644 --- a/scripts/addresses.py +++ b/scripts/addresses.py @@ -51,13 +51,15 @@ OWNERS = { # Журналы: описывают прошлые состояния и задним числом не переписываются. # Адрес, верный на момент записи, здесь останется навсегда, и это не дрейф. +# Ключ, кончающийся на `/`, — каталог целиком: журнал решений разложен по теме +# на файл, и каждый новый файл в нём — журнал по построению, а не по списку. JOURNALS = { "av-dev/skills/doc-canon/references/changelog.md": "журнал версий раскладки", "av-dev/skills/doc-canon/references/changelog-before-merge.md": "журнал версий канона до слияния", "av-dev/skills/doc-canon/references/changelog-tasks-before-merge.md": "журнал версий формата задач до слияния", - "DECISIONS.md": "журнал решений", + "decisions/": "журнал решений", "HISTORY.md": "журнал работ", "NOTES.md": "рабочие заметки", } @@ -136,7 +138,9 @@ def walk(root: Path) -> list[Path]: rel = p.relative_to(root) if SKIP_DIRS & set(rel.parts): continue - if rel.as_posix() in JOURNALS: + posix = rel.as_posix() + if any(posix == k or (k.endswith("/") and posix.startswith(k)) + for k in JOURNALS): continue out.append(p) return out