Слияние ничего из идей не тронуло, но сделало дешёвым дом для правила, натянутого между скиллами. Заведён shared/axes.md — дом перечня, а не значений: девять осей, их адреса и чего каждая не решает. Механика остаётся у владельца. Целиком сюда переехали две оси, у которых владельца не было. Коды выхода объявлялись общим словарём в одиннадцати местах, и каждое объявление называло свой набор соседей; машина их не сверяла, потому что copies.py смотрит markdown, а перечни лежали в docstring'ах. Теперь дом один, скрипты держат указатель, а три SKILL.md — помеченную копию, потому что на кодах они ветвятся. Режим прогона (с меткой, без метки) был размазан по четырём файлам и осью назван не был, хотя в уставе review-basics задаёт саму возможность запуска. Разведены два значения слова «стадия»: ступени 1-5 внутри прогона кода, стадии дизайна и кода снаружи. Карта нашла ошибку в себе: клетка «категория документа × метка» пустой не была — review-basics приёмник проектных тем при любой метке. Пустой оказалась соседняя: на прогоне без метки план фиксирован, и своих тем проекта в нём нет вовсе. Обе оставшиеся пустоты названы вслух, а не заполнены наугад.
50 KiB
Канон документов проекта
Номер версии здесь не стоит намеренно. Этот файл описывает канон таким, какой
он сейчас, а число живёт в двух домах, которые не расходятся: константа в
docs.py (её печатает docs.py version) и верхняя запись
журнала. Литерал в шапке был третьим и отстал на первом же
повышении — версию 13 он пережил, объявляя канон двенадцатым.
Это единственный дом определения канона. Скиллы doc-init, doc-canon и doc-sync
читают его, а не пересказывают: три описания одной раскладки разъедутся, и
работать будет то, которое прочитали последним. Меняется канон — меняется этот
файл и появляется запись в changelog.md.
Зачем канон жёсткий
Пути фиксированы, и проект под них подгоняется, а не наоборот. Причина не техническая: проектов много, все малого и среднего размера, и ориентироваться в слегка похожих, но разных раскладках дороже, чем один раз привести их к общей. Рядом лежит OpenSpec, у которого структура тоже строгая.
Цена принята сознательно: плагин не переносится на чужой репозиторий как есть —
чужой репозиторий приводится к канону скиллом doc-canon.
Раскладка отвечает, где текст лежит и на какой вопрос отвечает. Каким он должен быть словами — общий для всех документов канона файл shared/language.md: информационный стиль, англицизмы, жаргон. Он относится и к задачам, и к решениям ADR, и к запискам разведки.
Сопровождение и эксплуатация — целое и часть
Словарь этой темы — shared/operations.md:
целое и часть, три места одной темы (секция роадмапа, раздел архитектуры, тема
ревью 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.py каталог не открывает, его отсутствия не считает дрейфом и согласованность
задач не проверяет. Проект, не заведший каталог задач, их не ведёт
вовсе, и отказом это быть не может.
Раскладку, форму записи и команды держит скилл av-dev:task-track. Ниже — то,
от чего зависит, читается ли проект как продукт: канон высказывается об этом
потому, что роадмап отвечает на вопрос о системе, а не о работах.
ROADMAP.md отвечает на «что приложение уже умеет и чего ещё не умеет». Это
не очередь работ: цель — возможность приложения, задача — шаг к ней.
Достигнутая цель из роадмапа не исчезает — строка с датой переезжает в
секцию достигнутого, потому что «что умеет» и есть половина вопроса, ради
которого документ открывают. Вторым домом поведения роадмап при этом не
становится: нормативное поведение живёт в openspec/specs/, роадмап отвечает,
когда и в каком порядке оно появилось.
У каждой записи есть тип, и тип решает, что с ней можно делать. Дом типа —
поле меты Тип первой строкой; эмодзи в заголовке от него производна. Словарь
закрыт:
| Тип | Что это |
|---|---|
🎯 goal |
возможность приложения |
✨ 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/ROADMAP.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; порядок → ROADMAP.md; размышление → opsx:explore |
docs/plan.md |
tasks/ROADMAP.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 его зовёт doc-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
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
строкой, а не молчит.