--- name: doc-sync description: "Вести содержимое документов канона по ходу разработки. Правки двух родов, и спрашивается один: отражение сделанного (вливание дельт, миграция в database.md, компонент в architecture.md) пишется молча, а новая запись и новая норма (ADR, правило в conventions, записка в research, инвариант CLAUDE.md, периметр security.md, граница passport.md, дефект в review.md) только предлагается — пишет её второй запуск после слова человека. Построчный отчёт по каждому документу остаётся: каждый назван либо правкой, либо предложением, либо отрицанием с причиной. ADR и записка разведки — промоут цитатой из архивного design.md или записки, а не второе сочинение. Синк же считает и выдаёт строкой сигнал сверки: сколько задач сделано с прошлого прогона av-dev:doc-healthcheck, читая след в ключе [docs] healthcheck_last. Использовать, когда задача сделана и надо обновить документацию, когда просят завести ADR или записать решение, занести находку о внешних данных, записать проскочивший дефект, разгрузить разросшуюся архитектуру. Раскладку и соответствие канону проверяет скилл av-dev:canon." --- # Ведение содержимого канона Скилл владеет **содержимым** документов канона; раскладкой владеет `canon`. Определение канона и роли документов — [канон](../canon/references/canon.md), здесь не пересказывается. Главный вызывающий — **шаг синка документации в конвейере задачи**: скилл `av-dev:code-resolve` зовёт этот по имени. Задачу ведут не конвейером — документация ведётся тем же скиллом вручную. ## Правило, из которого всё следует **Принуждённое отрицание.** Синк обязан назвать **каждый** документ канона: либо чем он обновлён, либо «не требуется, потому что…». Нетронутые группируются одной строкой с общей причиной. Причина, по которой правило именно такое, измерена: у ADR был список триггеров прозой — и он дал **6 записей на 43 изменения**. Прозаический триггер, который некому проверить, не срабатывает. Отличить «не написал» от «написал, что не требуется» можно только тогда, когда отрицание обязательно. Это тот же приём, что «границы покрытия» в отчёте ревью и «пустое называется пустым» в каноне. ## Два рода правок, и спрашивается один Второе правило, поперёк первого: **пройти по всем документам обязан ты, а завести новое — человек**. Признак проверяемый и читается одним вопросом: **что станет с документом, если правку не сделать**. - **Отражение** — документ уже описывает эту вещь, и без правки он **станет ложным**: миграция написана, а `database.md` её не знает; компонент заведён, а `architecture.md` перечисляет прежние. Такая правка ничего не решает, она договаривает решённое на чекпоинте и уже стоящее в коде. **Пишется молча.** - **Новое** — в каноне заводится запись или норма, которой не было: ADR, правило в конвенциях, записка в `research/`, инвариант в `CLAUDE.md`, сдвиг периметра в `security.md`, граница в `passport.md`, дефект в журнале `review.md`. Такая запись переживёт задачу и свяжет следующие. **Пишется только по слову человека.** **Показывается новое одной репликой и одним списком.** Каждый пункт — строкой: что заведём, куда и на каком основании. Человек отвечает разом, и одобренное пишет **следующий заход синка** — в цикле задачи это третий такт шага 6 (`av-dev:code-resolve`, `references/solve.md`). **Нового нет — реплики нет**, и это обычный исход: у большинства задач хвост состоит из одного отражения. **«По слову» — это по слову, а не вторым вопросом.** Человек уже сказал в этом прогоне «заведи ADR», сам решил сузить проверки, сам одобрил формулировку конвенции — слово сказано, и переспрашивать нечего: запись идёт как одобренная, а в докладе стоит, чьим решением. Предложение существует ради нового, которое заметил ты, а не ради ритуала. **Отказ человека — строка доклада и всё.** В документы он не пишется: журнала отвергнутых ADR и снятых конвенций канон не держит, и заведение такого журнала здесь было бы ровно тем новым, которого никто не заказывал. **Отрицание от этого не ослабло.** Документ, по которому нечего предложить, по-прежнему обязан быть назван — просто раньше отрицание читал отчёт, а теперь человек, и читает он его **до** того, как что-то написано. Обязанность та же: пропуск неотличим от «не требуется», пока отрицание не сказано вслух. ## Чек-лист синка Идёт сверху вниз; каждая строка попадает в доклад. | Документ | Род | Обновляется, когда | Проверка | | --- | --- | --- | --- | | `openspec/specs/` | отражение | всегда при изменении поведения | вливает `opsx:archive` | | `database.md` | отражение | тронуты миграции | `docs.py check --base` | | `architecture.md` | отражение | новый компонент, граница, внешняя зависимость, изменилось окружение | `docs.py`: capability без упоминания | | `adr/` | новое | сработал триггер канона (перечень — [canon.md](../canon/references/canon.md#adr)) | нет — только этот чек-лист | | `research/` | новое | узнали новое о внешнем формате или данных | нет | | `security.md` | новое | новый недоверенный вход, токен, путь наружу, сдвиг периметра | нет | | `conventions/` | новое | находка принята и не специфична для одного места | промоут | | `review.md` | новое | дефект воспроизведён; сузили или расширили проверку | нет | | `passport.md` | новое | новый потребитель, сдвиг границы «чем не является» | нет | | `CLAUDE.md` | новое | изменился инвариант, гейт, запрет, необратимое | нет | **Разрез в таблице не произволен.** Ложным без правки становится ровно тот документ, который описывает **состояние системы**, — потому отражений в чек-листе и мало. Остальные задают норму или хранят память: им не с чем разойтись, пока в них не написано новое. Пример доклада: ``` Синк документации. Отражено, записано: - openspec/specs/ — влиты дельты change add-bucket-reindex - architecture.md — добавлен воркер свёртки, ссылка на capability reindex - database.md — миграция 00006, таблица bucket Предложено, жду слова: - adr/ — отказ от внешней очереди в пользу таблицы; источник: архивный design.md; триггер: намеренный отказ от очевидного подхода Не требуется: research, security, conventions, review, passport, CLAUDE.md — периметр не двигался, инварианты те же, новое о внешних данных не узнано. Сверка документов: с прошлой (a1b2c3d, 2026-07-30) сделано 11 задач — пора звать av-dev:doc-healthcheck. ``` ## Сверка — не здесь, а в `av-dev:doc-healthcheck` Синк правит документы поодиночке, а расходятся они **между собой**: факт, дописанный в `architecture.md`, уже живёт в `CLAUDE.md`; периметр в `security.md` не знает про новый эндпоинт. Поймать это на своей же правке нельзя, и судит это агент `doc-consistency`. **Но синк его не зовёт.** Обоими судьями документов владеет скилл `av-dev:doc-healthcheck`, и зовут их на весь канон разом, а не на пачку, отобранную работой. Причина в цене: `doc-consistency` на `opus` по каждой сделанной задаче — самая дорогая церемония процесса, а `doc-code-drift` хоть и на `sonnet`, но читает репозиторий целиком. К тому же расхождение между двумя документами по определению требует двух документов, а на большинстве задач синк правит один. Что теряется: привязка находки к задаче, которая её породила. Что выигрывается, кроме денег: синк перестаёт отбирать пачку, и в неё попадают документы, которых работа не касалась, — расхождение, внесённое правкой в одном месте, там и живёт. ## Вычитка — наоборот, здесь **Язык правленого вычитывается тем же прогоном, который его написал, и зовёшь агента `doc-wording` ты.** Довод обратный доводу про судей: он читает **только названную пачку**, стоит дёшево и ищет ровно то, что портится в момент письма, — залог, оценку без факта, жаргон, термин без ввода. Ждать `doc-healthcheck` здесь нечего: через месяц никто уже не помнит, какую фразу имел в виду автор. Позови его **последним шагом правки, до коммита**, отдав список файлов, которых она коснулась, — и назови этот список в промпте: по нему же он судит, известен ли термин. Находки он отдаёт готовыми формулировками, подставляешь их ты. **Условие вызова — правка, а не синк.** Синк самый частый вызывающий, но не единственный: разведка (`av-dev:code-resolve`, сценарий разведки) пишет ответ по одному адресу и синком себя не считает намеренно — вычитка ей нужна ровно та же. Признак один и читается буквально: **документы правились — зови, ничего не правил — не зови**. ## ADR — промоут, а не второе сочинение Обоснование уже написано: `opsx:propose` кладёт `design.md` в каждый change, и после архивации он лежит в `openspec/changes/archive//design.md` с разделами `Context` / `Goals / Non-Goals` / `Decisions` / `Risks / Trade-offs`. **ADR цитирует решение оттуда и ссылается на источник.** Не пересказывает и не сочиняет заново. **Второй законный источник — записка разведки**, и приходит он от скилла `av-dev:code-resolve`, сценарий разведки: решение, принятое разведкой (намеренный отказ, выбор подхода, «проверили и не делаем»), `design.md` не имеет по построению — change по нему не будет никогда. Промоут при этом тот же: цитата и ссылка, но на записку. Перечень источников закрыт и живёт в [каноне](../canon/references/canon.md), раздел `adr/`. **Триггер заведения, форма имени и правило замены — в [каноне](../canon/references/canon.md), раздел `adr/`.** Здесь они не повторяются: копия правила расходится с оригиналом на первой же смене версии канона, а расходится незаметно. Твоя часть — **применить триггер к этой задаче и сказать вслух, сработал он или нет**. Строка «adr/ — не требуется: решение рутинное» и есть то, ради чего чек-лист существует; её отсутствие неотличимо от «забыл посмотреть». **Запись — новое, и заводится она по слову** (раздел «Два рода правок»). Сработавший триггер даёт не файл, а строку предложения: какое решение, из какого источника, каким из трёх триггеров прошло. Своей записи ADR не стоит ничего, а каталог решений читают как список того, что в проекте всерьёз, — и разбавленный рутиной он перестаёт им быть. Порядок работы после «да»: открой источник — архивный `design.md` change либо записку разведки, — найди в нём решение, проходящее триггер, процитируй его и причину, сошлись на источник, добавь строку в индекс `docs/adr/README.md` сверху. ## Чистка `architecture.md` Обзор не держит поведение — его нормативный дом `openspec/specs/`. **Форма маркера долга и правило «гейт от них не краснеет» — в [каноне](../canon/references/canon.md), раздел `architecture.md`.** Разбирается порциями: раздел вычищает та задача, которая его касается. Содержимое не выбрасывается, а переезжает — требования в дельта-спеку change, обоснование в ADR, обзор остаётся строкой со ссылкой на capability. ## Запись в `research/` Наблюдение о внешнем мире: что реально шлёт источник, чем документация формата расходится с практикой. **Требование происхождения и правило про расходящееся число — в [каноне](../canon/references/canon.md), раздел `research/`.** Твоя часть — заметить, что по ходу задачи узналось новое о внешних данных, и не дать этому остаться в контексте. Признак: ты правил разбор, опираясь на то, чего нет ни в одном документе. ## Чего может не быть Два раздела ниже — запись в `review.md` и промоут в конвенции — берут форму у конвейера ревью: она принадлежит ему, а не канону. Берут **вызовом скилла**, а не чтением файла по пути. **Копия.** Дом правила — `shared/absence.md` в репозитории плагина. Правится дом, а не этот файл. **Скилл не вправе считать раскладку проекта полной.** Части заводятся порознь и живут порознь; каждая узнаётся своим следом: | Чего нет | Как видно | Чего теперь не делает никто | | --- | --- | --- | | настройки av-dev | нет `.av-dev.toml` в корне | проект под процесс не заводился; версии нет, настроек нет | | документы канона | нет `docs/` | проектную конкретику брать неоткуда — темы, инварианты, прецеденты | | учёт работ | нет каталога задач | запись остаётся владельцу: назови её текстом в докладе | | источник требований | нет `openspec/config.yaml` | цикл SDD не запускается: спеки не с чем сверять | **Свой скилл зовётся полным именем** — `av-dev:canon`, `av-dev:task-track`, `av-dev:code-review`. Короткое имя может разрешиться в устаревшую проектную копию из `.claude/skills/`, и подмены не будет видно ни в докладе, ни в поведении. **Внешний плагин может не стоять.** Их два: `opsx:*` — цикл SDD, и `av-dev-git:commit` — сообщения коммитов. Путь в дерево чужого плагина не пишется никогда: `$CLAUDE_PLUGIN_ROOT` ведёт только в своё дерево, а вычисленный от него путь к соседу либо не откроется, либо откроет чужую установку. Нужен чужой справочник — зови владеющий им скилл, он прочитает его сам. **Отсутствие — исход, а не поломка.** Назови строкой доклада, чего теперь не делает никто, и продолжай работу. Молчать нельзя: пропуск неотличим от сделанного. Выдумывать обходной путь нельзя тоже. **Присутствие узнаётся следом в проекте, а не объявлением.** Перечня того, что здесь заведено, проект не ведёт — он разошёлся бы с действительностью молча. Здесь это значит: документов канона может не быть вовсе — тогда синка нет, и это исход, а не повод раскладывать документы по своему усмотрению. ## Запись в `review.md` Файл держит два раздела с разными сроками жизни — журнал дефектов и настройку конвейера. **Что в каком и в какой форме — в [каноне](../canon/references/canon.md), раздел `review.md`**; подробности формы записи и выбор адреса, куда она ведёт, — у конвейера ревью: `Skill av-dev:code-review`, его `references/review-journal.md`. Твоя часть на синке: **дефект пишется сразу**, а не «потом, когда починим». Со временем теряется не факт, а то, почему дефект не поймали, — единственное, ради чего журнал есть. «Сразу» и «по слову» здесь не спорят: запись — новое, и она идёт предложением, но **предложением этого прогона**, а не следующего. Отложить её до «когда починим» нельзя ни с чьего согласия: чинится дефект, а теряется причина промаха. **Решение сузить проверки** (перестали звать проход, переселили его в другой скилл) обязано попасть в раздел настройки, а не остаться в отчёте ревью. Второй раз оно не спрашивается: такое решение принимает человек по определению, и слово по нему уже сказано — сказано тогда, когда проверку сузили. ## Промоут в конвенции Находка → конвенция → правило линтера → **удаление из прозы**. Процедура целиком принадлежит конвейеру ревью — его `references/promote.md`, читается через `Skill av-dev:code-review`; роль каталога конвенций — в [каноне](../canon/references/canon.md). **Прогон идёт вне конвейера** (находку принесли руками) — три шага всё равно твои, просто без его процедуры: сформулируй правило, поищи, чем оно механизируется, и вычеркни прозу, если механизировалось. Твоя часть — **третий шаг, который пропускают чаще всего**: правило заработало, а формулировка осталась в прозе, и проход продолжает проверять уже проверенное. На синке это отдельная строка: «conventions/ — правило X механизировано, формулировка удалена» либо «не требуется». **Конвенция — самое дорогое из нового, и на синке она только предлагается.** Одна её строка становится входом каждого следующего прогона ревью и критерием для всех будущих задач; находка, доехавшая до конвенции по инерции хвоста, потом годами разменивается на внимание прохода. Предложение называет **проверяемое свойство и проход, который его нашёл**, — по этой паре человек и решает. **Шаг 2 в хвост задачи не помещается.** Механизация правила — конфиг линтера или сканер, плюс приведение кода к зелёному — это работа размером с задачу, и делать её попутно значит удваивать чужой прогон. Согласованный промоут даёт строку конвенции сейчас и **задачу типа `chore`** на механизацию — заводит её `av-dev:task-track`, и заводится она тем же словом человека, что и сама конвенция. ## Сигнал сверки — строка, а не вызов Сверку документов (`av-dev:doc-healthcheck`) зовёт человек по признаку **«с прошлой сверки сделан десяток задач»**. Признак наблюдаемый, но считать его было нечем: следа у сверки не оставалось, и «десяток» держался в чьей-то памяти. Это ровно тот прозаический триггер, который дал 6 записей ADR на 43 изменения, — и здесь он не срабатывал по той же причине. **След оставляет сама сверка** — ключ `healthcheck_last` в секции `[docs]` файла `.av-dev.toml` (состав ключей — [canon.md](../canon/references/canon.md), раздел `.av-dev.toml`). **Считает синк**, и вот чем: ```sh git rev-list --count ..HEAD -- openspec/changes/archive ``` Коммит, тронувший архив, — это доехавшая до конца задача, так что счёт идёт в задачах, а не в правках. `openspec` в проекте нет — считай коммиты (`git rev-list --count ..HEAD`) и **скажи, что считал коммиты**: число другого рода, и молчаливая подмена сделала бы признак вдвое чувствительнее. Строка доклада обязательна всегда, и вариантов у неё три: - **счёт меньше десятка** — «с прошлой сверки N задач, звать рано»; - **счёт от десятка** — «с прошлой сверки N задач, пора звать `av-dev:doc-healthcheck`»; - **ключа нет** — «сверка документов не проводилась ни разу», и это самый сильный из трёх сигналов, а не отсутствие данных. **Сам не зовёшь.** Прогон сверки идёт по всему канону и держит `opus`; решение о таких часах принимает тот, кто их оплачивает. Синк, позвавший её сам, превратил бы самую дорогую проверку процесса в церемонию хвоста задачи — против чего она и вынесена в отдельный скилл. ## Чего этот скилл не делает - **Не проверяет раскладку** — это `canon`. - **Не заводит недостающие документы** — их скелет кладёт `canon adopt` или `doc-init`. - **Не сочиняет содержание.** Нечего записать — так и пишется, честной строкой. - **Не переоформляет документы «заодно»**: правится то, чего коснулась работа. - **Не заводит новое молча** — ни ADR, ни конвенцию, ни записку. Молча идёт только отражение, и признак у него один: без правки документ станет ложным. - **Не зовёт сверку документов** — считает и говорит строкой; зовёт человек.