Files
dev-skills/av-dev/skills/canon/references/canon.md
T
av ed83ec7dc0 задачи: починена смена стадии, разобраны находки ревью плагина
Команда stage была дефектна по шести пунктам, и все шесть подтверждены
прогоном: не звала raw_last (переход оставлял каталог красным), не
переписывала шапку беклога (индекс продолжал объявлять прежнюю стадию),
шла в обход write_config, молча пропускала файлы с непересобираемой метой,
ломалась на беклоге без заголовков и схлопывала полки при первом
объявлении стадии.

Объявление и смена разведены: объявление беклога не трогает вовсе, смена
трогает состав секций только по явному --sections, а слить полки скрипт
не берётся ни в одном случае. Абзац шапки размечен парой «стадия», и
расхождение с конфигом стало обычным дрейфом.

Отказ по недостающей строке индекса запирал запись, пережившую упразднение
роадмапа: edit, close и reopen теперь заводят или пропускают строку сами.
Прочее: регистр stage нормализуется при чтении; --fix снимает мёртвые теги
и у неразобранных записей; move отказывает переставлять сырьё; adopt
держит место сырья; docs.py bump двигает одну запись журнала за раз;
tasks.py получил перечень упразднённых адресов, и гейт наконец видит
собственное упразднение ROADMAP.md.

Запись «Версия 3» переписана по прогону на игрушечном проекте: прежний
порядок шагов был неисполним. Закрыты дыры модели стадий (пересмотр плана
стройки стал сценарием, приёмка отвязана от груминга, from-review,
research и adopt получили развилку по стадии, перечень осей пересчитан) и
находки, старшие этой сессии: review-triage получил режим без метки, три
списка проектных копий сведены к дому с проверяемыми копиями, пять
пересказов правил стали помеченными копиями или ссылками, language.md
перестал объявлять юрисдикцию над чужим плагином.
2026-08-13 15:08:29 +03:00

50 KiB
Raw Blame History

Канон документов проекта

Номер версии здесь не стоит намеренно. Этот файл описывает канон таким, какой он сейчас, а число живёт в двух домах, которые не расходятся: константа в docs.py (её печатает docs.py version) и верхняя запись журнала. Литерал в шапке был третьим и отстал на первом же повышении — версию 13 он пережил, объявляя канон двенадцатым.

Это единственный дом определения канона. Скиллы doc-init, canon и doc-sync читают его, а не пересказывают: три описания одной раскладки разъедутся, и работать будет то, которое прочитали последним. Меняется канон — меняется этот файл и появляется запись в changelog.md.

Зачем канон жёсткий

Пути фиксированы, и проект под них подгоняется, а не наоборот. Причина не техническая: проектов много, все малого и среднего размера, и ориентироваться в слегка похожих, но разных раскладках дороже, чем один раз привести их к общей. Рядом лежит OpenSpec, у которого структура тоже строгая.

Цена принята сознательно: плагин не переносится на чужой репозиторий как есть — чужой репозиторий приводится к канону скиллом canon.

Раскладка отвечает, где текст лежит и на какой вопрос отвечает. Каким он должен быть словами — общий для всех документов канона файл shared/language.md: информационный стиль, англицизмы, жаргон. Он относится и к задачам, и к решениям ADR, и к запискам разведки.

Сопровождение и эксплуатация — целое и часть

Словарь этой темы — shared/operations.md: целое и часть, три места одной темы (задачи chore, раздел архитектуры, тема ревью operations) и граница с возможностями проекта. Здесь он не пересказывается: копия жила рядом с домом в одном дереве и была ровно тем вторым домом, против которого правило и написано.

Раскладка

Документ канона живёт файлом или каталогом. docs/security.md и docs/security/ — одно и то же; форму выбирает проект по объёму написанного, и переход между формами не меняет ни канон, ни версию. Обе формы сразу — ошибка: два дома для одного факта расходятся молча.

CLAUDE.md                      памятка агенту: что это, стек, инварианты с
                               severity, команды, семантика гейта, запреты
AGENTS.md                      необязателен, лежит рядом; читается теми же
.av-dev.toml                   версия раскладки и настройки проверок; лежит
                               в корне, потому что нужен и без docs/
