Проход упрощения уткнулся в один класс у всех пяти агентов: слово, живущее в трёх-шести файлах разом. Правка в одном месте развела бы словарь, правка во всех — уже не упрощение текста скилла. Каждый честно остановился и записал слово в отчёт, и одни и те же слова всплыли в разных отчётах. Разобрано этим проходом. Причина, по которой они вообще накопились, оказалась в самом уставе языка. Он разрешал не переводить «термин, у которого нет точного русского эквивалента и который в команде уже прижился». Проверить это нельзя: прижившимся выглядит любое слово, встреченное трижды, — и ровно так рассудили пять агентов подряд, каждый независимо. Оговорка заменена закрытым списком из девяти терминов с колонкой «что называет»: интейк, триаж, провенанс, дедуп, чек-лист, дифф, промпт, сущности OpenSpec, роды проходов ревью. Интейк оставлен потому, что «заведение» называет создание файла, и слить их значит смешать две операции; провенанс — потому что «источник» рядом называет саму запись, а не свойство числа. Слово не из списка и не из таблицы имён вещей — находка, а не стиль. Список заведён домом язык-словарь в language.md и копией в уставе doc-wording. Копия обязательна: агент работает в репозитории проекта, где плагина может не быть, и без списка предъявил бы интейк как англицизм. Снято пять слов, 29 мест: конфляция → смешение, декорреляция → разведённость, непоймание → почему не поймали, эвал-сет → проверочный набор, гайд → руководство. Латинизм или калька при живом русском слове в каждом случае. Разбор декорреляции показателен: проект уже владел нужным словом — «агенты разведены по глубине», «разведены по охвату» — и держал рядом латинский синоним того же понятия. Это не англицизм, а второй дом для слова. Непоймание снято ещё и потому, что форма журнала дефектов, которую канон кладёт в проекты, спрашивает «Почему не поймали», а проза рядом называла это «причиной непоймания». Скелет и проза о скелете говорили разными словами. Снятое записано вместе с оставленным, в одном списке и с заменой каждого. Иначе слово возвращается: из текстов оно уходит, но ничто не мешает следующему проходу завести его заново — оно ведь короткое и точное на вид. Тема 32 в DECISIONS.md, следствия 124-126. Нумерация правил в уставе doc-wording сдвинута: словарь встал шестым, жаргон и далее уехали на единицу. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
443 lines
34 KiB
Markdown
443 lines
34 KiB
Markdown
# Канон документов проекта
|
||
|
||
**Версия 4.**
|
||
|
||
Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs`
|
||
читают его, а не пересказывают: три описания одной раскладки разъедутся, и
|
||
работать будет то, которое прочитали последним. Меняется канон — меняется этот
|
||
файл и появляется запись в [changelog.md](changelog.md).
|
||
|
||
## Зачем канон жёсткий
|
||
|
||
Пути фиксированы, и проект под них подгоняется, а не наоборот. Причина не
|
||
техническая: проектов много, все малого и среднего размера, и ориентироваться в
|
||
слегка похожих, но разных раскладках дороже, чем один раз привести их к общей.
|
||
Рядом лежит OpenSpec, у которого структура тоже строгая.
|
||
|
||
Цена принята сознательно: плагин не переносится на чужой репозиторий как есть —
|
||
чужой репозиторий **приводится** к канону скиллом `canon`.
|
||
|
||
Раскладка отвечает, **где** текст лежит и на какой вопрос отвечает. Каким он
|
||
должен быть **словами** — общий для всех документов канона файл
|
||
[language.md](language.md): информационный стиль, англицизмы, жаргон. Он
|
||
относится и к задачам, и к решениям ADR, и к запискам разведки.
|
||
|
||
## Сопровождение и эксплуатация — целое и часть
|
||
|
||
Одна тема живёт в трёх местах канона, и путать их слова нельзя.
|
||
|
||
**Сопровождение** — всё, чем держат проект: инструмент и сборка, процесс,
|
||
выкладка, метрики и логи, инфраструктура, дежурство. **Эксплуатация** — его
|
||
часть: работа системы на проде. Целое и часть, и никогда наоборот.
|
||
|
||
| Место | Уровень | Что там |
|
||
| --- | --- | --- |
|
||
| `ROADMAP.md`, секция `Сопровождение` | план | **работы**, которые собираемся делать: цели и их задачи |
|
||
| `architecture.md`, раздел «Эксплуатация» | состояние | **как устроено сейчас**: где работает, что рядом, кто перезапускает |
|
||
| эксплуатационный проход ревью | оптика | **чем проверяем**: «это упало через неделю на проде» |
|
||
|
||
Слово **«поддержка» не употребляется вовсе** — в нём слышится помощь
|
||
пользователю, а это другая работа.
|
||
|
||
**Граница с возможностями проходит по тому, кто наблюдает.** «Приложение
|
||
сообщает о своём состоянии» — возможность приложения, её место среди прочих
|
||
целей: наблюдает пользователь сервиса. «Дежурный видит состояние на одном
|
||
экране» — сопровождение: наблюдаем мы. Одни и те же метрики попадают в разные
|
||
секции роадмапа, и это верно — секции отвечают на разные вопросы.
|
||
|
||
## Раскладка
|
||
|
||
```
|
||
CLAUDE.md памятка агенту: что это, стек, инварианты с
|
||
severity, команды, семантика гейта, запреты
|
||
docs/
|
||
.pm.json версия канона и пути, нужные проверкам
|
||
passport.md зачем и для кого; чем НЕ является; сценарии
|
||
architecture.md как сложено — обзор; окружение и эксплуатация
|
||
database.md схема хранилища; представление данных и настройки
|
||
security.md периметр; недоверенный вход; что вне модели
|
||
conventions/
|
||
README.md индекс, правило промоута, что механизировано
|
||
<slug>.md
|
||
research/
|
||
README.md как снималось, индекс
|
||
<slug>.md наблюдения и числа с провенансом
|
||
adr/
|
||
README.md индекс записей, статусы, правило замены
|
||
template.md
|
||
ADR-ГГГГ-ММ-ДД-slug.md
|
||
review.md настройка конвейера под проект + журнал дефектов
|
||
tasks/ скилл tasks: items/, ROADMAP.md, BACKLOG.md,
|
||
SPRINT.md, REJECTED.md
|
||
openspec/
|
||
config.yaml только нужды генерации артефактов + ссылки
|
||
specs/<capability>/spec.md что система делает — нормативно
|
||
changes/archive/ архив изменений с design.md — сырьё для 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` потом
|
||
показывает, что ссылки целы.
|
||
|
||
## Роли документов
|
||
|
||
Одна строка на каждый — на какой вопрос он отвечает и кто его читает.
|
||
|
||
| Документ | Вопрос | Кто читает, кроме человека |
|
||
| --- | --- | --- |
|
||
| `CLAUDE.md` | что нельзя нарушать, чем краснеет гейт | все агенты, всегда |
|
||
| `passport.md` | зачем и для кого, чем это **не** является | `architecture`, `rubric`, `reimpl`, `specs` |
|
||
| `architecture.md` | как сложено и где что работает | все проходы ревью |
|
||
| `database.md` | что лежит в хранилище и какими настройками | `ops`, `adversary`, `reimpl` |
|
||
| `security.md` | против кого защищаемся и что вне модели | `adversary` |
|
||
| `conventions/` | как мы пишем код | `code` |
|
||
| `research/` | что показала реальность, а не документация | `specs`, `reimpl`, `ops`, `adversary` |
|
||
| `adr/` | почему решено именно так | `architecture` |
|
||
| `review.md` | как настроен конвейер и что уже проскакивало | `triage`, каждый проход — свою часть |
|
||
| `openspec/specs/` | что система делает — нормативно | `specs` |
|
||
|
||
### `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`
|
||
|
||
**Периметр первой строкой.** «Сервис открыт наружу» и «контур доверенный,
|
||
публичного интернета здесь нет, не выдумывай его» — противоположные постановки
|
||
под одним заголовком, и враждебный проход между ними сам не выберет. Контур ещё
|
||
не развёрнут — назови **оба** периметра, целевой и сегодняшний, и скажи прямо,
|
||
против какого строятся находки.
|
||
|
||
Дальше: что недоверенное и каким каналом приходит; **из чего строятся пути и
|
||
ключи** (раскладка файлов, состав координатного ключа, имя каталога) — отсюда
|
||
строится выход за пределы песочницы; что разграничивает доступ; что
|
||
чувствительнее чего; **что вне модели** — перечислить явно.
|
||
|
||
### `conventions/`
|
||
|
||
Прозой остаётся **только то, что не выражается правилом**. `README.md` держит
|
||
индекс, правило промоута и **перечень уже механизированного** со ссылкой на
|
||
место механизации — конфиг линтера, собственный анализатор, тест-сканер
|
||
исходников. Не названное место механизации означает, что проход добросовестно
|
||
проверит уже проверенное.
|
||
|
||
### `research/`
|
||
|
||
Наблюдения за внешним миром: что реально шлёт источник, чем документация формата
|
||
расходится с практикой, какие числа сняты с живого потока. **Числа — с
|
||
провенансом**, то есть с командой или условиями, которыми получены.
|
||
`README.md` — как снималось и индекс тем.
|
||
|
||
Число без источника проход обязан читать как условие, а не как замер. Число, чей
|
||
источник по ссылке не подтвердился, не выбрасывается и не переписывается по
|
||
догадке — остаётся с пометкой «расходится с источником: там <что нашли>».
|
||
|
||
### `adr/`
|
||
|
||
**ADR — промоут поверх архивных `design.md`, а не второе сочинение.** Запись
|
||
цитирует решение и ссылается на `openspec/changes/archive/<id>/design.md`.
|
||
|
||
Заводится, когда верно одно из трёх:
|
||
|
||
<!-- дом: adr-когда-заводить -->
|
||
- **дорогой откат** — переделка стоит дороже переписывания одного файла;
|
||
- **намеренный отказ** от очевидного подхода;
|
||
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус
|
||
«заменено на».
|
||
<!-- /дом: adr-когда-заводить -->
|
||
|
||
Не заводится для рутины и для того, что видно из кода и `git log`.
|
||
|
||
Записи неизменяемы: передумали — заводится новая, старая получает статус.
|
||
Активная запись статуса не имеет.
|
||
|
||
**Статус живёт полем меты записи**, там же, где дата и источник:
|
||
`- **Статус:** заменено на ADR-…` либо `- **Статус:** устарело`. Места ему в
|
||
шаблоне не отводилось, и каждая запись изобретала своё — то абзацем, то
|
||
заголовком; в таблице `adr/README.md` статус при этом обязан быть, а брать его
|
||
оттуда, где он у каждого свой, нельзя.
|
||
|
||
### `review.md`
|
||
|
||
Два раздела с разными сроками жизни.
|
||
|
||
**Настройка конвейера под проект**, пять подразделов с точными именами — по ним
|
||
проходы находят свой кусок:
|
||
|
||
- **Типовые узлы** — рода узлов проекта и 3–5 проверяемых свойств к каждому;
|
||
- **Типовые ложноположительные** — находки, которые здесь выглядят убедительно и
|
||
всегда неверны, каждая со строкой «почему здесь это не дефект»;
|
||
- **Вопросы к проходам** — поимённо, в форме `<имя прохода>: <вопрос>
|
||
(<провенанс>)`;
|
||
- **Триггеры профиля** — проектная конкретизация правила выбора профиля ревью:
|
||
что в этом проекте считается **новым понятием или структурной единицей** (это
|
||
поднимает прогон до `wide`) и **где живут правила идентичности, слияния и
|
||
разбора** (до `deep`) — перечнем мест, производным от теста конвейера, а не
|
||
вторым определением класса. Уточняет умолчания, а не отменяет их. Рабочее
|
||
умолчание — `standard`: миграция схемы и публичный контракт ступень **не**
|
||
поднимают, их проверяют проходы, которые в `standard` и так есть;
|
||
- **Недоступно проверке** — два подраздела: «не проверит ни один проход»
|
||
(принципиальная граница, по факту промаха не пересматривается) и «перестали
|
||
проверять сознательно» (пересматривается первым).
|
||
|
||
**Журнал дефектов:** запись на каждый воспроизведённый дефект с пометкой
|
||
**проскочил / пойман ревью**. Проскочившие — проверочный набор для калибровки конвейера,
|
||
выборка по пометке. Пойманные с оракулом — лучшая опора для прохода: проектные,
|
||
воспроизводимые, однажды оказавшиеся правдой.
|
||
|
||
### `tasks/`
|
||
|
||
Раскладку, форму записи и команды держит скилл `tasks` — канон фиксирует имена
|
||
файлов (`items/`, `ROADMAP.md`, `BACKLOG.md`, `SPRINT.md`, `REJECTED.md`) и то,
|
||
от чего зависит, читается ли проект как продукт.
|
||
|
||
**`ROADMAP.md` отвечает на «что приложение уже умеет и чего ещё не умеет».** Это
|
||
не очередь работ: цель — **возможность приложения**, задача — шаг к ней.
|
||
Достигнутая цель из роадмапа **не исчезает** — строка с датой переезжает в
|
||
секцию достигнутого, потому что «что умеет» и есть половина вопроса, ради
|
||
которого документ открывают. Вторым домом поведения роадмап при этом не
|
||
становится: нормативное поведение живёт в `openspec/specs/`, роадмап отвечает,
|
||
**когда и в каком порядке** оно появилось.
|
||
|
||
**У каждой записи есть тип, и тип решает, что с ней можно делать.** Дом типа —
|
||
поле меты `Тип` первой строкой; эмодзи в заголовке от него производна. Словарь
|
||
закрыт:
|
||
|
||
| Тип | Что это |
|
||
| --- | --- |
|
||
| 🎯 `goal` | возможность приложения |
|
||
| ✨ `feature` | снаружи появляется то, чего не было |
|
||
| 🐞 `fix` | поведение расходится с заявленным |
|
||
| 🧹 `chore` | обслуживание, поведение не меняется |
|
||
| 🔬 `research` | исход — знание, а не изменение |
|
||
|
||
**Схемы записи здесь нет намеренно.** Какие разделы тип требует, нужна ли ему
|
||
цель и берётся ли он в спринт — скилл `tasks`: сводка в его
|
||
[SKILL.md](../../tasks/SKILL.md), раздел «Тип записи», подробно — по файлу на
|
||
тип в `tasks/references/task-<тип>.md`. Канон фиксирует **словарь**, потому что
|
||
от него зависит, читается ли проект как продукт; схема — механика ведения задач,
|
||
и второй её экземпляр разошёлся бы с первым (он и разошёлся: канон успел
|
||
объявить цель у `fix` запрещённой, хотя она там необязательна).
|
||
|
||
Схема требуется **к взятию в спринт**, а не к заведению: беклог пополняется чаще,
|
||
чем разбирается, и требование на входе выгоняло бы в заметки то, что должно
|
||
лежать задачей. Запись, не собравшая разделы своего типа, — законное состояние
|
||
беклога; невзятой её делает `sprint take`.
|
||
|
||
Отдельного типа для незаполненной записи нет: «ещё не описано» — состояние, а не
|
||
род работы, и называется оно **`research` без раздела «Вопрос»**. Такая запись в
|
||
спринт не берётся и лежит в конце своей категории.
|
||
|
||
Раскладку, форму записи и алгоритм работы над каждым типом держит скилл `tasks`.
|
||
|
||
### `CLAUDE.md`
|
||
|
||
Что это и стек; **инварианты с severity рядом с формулировкой** — по ним проходы
|
||
присваивают `critical`, поэтому severity стоит здесь, а не выводится каждым
|
||
проходом заново; команды; **семантика гейта** — чем краснеет безусловно и почему,
|
||
где логи, что означает исход, чего в гейте намеренно нет, **кто и когда обязан
|
||
гонять дорогое вне гейта**.
|
||
|
||
Плюс то, что нужно git-операциям и проходам и не выводится ниоткуда:
|
||
|
||
- **имя основной ветки** — от неё считается база диффа
|
||
(`git merge-base HEAD <ветка>`), в неё вливает батч, от неё ветвятся задачи.
|
||
Угадывание между `master` и `main` ломает интеграцию целиком;
|
||
- **что запускать запрещено, с путями** — рабочая БД, боевой каталог данных,
|
||
внешние сервисы. Запретом с путями, а не «будь осторожен»;
|
||
- **где `testdata`** и что в них лежит; **куда писать временное**;
|
||
- **что считается необратимым** — единственный дом: от обратимости зависит вся
|
||
шкала ранжирования триажа и право проходов на `critical`;
|
||
- **общий станок**, врывающийся в замороженный спринт; **ориентир по размеру
|
||
спринта**.
|
||
|
||
### `openspec/config.yaml`
|
||
|
||
**Только нужды генерации артефактов** — язык, правила именования capability,
|
||
придирки валидатора RFC 2119 — плюс ссылки на документы канона. Правило ревью,
|
||
пересказ конвенций и инварианты сюда не пишутся: у них есть свои дома, и второй
|
||
дом разойдётся на первой же правке.
|
||
|
||
## Правило единственного дома
|
||
|
||
Факт живёт ровно в одном файле; остальные ссылаются. Карта на случай спора:
|
||
|
||
<!-- дом: карта-домов -->
|
||
| Факт | Дом |
|
||
| --- | --- |
|
||
| поведение системы | `openspec/specs/<capability>/spec.md` |
|
||
| почему решено так | `adr/`, источник — архивный `design.md` |
|
||
| граница домена, «чем не является» | `passport.md` |
|
||
| инвариант и его severity | `CLAUDE.md` |
|
||
| что приложение умеет и чего не умеет; порядок работ | `docs/tasks/ROADMAP.md` |
|
||
| измеренное число | `research/` |
|
||
| настройка с числовым значением | `database.md` |
|
||
| периметр и модель угроз | `security.md` |
|
||
| что необратимо | `CLAUDE.md` — **не** `architecture.md` |
|
||
| единые точки проекта | `architecture.md` |
|
||
| имя основной ветки, `testdata`, временный каталог | `CLAUDE.md` |
|
||
| что уже механизировано правилом | `conventions/README.md` |
|
||
<!-- /дом: карта-домов -->
|
||
|
||
## Пустое называется пустым
|
||
|
||
Скелет канона заводится **целиком** с первого дня. Незаполненный документ держит
|
||
**одну честную информативную строку**, а не заглушку:
|
||
|
||
- «внешних зависимостей нет — смотри на диск и на СУБД»;
|
||
- «наблюдений на живых данных нет: внешний источник один, формат документирован»;
|
||
- «прецедентов не накоплено»;
|
||
- «сознательно ничего не отключали»;
|
||
- «архитектуры пока нет: кода нет, заводится первой задачей».
|
||
|
||
Проход читает такую строку **как факт** и не тратит на неё обязательный вопрос.
|
||
Отсутствие файла он не может прочитать никак, а «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` | `docs/tasks/ROADMAP.md` |
|
||
| `BRIEF.md` | `passport.md` |
|
||
| `docs/backlog/` | `docs/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 без ссылки на `design.md`, замена без парного статуса | `doc-consistency` |
|
||
| маркеры долга — числом | **протухший факт, разошедшийся с кодом** | `doc-code-drift` |
|
||
| миграция изменена, а `database.md` нет | зависимость в манифесте, не названная в обзоре | `doc-code-drift` |
|
||
| capability без упоминания в `architecture.md` | второй способ там, где обзор обещал единственный | `doc-code-drift` |
|
||
| | связность и читаемость | `doc-wording` |
|
||
|
||
**Агентов двое, и разведены они по глубине, а не по охвату.** `doc-consistency`
|
||
читает только `docs/` и `openspec/`, `doc-code-drift` — весь репозиторий и гоняет
|
||
читающие команды. Слитый агент делал бы одну половину поверхностной; тот же
|
||
разрез, что между `task-form` и `doc-wording`.
|
||
|
||
**Зовутся оба одинаково — раз в спринт на сессии, а также после `adopt` и после
|
||
`upgrade`, на весь канон разом.** Не на синке документации: агент на `opus` по
|
||
каждой сделанной задаче не окупается, а расхождение между двумя документами по
|
||
определению требует двух, и на большинстве задач синк правит один. Пачка,
|
||
отбираемая работой, вдобавок не видит того, чего работа не касалась, — а именно
|
||
там расхождение и живёт: правка отменяет решение в одном документе, парный статус
|
||
нужен в другом.
|
||
|
||
**Перечень фактов, которые `doc-code-drift` сверяет с кодом, закрыт** — имя
|
||
основной ветки, команды, пути, зависимости поимённо, настройки с числовым
|
||
значением, единые точки проекта, capability, проверяемые инварианты. «Сверить
|
||
архитектуру с кодом» задача без дна, и агент, которому её поставили, выдаёт
|
||
правдоподобную труху вместо находок.
|
||
|
||
## `docs/.pm.json`
|
||
|
||
```json
|
||
{
|
||
"canon": 4,
|
||
"migrations": "internal/store/migrations",
|
||
"tasks": {
|
||
"backlog": "INDEX.md"
|
||
}
|
||
}
|
||
```
|
||
|
||
`canon` — версия канона, под которую проект приведён, целым числом: обратной
|
||
совместимости у канона нет, есть «приведён» и «не приведён». `migrations` — путь
|
||
каталога миграций, если БД есть; по нему `docs.py` делает сверку с
|
||
`database.md`. `tasks` — настройки каталога задач, переехавшие сюда из прежнего
|
||
`<tasks>/.tasks.json`: **один конфиг на весь канон, а не по одному на каталог**.
|
||
Внутри `tasks` — **только имена файлов и заголовков** (`items`, `backlog`,
|
||
`roadmap`, `sprint`, `rejected`, `sprint_section`, `oracle_word` и заголовки
|
||
разделов тела: `criteria_heading`, `surface_heading`, `questions_heading`,
|
||
`completion_heading`, `repro_heading`, `question_heading`, `answer_heading`,
|
||
`scope_heading`), и ключ пишется, лишь когда имя отличается от умолчания.
|
||
**Словаря типов здесь нет** — он закрыт каноном, а не настраивается проектом:
|
||
настраиваемый словарь типов разъехался бы на синонимах ровно так же, как
|
||
открытый. **Категорий беклога здесь тоже нет:** их дом — заголовки `##` самого
|
||
индекса, и второй список сразу разошёлся бы с первым. Неизвестный ключ `tasks.py`
|
||
отвергает кодом 3, поэтому лишнее слово в этом объекте останавливает работу с
|
||
задачами целиком.
|
||
|
||
Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py`
|
||
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
|
||
строкой, а не молчит.
|