Ревью трансформации нашло, что тема 78 спорит сама с собой в четырёх местах. Учёт был отдан агенту, хотя перечень оркестратора объявлен закрытым, а границы задания прямо говорят «задач не заводит»: вызов av-dev:task-track вернулся оркестратору, агенту третьего такта осталось письмо в документы. Ветка отказа не была покрыта — на ответе «ничего» правка первого такта уезжала в коммит невычитанной и с непрогнанным гейтом. Теперь третий такт идёт всякий раз, когда была реплика; не идёт он только тогда, когда реплики не было вовсе. Дом правила вычитки в doc-sync знает про два захода. Барьер карты кластеров из сценария «задачи из ревью и аудита» снимается там, где его уже прошли: список показан человеку и получил ответ. При прямом вызове и вызове из code-deep-review карта по-прежнему вопрос. Счёт стопов сведён в таблицу по сценариям; у обслуживания появился второй заход и одна реплика с поводом «новый запрет или инвариант». Сигнал сверки считается по архиву change и каталогу задач разом — иначе chore и research не считались вовсе — и вошёл в возврат агента и в доклады трёх сценариев. Ось «род правки документа» внесена в перечень осей. Журнал — тема 80; отложенный старший долг назван в С287.
358 lines
31 KiB
Markdown
358 lines
31 KiB
Markdown
---
|
||
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/<id>/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/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 <last>..HEAD -- openspec/changes/archive <каталог задач>
|
||
```
|
||
|
||
Считаются коммиты, тронувшие **архив change или каталог задач** (его путь — ключ
|
||
`[tasks] dir`). Оба пути выбраны потому, что доведённая до конца задача оставляет
|
||
след хотя бы в одном: решение архивирует change, а обслуживание и разведка change
|
||
не заводят вовсе и видны только закрытием — правкой индексов учёта. Считать один
|
||
архив значило бы не считать `chore` и `research`, то есть на проекте с их
|
||
перевесом говорить «звать рано» вечно.
|
||
|
||
Ни `openspec`, ни каталога задач в проекте нет — считай коммиты
|
||
(`git rev-list --count <last>..HEAD`) и **скажи, что считал коммиты**: число
|
||
другого рода, и молчаливая подмена сделала бы признак вдвое чувствительнее.
|
||
Постановка, пришедшая текстом, следа не оставляет ни там ни там — такие задачи в
|
||
счёт не входят, и это тоже говорится строкой, когда прогон шёл текстом.
|
||
|
||
Строка доклада обязательна всегда, и вариантов у неё три:
|
||
|
||
- **счёт меньше десятка** — «с прошлой сверки N задач, звать рано»;
|
||
- **счёт от десятка** — «с прошлой сверки N задач, пора звать
|
||
`av-dev:doc-healthcheck`»;
|
||
- **ключа нет** — «сверка документов не проводилась ни разу», и это самый
|
||
сильный из трёх сигналов, а не отсутствие данных.
|
||
|
||
**Сам не зовёшь.** Прогон сверки идёт по всему канону и держит `opus`; решение о
|
||
таких часах принимает тот, кто их оплачивает. Синк, позвавший её сам, превратил
|
||
бы самую дорогую проверку процесса в церемонию хвоста задачи — против чего она и
|
||
вынесена в отдельный скилл.
|
||
|
||
## Чего этот скилл не делает
|
||
|
||
- **Не проверяет раскладку** — это `canon`.
|
||
- **Не заводит недостающие документы** — их скелет кладёт `canon adopt` или
|
||
`doc-init`.
|
||
- **Не сочиняет содержание.** Нечего записать — так и пишется, честной строкой.
|
||
- **Не переоформляет документы «заодно»**: правится то, чего коснулась работа.
|
||
- **Не заводит новое молча** — ни ADR, ни конвенцию, ни записку. Молча идёт
|
||
только отражение, и признак у него один: без правки документ станет ложным.
|
||
- **Не зовёт сверку документов** — считает и говорит строкой; зовёт человек.
|