docs/
  passport.md   | passport/    зачем и для кого; чем НЕ является; сценарии
  architecture.md | architecture/  как сложено — обзор; окружение и эксплуатация
  database.md   | database/    схема хранилища; представление данных и настройки
  security.md   | security/    периметр; недоверенный вход; что вне модели
  conventions.md | conventions/  как пишем код; что механизировано
  research.md   | research/    наблюдения и числа с провенансом
  adr.md        | adr/         почему решено так; статусы, правило замены
  review.md     | review/      настройка конвейера под проект + журнал дефектов
  <своя тема>.md | <своя тема>/  всё, что проект счёл нужным проверять
tasks/                         каталог задач — скилл task-track, не канон;
                               лежит в корне, вне docs/, и канон его не требует
openspec/
  config.yaml                  только нужды генерации артефактов + ссылки
  specs/<capability>/spec.md   что система делает — нормативно
  changes/archive/             архив изменений с design.md — сырьё для ADR

У документа-каталога обязателен README.md — вход, по которому его читают агенты. adr/ в форме каталога держит ещё и template.md, а записи именуются ADR-ГГГГ-ММ-ДД-slug.md.

Три категории документов

Категория документа — ось процесса; перечень осей и того, чего каждая не решает, — shared/axes.md.

Раньше здесь стояло плоское правило «каждый документ docs/ — тема ревью». Оно неверно ровно наполовину: паспорт и схема хранилища ревью нужны, но темами не являются, а журнал решений и журнал наблюдений ревью изменения не нужны вовсе. Плоское правило заставляло разметчика либо плодить фантомные темы, либо терять документы молча — а молчащая потеря и есть то, против чего канон написан.

Разрез один и проверяемый: можно ли по документу сказать «в этом изменении сделано не так»?

Категория Ответ на разрез Что с ней делает ревью
тема да, прямо заводит направление проверки и требует исполнителя
источник темы нет, но он задаёт границу, по которой судит чужая тема читается как материал, своей темы не порождает
процессный документ нет: он про то, как мы работаем, а не про изменение не судит по нему изменение
Документ Категория Куда питает
conventions.* тема conventions
security.* тема security
architecture.* тема architecture; раздел эксплуатации — operations
свой документ проекта тема своя тема, именем документа
passport.* источник architecture — граница домена, «чем не является»
database.* источник operations — схема и настройки с числами
CLAUDE.md, AGENTS.md источник autotests (семантика гейта); инварианты — сквозные
openspec/specs/ источник requirements
openspec/config.yaml процессный — (настройка порождения артефактов, слой до тем; заводит конвейер)
tasks/ процессный — (чужое владение: скилл task-track)
review.* процессный — (настройка самого конвейера, слой над темами)
adr.* процессный
research.* процессный
.av-dev.toml процессный — (служебный файл, не документ)

Список тем открытый, и это не послабление, а механизм. Категории источник и процессный закрыты — они перечислены здесь поимённо и проектом не пополняются. Всё остальное, что проект кладёт в docs/, — тема: у конвейера есть приёмник для темы, к которой нет именной оптики, и заведён он ровно за этим. Завёл docs/accessibility.md — появилась тема accessibility, и она попадает в план каждого прогона.

Отсюда следствие, ради которого правило и заведено: docs/ — это конфигурация ревью. Проект настраивает проверку тем, что пишет о себе, а не отдельным файлом настроек, который разошёлся бы с документами.

«Не судит по нему» и «не открывает» — не одно и то же, и разница существенна. docs/review.* проходы читают на каждом прогоне: там лежат вопросы по темам, журнал дефектов, типовые узлы и типовые ложноположительные. Это чтение конвейером собственной настройки, а не суждение об изменении, и потому оно законно. adr/, research/ и tasks/ не открывает никто: по ним изменение не судят, и настройкой конвейера они не являются.

Процессный документ — не документ второго сорта. adr/ и research/ проверяются наравне с остальными, но сверкой документации, а не прогоном ревью: ADR без ссылки на источник, замена без парного статуса, число без провенанса — это работа агентов doc-consistency и doc-code-drift, и она осталась там же, где была. Изменилось одно: прогон ревью не открывает их как критерий и не судит по ним изменение.

Цена этого решения записана, а не подразумевается: расхождение изменения с записанным решением прогоном больше не ловится. Раньше архитектурный проход читал adr/ и мог сказать «здесь отменено решение ADR-2026-03-11, а парного статуса нет»; теперь это скажет только doc-consistency, а зовёт его скилл healthcheck. Сделка сознательная — ADR объясняет прошлое, а не предъявляет требование к изменению, и чтение всего каталога решений на каждой задаче оплачивалось на каждой, а срабатывало на единицах.

Имена файлов английские, текст русский

Текст документов русский; имена файлов, capability и задач — английские, kebab-case. Причина не эстетическая: имя файла стоит в ссылках из других документов, в коммитах и в путях, которые набирают руками, — а кириллица в пути ломается по-разному в разных местах и не набирается на английской раскладке.

Транслита не заводим. Слаг именуется английским словом по сути, а не записью русского латиницей: queue-as-table, а не ochered-tablicej. Транслит нечитаем тому, кто ищет по смыслу, и не сокращается.

У ADR имя вдобавок несёт форму — ADR-ГГГГ-ММ-ДД-slug.md: по ней записи сортируются, и по ней же ищется дата решения.

docs.py check проверяет кириллицу и kebab-case жёстко, форму имени ADR — тоже, а транслит эвристикой, то есть замечанием: английское слово от транслита машина не отличает. Слаги каталога задач ведёт tasks.py — там та же проверка и тот же разрез.

Переименование — не правка, а перенос ссылок: делается одним проходом по всем местам, где имя упомянуто, иначе останутся битые ссылки. Для задач это умеет tasks.py adopt; для документов канона правит человек, а docs.py потом показывает, что ссылки целы.

Роли документов и темы ревью

Одна строка на каждый — на какой вопрос он отвечает, в какой он категории и какую тему питает. Кто именно закрывает тему, здесь не указано намеренно: это зависит от метки прогона и меняется вместе с конвейером, а документ живёт дольше. Раскладку «тема → проход → глубина» держит скилл av-dev:code-review.

Общего словаря у канона с конвейером ровно три вида имён: имена категорий, имена тем и имена меток. Категорий три — тема, источник, процессный; меток тоже три, и они закрыты: small, medium, large. Метка это итог классификации задачи, и по ней конвейер выбирает исполнителей на обеих стадиях ревью; проект её не выдумывает, а только уточняет триггеры. Ими проект и настраивает ревью — вопросами по темам и триггерами метки. Имён проходов канон не называет нигде, включая вывод docs.py: проход переименовывается и переезжает между метками, и канон, назвавший его, в этот день соврёт молча. Обратное направление законно — конвейер называет документы канона поимённо, потому что он их читатель.

Документ Вопрос Категория и тема
CLAUDE.md, AGENTS.md что нельзя нарушать, чем краснеет гейт источник: autotests; инварианты — сквозные, во все темы
passport.* зачем и для кого, чем это не является источник: architecture
architecture.* как сложено и где что работает тема architecture; раздел эксплуатации — operations
database.* что лежит в хранилище и какими настройками источник: operations
security.* против кого защищаемся и что вне модели тема security
conventions.* как мы пишем код тема conventions
openspec/specs/ что система делает — нормативно источник: requirements
research.* что показала реальность, а не документация процессный
adr.* почему решено именно так процессный
review.* как настроен конвейер и что уже проскакивало процессный: слой над темами
tasks/ что делаем и в каком порядке процессный
свой документ проекта что проект счёл нужным проверять своя тема, именем документа

passport.md

Цель; закрытый список потребителей и что каждому нужно; чем целью не является — это граница домена, по которой архитектурный проход судит о переносе понятия; типовые сценарии; мера, по которой проект считается удавшимся; референсы, у кого подсматривать.

architecture.mdобзор, не поведение

Принципы; компоненты со ссылками на capability, а не с пересказом их требований; единые точки проекта — где генерируются идентификаторы и время, где единственный парсер входного формата, где маппинг доменной ошибки в код ответа, где общий путь приёма (это материал для вопроса «не появился ли второй способ»); внешние границы и форматы чужих систем; окружение — где работает, что рядом, кто перезапускает; внешние зависимости поимённо и чем каждая отказывает (не только «падает», но и «отвечает медленно», «молчит», «отдаёт мусор»); кто заметит отказ и когда; характер потока — непрерывный, по запросу, по расписанию; деплой; открытые вопросы.

Обратимости здесь нет — её единственный дом CLAUDE.md: туда ходят пять проходов, и раздвоение адреса означало бы, что проект написал ответ, а ревью его не прочитало.

Поведение системы сюда не пишется. Его нормативный дом — openspec/specs/, куда opsx:archive вливает дельты; второй дом синхронизировать руками невозможно, и он разойдётся.

Раздел, ещё не разнесённый при переезде, помечается маркером долга:

<!-- канон: поведение → openspec/specs/<capability> -->

docs.py считает маркеры и печатает остаток числом. Гейт от них не краснеет: это долг, а не отказ, иначе постепенный переезд стал бы невозможен.

database.md

Схема: таблицы, ключи, связи, правило времени и идентификаторов. Плюс то, чего нет в схеме, но без чего замер не превращается в находку: чем физически лежит запись (сжатый BLOB, JSON-строка, колонки), что происходит при чтении и записи (распаковка целиком, read-modify-write), и настройки с числовым значением — таймаут занятости, режим журналирования, лимит тела, размер пула, ретеншен.

Конвенции идентификаторов и именования — не схема, они в conventions/.

security.md

Периметр первой строкой. «Сервис открыт наружу» и «контур доверенный, публичного интернета здесь нет, не выдумывай его» — противоположные постановки под одним заголовком, и разбор темы security между ними сам не выберет. Контур ещё не развёрнут — назови оба периметра, целевой и сегодняшний, и скажи прямо, против какого строятся находки.

Дальше: что недоверенное и каким каналом приходит; из чего строятся пути и ключи (раскладка файлов, состав координатного ключа, имя каталога) — отсюда строится выход за пределы песочницы; что разграничивает доступ; что чувствительнее чего; что вне модели — перечислить явно.

conventions/

Прозой остаётся только то, что не выражается правилом. README.md держит индекс, правило промоута и перечень уже механизированного со ссылкой на место механизации — конфиг линтера, собственный анализатор, тест-сканер исходников. Не названное место механизации означает, что проход добросовестно проверит уже проверенное.

research/

Наблюдения за внешним миром: что реально шлёт источник, чем документация формата расходится с практикой, какие числа сняты с живого потока. Числа — с провенансом, то есть с командой или условиями, которыми получены. README.md — как снималось и индекс тем.

Число без источника проход обязан читать как условие, а не как замер. Число, чей источник по ссылке не подтвердился, не выбрасывается и не переписывается по догадке — остаётся с пометкой «расходится с источником: там <что нашли>».

adr/

ADR продвигает уже написанное решение, а не сочиняет его заново. Запись цитирует решение и ссылается на источник. Источников два, и оба законны:

  • архивный design.md — решение принято по ходу изменения: openspec/changes/archive/<id>/design.md. Обычный случай;
  • записка разведки — решение принято разведкой, и change по нему не будет никогда: намеренный отказ, выбор подхода, «проверили и не делаем». У такой работы нет design.md по построению, и без второго источника её решение либо не попадало в adr/ вовсе, либо попадало сочинённым заново.

Источник называется в записи всегда — по нему видно, чем решение подтверждено.

Заводится, когда верно одно из трёх:

  • дорогой откат — переделка стоит дороже переписывания одного файла;
  • намеренный отказ от очевидного подхода;
  • пересмотр прежнего решения — тогда у старой записи обязателен статус «заменено на».

Не заводится для рутины и для того, что видно из кода и git log.

Записи неизменяемы: передумали — заводится новая, старая получает статус. Активная запись статуса не имеет.

Статус живёт полем меты записи, там же, где дата и источник: - **Статус:** заменено на ADR-… либо - **Статус:** устарело. Места ему в шаблоне не отводилось, и каждая запись изобретала своё — то абзацем, то заголовком; в таблице adr/README.md статус при этом обязан быть, а брать его оттуда, где он у каждого свой, нельзя.

review.md

Два раздела с разными сроками жизни.

Настройка конвейера под проект, пять подразделов с точными именами — по ним проходы находят свой кусок:

  • Типовые узлы — рода узлов проекта и 3–5 проверяемых свойств к каждому;
  • Типовые ложноположительные — находки, которые здесь выглядят убедительно и всегда неверны, каждая со строкой «почему здесь это не дефект»;
  • Вопросы по темам — в форме <тема>: <вопрос> (<провенанс>). Не по именам проходов: проход уезжает между метками, а тема остаётся, и вопрос, адресованный проходу, перестал бы задаваться молча в тот день, когда тот уехал в старшую метку. Задаёт вопрос тот, кто закрывает тему на этом прогоне. Адресовать можно только теме: passport, database, adr, research и review — не темы, и вопрос, адресованный им, не задаст никто;
  • Триггеры метки — проектная конкретизация правила выбора метки ревью, тремя списками. Два поднимают, по одному на ось: что в этом проекте считается крупным (объём: сколько узлов и слоёв трогает) и что считается незнакомым (форма решения: известна до начала или нащупывается по ходу). Любая из двух осей поднимает прогон до large, старшей метки, — а она рассчитана на 5–10% задач. Третий список — что считается мелким (опускает до small); он один, потому что вниз метку опускает только совпадение обеих осей сразу. Перечнем мест, узлами или capability, а не вторым определением класса. Уточняет умолчания, а не отменяет их. Рабочее умолчание — medium: миграция схемы и публичный контракт метку не поднимают, их проверяют проходы, которые в medium и так есть;
  • Недоступно проверке — два подраздела, оба по темам: «не проверит ни один проход» (принципиальная граница, по факту промаха не пересматривается) и «перестали проверять сознательно» (пересматривается первым). Тема, у которой в проекте нет дома, сюда не пишется: её и так называет план каждого прогона.

Журнал дефектов: запись на каждый воспроизведённый дефект с пометкой проскочил / пойман ревью. Проскочившие — проверочный набор для калибровки конвейера, выборка по пометке. Пойманные с оракулом — лучшая опора для прохода: проектные, воспроизводимые, однажды оказавшиеся правдой.

tasks/

Каталог задач канону не принадлежит. Его ведёт скилл task-track — своим скриптом и своими проверками; где каталог лежит и как названы его части, говорит секция [tasks] в .av-dev.toml. Версия там одна на всю раскладку: канон и каталог задач двигаются вместе, потому что ведёт их один плагин. Каталог лежит в корне репозитория, вне docs/ — места в docs/ канон ему больше не резервирует (переезд — запись 11 закрытого журнала), а внутрь не смотрит и подавно: docs.py каталог не открывает, его отсутствия не считает дрейфом и согласованность задач не проверяет. Проект, не заведший каталог задач, их не ведёт вовсе, и отказом это быть не может.

Раскладку, форму записи и команды держит скилл av-dev:task-track. Ниже — то, от чего зависит, читается ли проект как продукт.

Индекс работ один — BACKLOG.md, и «что приложение уже умеет» он не отвечает. На этот вопрос отвечают openspec/specs/ (нормативное поведение) и git log индекса (когда и в каком порядке оно появилось). Роадмапа в каноне нет: половину своего вопроса он дублировал беклогом, а вторую — спеками.

У проекта есть стадия, и она решает, что значит порядок строк беклога: build — зависимость, support — важность. Канон её называет, потому что от неё зависит, читается ли список работ как план стройки или как очередь правок; механика — task-track, «Две стадии».

У каждой задачи есть тип, и тип решает, что с ней можно делать. Дом типа — поле меты Тип первой строкой; эмодзи в заголовке от него производна. Словарь закрыт:

Тип Что это
feature снаружи появляется то, чего не было
🐞 fix поведение расходится с заявленным
🧹 chore обслуживание, поведение не меняется
🔬 research исход — знание, а не изменение

Схемы записи здесь нет намеренно. Какие разделы тип требует — скилл av-dev:task-track, раздел «Тип записи», подробно — по файлу на тип в его references/task-<тип>.md. Канон фиксирует словарь, потому что от него зависит, читается ли проект как продукт; схема — механика ведения задач, и второй её экземпляр разошёлся бы с первым (он и разошёлся: канон успел объявить цель у fix запрещённой, хотя она была необязательна, — и целей теперь нет вовсе).

Схема требуется к взятию в работу, а не к заведению: беклог пополняется чаще, чем разбирается, и требование на входе выгоняло бы в заметки то, что должно лежать задачей. Запись, не собравшая разделы своего типа, — законное состояние беклога; невзятой её делает tasks.py ready.

Отдельного типа для незаполненной записи нет: «ещё не описано» — состояние, а не род работы, и называется оно research без раздела «Вопрос». Такая запись в работу не берётся и лежит в конце своей категории.

Раскладку, форму записи и алгоритм работы над каждым типом держит скилл av-dev:task-track.

CLAUDE.md

Что это и стек; инварианты с severity рядом с формулировкой — по ним проходы присваивают critical, поэтому severity стоит здесь, а не выводится каждым проходом заново; команды; семантика гейта — чем краснеет безусловно и почему, где логи, что означает исход, чего в гейте намеренно нет, кто и когда обязан гонять дорогое вне гейта.

Плюс то, что нужно git-операциям и проходам и не выводится ниоткуда:

  • имя основной ветки — от неё считается база диффа (git merge-base HEAD <ветка>), в неё коммитит работу конвейер. Угадывание между master и main ломает интеграцию целиком;
  • что запускать запрещено, с путями — рабочая БД, боевой каталог данных, внешние сервисы. Запретом с путями, а не «будь осторожен»;
  • где testdata и что в них лежит; куда писать временное;
  • что считается необратимым — единственный дом: от обратимости зависит вся шкала ранжирования триажа и право проходов на critical;
  • что считается сломанным — красная проверка, обгоняющая развитие; ориентир по размеру порции, если он замерялся. Оба слота читает скилл av-dev:task-groom, и имена их — его; названы они здесь потому, что дом содержимого CLAUDE.md один и он тут.

openspec/config.yaml

Файл канону не принадлежит, и проверяет его тоже не канон. Каталог openspec/ — предпосылка конвейера: без него не работают ни opsx:propose, ни ревью дизайна, ни сверка требований. Заводит его, настраивает и проверяет форму скилл av-dev:code-openspec: там образец файла, там же скрипт openspec.py check. docs.py о файле не говорит ничего.

Канон называет его здесь по одной причине: openspec/specs/дом темы requirements, и без этой строки карта тем неполна. На форму самого config.yaml канон не высказывается.

Одно за каноном всё же остаётся, и это не форма, а единственный дом. Блок context — самое частое место для второго дома: он читается при порождении каждого артефакта, туда удобно дописать «чтобы агент знал», и так заводятся копии инвариантов, конвенций, состава гейта и правил ревью. Расходятся они молча. Разрез: утверждение, которое можно опровергнуть, открыв другой файл проекта, — пересказ; строка, которая говорит, какой файл открыть, — ссылка. Машина этого не различает; судит агент doc-consistency, и config.yaml у него во входе.

Правило единственного дома

Факт живёт ровно в одном файле; остальные ссылаются. Карта на случай спора:

Факт Дом
поведение системы openspec/specs/<capability>/spec.md
почему решено так adr/, источник — архивный design.md либо записка разведки
граница домена, «чем не является» passport.md
инвариант и его severity CLAUDE.md
что осталось сделать и в каком порядке tasks/BACKLOG.md
измеренное число research/
настройка с числовым значением database.md
периметр и модель угроз security.md
что необратимо CLAUDE.mdне architecture.md
единые точки проекта architecture.md
имя основной ветки, testdata, временный каталог CLAUDE.md
что уже механизировано правилом conventions.*, раздел «Механизировано»

Пустое называется пустым

Скелет канона заводится целиком с первого дня. Незаполненный документ держит одну честную информативную строку, а не заглушку:

  • «внешних зависимостей нет — смотри на диск и на СУБД»;
  • «наблюдений на живых данных нет: внешний источник один, формат документирован»;
  • «прецедентов не накоплено»;
  • «сознательно ничего не отключали»;
  • «архитектуры пока нет: кода нет, заводится первой задачей».

Проход читает такую строку как факт и не тратит на неё обязательный вопрос. Отсутствие файла он не может прочитать никак, а «TBD» читает как пробел — поэтому docs.py check отличает честную строку от нетронутого плейсхолдера шаблона и напоминает о втором.

Слотов нет

Файлы и каталоги, которых в каноне нет, и куда уезжает их содержимое:

Было Куда
docs/review-brief.md документы канона и есть бриф; остаток — в review.md
docs/specs/ openspec/specs/ (поведение) и architecture.md (обзор)
docs/drafts/ идея → запись research; отказ → ADR; порядок → BACKLOG.md; размышление → opsx:explore
docs/plan.md tasks/BACKLOG.md
BRIEF.md passport.md
docs/backlog/ tasks/ в корне репозитория
docs/review-journal.md, docs/review/journal.md docs/review.md

Что проверяет машина, а что человек

Граница объявляется вслух в каждом отчёте: check, отчитавшийся «канон соблюдён» на проекте, где из шести файлов три лишние, хуже отсутствующего.

Проверяет docs.py Судит агент Какой
отсутствующие пути канона смысловой дубль документа и capability doc-consistency
файлы в docs/ вне канона поведение, оставшееся в architecture.md doc-consistency
имя файла не kebab-case латиницей; форма имени ADR транслит в имени — сверх эвристики doc-wording
битые относительные ссылки прямое противоречие между документами doc-consistency
версия канона и её отставание достаточность честной строки в пустом слоте doc-consistency
нетронутый плейсхолдер шаблона ADR без ссылки на источник, замена без парного статуса doc-consistency
маркеры долга — числом протухший факт, разошедшийся с кодом doc-code-drift
миграция изменена, а database.md нет зависимость в манифесте, не названная в обзоре doc-code-drift
capability без упоминания в architecture.md второй способ там, где обзор обещал единственный doc-code-drift
пересказ документа канона в context вместо ссылки doc-consistency
придирки валидатора: сменились ли они никакой — проявляются отказом openspec validate --strict
связность и читаемость doc-wording

Форма openspec/config.yaml в левой колонке отсутствует не по забывчивости. С канона 10 docs.py о файле не говорит ничего: имя, schema, незаменённый пример, адреса паспорта и CLAUDE.md, ключи rules и сторож версии OpenSpec — всё это смотрит openspec.py check скилла av-dev:code-openspec. Плагина конвейера в проекте может не быть; тогда форму не проверяет никто, и это строка доклада.

Агентов двое, и разведены они по глубине, а не по охвату. doc-consistency читает только docs/ и openspec/, doc-code-drift — весь репозиторий и гоняет читающие команды. Слитый агент делал бы одну половину поверхностной; тот же разрез, что между task-form и task-wording.

Зовутся оба одинаково и одним скиллом — av-dev:doc-healthcheck, на весь канон разом; шагом adopt и шагом upgrade его зовёт canon. Не на синке документации: doc-consistency на opus по каждой сделанной задаче не окупается, а расхождение между двумя документами по определению требует двух, и на большинстве задач синк правит один. Пачка, отбираемая работой, вдобавок не видит того, чего работа не касалась, — а именно там расхождение и живёт: правка отменяет решение в одном документе, парный статус нужен в другом.

Перечень фактов, которые doc-code-drift сверяет с кодом, закрыт — имя основной ветки, команды, пути, зависимости поимённо, настройки с числовым значением, единые точки проекта, capability, проверяемые инварианты. «Сверить архитектуру с кодом» задача без дна, и агент, которому её поставили, выдаёт правдоподобную труху вместо находок.

.av-dev.toml

# Раскладка av-dev в этом проекте: версия и настройки проверок.

version = 1                            # версия раскладки

[docs]
migrations = "internal/store/migrations"  # если БД есть

[tasks]
dir = "tasks"                          # каталог задач от корня репозитория

version — версия раскладки, под которую проект приведён, целым числом: обратной совместимости нет, есть «приведён» и «не приведён». Число подставляет init, adopt или upgrade, и берётся оно из docs.py version, а не из образца: литерал в образце протухает на первом же повышении. [docs] migrations — путь каталога миграций, если БД есть; по нему docs.py делает сверку с database.md. [tasks] — где лежит каталог задач и как названы его части; состав ключей описывает скилл task-track.

Формат TOML взят ради комментариев. Файл лежит в репозитории проекта, и человек, открывший его через полгода, обязан прочитать в нём, что означает число. JSON комментариев не знает, и объяснение приходилось держать в другом файле. Отсюда же правило записи: скрипты правят строку, а не переписывают файл — перезапись стёрла бы то, ради чего формат и выбран.

Файл один, и лежит он в корне. До слияния плагинов их было два — docs/.docs.json с версией канона и <каталог задач>/.tasks.json с версией формата задач, — и версии двигались порознь, потому что плагины ставились порознь. Плагин теперь один, версия одна, а корень выбран потому, что он есть и у проекта без docs/, и у проекта без каталога задач. Прежние имена не читаются: два дома для одной версии расходятся молча. Увидев их, docs.py и tasks.py говорят «прежняя раскладка» и зовут upgrade — версия 1 журнала.

Ключей будет больше по мере роста проверок; неизвестный ключ docs.py игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом строкой, а не молчит.