Имя `.pm.json` пережило плагин `av-dev-pm` на два месяца и указывало в пустоту. Правило, которое из этого вынуто: имя служебного файла — имя плагина, который его завёл, и по нему же владельца узнают. - `docs/.pm.json` → `docs/.docs.json`, запись 13 журнала. Прежнее имя docs.py не читает намеренно: по этому числу upgrade решает, какие записи применять, и два дома разъехались бы молча ровно там, где это дороже всего. Вместо совместимости — узнавание: check видит старый файл и печатает готовую git mv - у каталога задач появилась своя версия формата — ключ `tasks` в `.tasks.json`, свой журнал версий и своё повышение. До сих пор её не было вовсе, хотя docs.py в комментарии уверенно на неё ссылался: описание опережало механику ровно так, как сказано в решении 195 - число своё, а не копия канонического: плагин ставится в одиночку, и у проекта без docs/ версии канона нет — сверять было бы не с чем - конфиг задач стал обязательным (init и adopt apply пишут его всегда), check сверяет число, `check --fix` его не приписывает: приписанное объявляло бы каталог приведённым к формату, шагов которого никто не делал - переезды 11 и 12 в новый журнал задним числом не переписаны — версия 1 велит догнать формат по журналу канона, называя признаки отставания поимённо (каталог в docs/tasks/, живой SPRINT.md) - запись 60 в DECISIONS со следствиями 200–203; отдельно разведено с решением F, где `.docs.json` отвергался как указатель путей: отвергнут был указатель, а не имя
462 lines
24 KiB
Markdown
462 lines
24 KiB
Markdown
# Скелеты документов канона
|
||
|
||
Что кладут `init` и `canon adopt` в незаполненный слот. Правило одно:
|
||
**честная информативная строка вместо заглушки**. Проход читает строку как факт;
|
||
`<!-- заполнить: … -->` он читает как пробел, и `docs.py check` о таком
|
||
плейсхолдере напоминает.
|
||
|
||
Плейсхолдер ставится только там, где ответ **обязан** быть и его не спросили.
|
||
Всё, чего в проекте пока просто нет, описывается словами, а не плейсхолдером.
|
||
|
||
**Шаблоны — единственное место, где правило канона копируется намеренно.**
|
||
`adr/README.md` и `review.md` уезжают в репозиторий проекта и обязаны там что-то
|
||
говорить; определение при этом остаётся в [canon.md](canon.md). Отсюда
|
||
обязанность: **правка такого правила в каноне тянет запись в
|
||
[changelog.md](changelog.md)** — и запись называет, какой файл проекта поднимает
|
||
`upgrade`. Без этого копия в проекте останется на старой версии молча.
|
||
|
||
**Каждая такая копия помечена и сверяется машиной.** Дом обрамляется
|
||
`<!-- дом: <id> -->` … `<!-- /дом: <id> -->`, копия —
|
||
`<!-- копия: <id> из <путь> -->` … `<!-- /копия: <id> -->`;
|
||
`scripts/copies.py` маркетплейса требует дословного
|
||
совпадения. Правишь текст внутри маркеров — правь дом, а не копию.
|
||
|
||
**Сама пара маркеров в проект не переносится.** Это машинерия маркетплейса:
|
||
путь в ней ведёт в дерево плагина, и в репозитории проекта он не разрешится ни
|
||
во что. Кладя скелет, копируй содержимое между маркерами, а строки
|
||
`<!-- копия: … -->` и `<!-- /копия: … -->` оставляй здесь.
|
||
|
||
## `docs/passport.md`
|
||
|
||
```markdown
|
||
# Паспорт проекта
|
||
|
||
Зачем это и для кого. [architecture.md](architecture.md) отвечает «как
|
||
устроено», [tasks/ROADMAP.md](../tasks/ROADMAP.md) — «в каком порядке», паспорт —
|
||
«зачем и для кого».
|
||
|
||
## Цель
|
||
|
||
<!-- заполнить: одна фраза без технических деталей -->
|
||
|
||
**Потребители** — список закрытый: он определяет, что считать нужным, а что
|
||
интересным.
|
||
|
||
| Кто | Что ему нужно от нас |
|
||
| --- | --- |
|
||
|
||
Цель достигнута, когда:
|
||
|
||
## Что целью не является
|
||
|
||
Граница домена. По ней в теме `architecture` судят, не перенесено ли понятие
|
||
через границу.
|
||
|
||
## Типовые сценарии
|
||
|
||
## Референсы
|
||
|
||
Где смотреть prior art, когда упёрлись.
|
||
```
|
||
|
||
## `docs/architecture.md`
|
||
|
||
```markdown
|
||
# Архитектура
|
||
|
||
Обзор: как сложено и где что работает. **Поведение системы здесь не
|
||
описывается** — его нормативный дом `openspec/specs/`.
|
||
|
||
## Принципы
|
||
|
||
## Компоненты
|
||
|
||
Каждый — строкой со ссылкой на capability, а не пересказом её требований.
|
||
|
||
## Внешние границы и форматы
|
||
|
||
## Эксплуатация
|
||
|
||
- Где работает, что рядом, кто перезапускает:
|
||
- Внешние зависимости поимённо и чем каждая отказывает (падает, отвечает
|
||
медленно, молчит, отдаёт мусор):
|
||
- Кто заметит отказ и когда:
|
||
- Характер потока (непрерывный, по запросу, по расписанию):
|
||
|
||
## Единые точки проекта
|
||
|
||
Где генерируются идентификаторы и время; где единственный парсер входного
|
||
формата; где маппинг доменной ошибки в код ответа; где общий путь приёма.
|
||
Материал для вопроса «не появился ли второй способ делать то, что уже делается».
|
||
|
||
## Деплой
|
||
|
||
## Открытые вопросы
|
||
```
|
||
|
||
Пустой проект: «Архитектуры пока нет: кода нет. Наполняется первой задачей.»
|
||
Нет внешних зависимостей: «Внешних зависимостей нет — смотри на диск и на СУБД.»
|
||
|
||
## `docs/database.md`
|
||
|
||
```markdown
|
||
# Схема хранилища
|
||
|
||
СУБД, миграции, правило времени и идентификаторов.
|
||
|
||
## Таблицы
|
||
|
||
## Представление данных
|
||
|
||
Чем физически лежит запись и что происходит при чтении и записи.
|
||
|
||
## Настройки с числовым значением
|
||
|
||
Таймаут занятости, режим журналирования, лимит тела, размер пула, ретеншен.
|
||
Без них замер не превращается в находку: пик памяти — аномалия только рядом
|
||
со строкой «запись лежит сжатой и распаковывается целиком».
|
||
```
|
||
|
||
Нет БД — файла нет, и в `docs/.docs.json` нет ключа `migrations`.
|
||
|
||
## `docs/security.md`
|
||
|
||
```markdown
|
||
# Модель угроз
|
||
|
||
## Периметр
|
||
|
||
<!-- заполнить: первой строкой, против кого защищаемся -->
|
||
|
||
Контур не развёрнут — назови оба периметра, целевой и сегодняшний, и скажи
|
||
прямо, против какого строятся находки.
|
||
|
||
## Недоверенный вход
|
||
|
||
Что приходит извне и каким каналом: тело запроса, файл, аргумент команды,
|
||
ответ внешней системы, содержимое архива.
|
||
|
||
## Из чего строятся пути и ключи
|
||
|
||
Раскладка файлов на диске, состав координатного ключа записи, имя каталога.
|
||
Отсюда строится выход за пределы песочницы.
|
||
|
||
## Что разграничивает доступ
|
||
|
||
## Что чувствительнее чего
|
||
|
||
## Что вне модели
|
||
|
||
Перечислить явно. Пустой пункт означает, что в теме `security` угрозу выдумают
|
||
за тебя, и находка никогда не будет исправлена.
|
||
```
|
||
|
||
## `docs/conventions/README.md`
|
||
|
||
```markdown
|
||
# Конвенции кода
|
||
|
||
Как мы пишем код — в отличие от `openspec/specs/`, который описывает, что
|
||
система делает.
|
||
|
||
**Прозой остаётся только то, что не выражается правилом.** Свойство, ставшее
|
||
правилом линтера, отсюда удаляется и переезжает в перечень ниже.
|
||
|
||
## Записи
|
||
|
||
## Механизировано
|
||
|
||
| Правило | Где механизировано |
|
||
| --- | --- |
|
||
|
||
Не названное здесь место механизации означает, что проход по конвенциям будет
|
||
добросовестно проверять уже проверенное.
|
||
```
|
||
|
||
Пустой проект: «Конвенций пока нет: код не написан. Наполняется по мере
|
||
реального трения, а не вперёд.»
|
||
|
||
## `docs/research/README.md`
|
||
|
||
```markdown
|
||
# Разведка
|
||
|
||
Наблюдения за внешним миром: что реально шлёт источник, чем документация
|
||
формата расходится с практикой. Источник истины — этот каталог, а не чужая
|
||
документация.
|
||
|
||
**Каждый вывод — с числами и командой, которой получен**, чтобы его можно было
|
||
перепроверить.
|
||
|
||
## Как снималось
|
||
|
||
## Записи
|
||
```
|
||
|
||
Нет внешних источников: «Внешних источников данных нет — разведка неприменима.»
|
||
|
||
## `docs/adr/README.md`
|
||
|
||
```markdown
|
||
# Журнал решений
|
||
|
||
Одна запись — одно решение. **ADR это промоут поверх архивного `design.md`**,
|
||
а не второе сочинение: запись цитирует решение и ссылается на
|
||
`openspec/changes/archive/<id>/design.md`.
|
||
|
||
## Когда заводить
|
||
|
||
Верно одно из трёх:
|
||
|
||
<!-- копия: adr-когда-заводить из av-dev-docs/skills/canon/references/canon.md -->
|
||
- **дорогой откат** — переделка стоит дороже переписывания одного файла;
|
||
- **намеренный отказ** от очевидного подхода;
|
||
- **пересмотр прежнего решения** — тогда у старой записи обязателен статус
|
||
«заменено на».
|
||
<!-- /копия: adr-когда-заводить -->
|
||
|
||
Не заводить для рутины и для того, что видно из кода и `git log`.
|
||
|
||
## Соглашения
|
||
|
||
- Имя файла — `ADR-ГГГГ-ММ-ДД-slug.md`, дата — когда решение реально принято.
|
||
Слаг **английский по сути, а не транслитом**: `queue-as-table`, не
|
||
`ochered-tablicej`. Форму имени и слаг проверяет `docs.py check`.
|
||
- Записи неизменяемы: передумали — новая запись, старой ставится статус.
|
||
- Активная запись статуса не имеет. Значений два: `заменено на ADR-…` и
|
||
`устарело`; ставятся полем меты записи — `- **Статус:** …` рядом с датой и
|
||
источником, а не абзацем в теле.
|
||
|
||
## Записи
|
||
|
||
Новые сверху.
|
||
|
||
| Дата | Запись | Статус |
|
||
| --- | --- | --- |
|
||
```
|
||
|
||
## `docs/adr/template.md`
|
||
|
||
```markdown
|
||
# Краткий заголовок решения
|
||
|
||
- **Дата:** ГГГГ-ММ-ДД
|
||
- **Источник:** openspec/changes/archive/<id>/design.md
|
||
|
||
Статус ставится тем же полем и только при пересмотре:
|
||
`- **Статус:** заменено на ADR-…` либо `- **Статус:** устарело`.
|
||
У активной записи поля нет.
|
||
|
||
## Решение
|
||
|
||
Что именно решено — одной фразой.
|
||
|
||
## Почему
|
||
|
||
Намерение и причина. Цитата из источника, а не пересказ. Пиши так, чтобы через
|
||
год было понятно без чтения переписки.
|
||
|
||
## Последствия
|
||
|
||
- `+` что стало лучше.
|
||
- `−` чем платим: ограничения, риски, нагрузка на поддержку.
|
||
```
|
||
|
||
## `docs/review.md`
|
||
|
||
```markdown
|
||
# Ревью: настройка и журнал
|
||
|
||
## Как настроен конвейер
|
||
|
||
### Типовые узлы
|
||
|
||
Рода узлов проекта и 3–5 проверяемых свойств к каждому. Рода, а не инвентарь
|
||
пакетов: род, который проект задумал, но ещё не написал, включать полезно.
|
||
|
||
### Типовые ложноположительные
|
||
|
||
Находки, которые здесь выглядят убедительно и всегда неверны. Каждая — с одной
|
||
строкой «почему здесь это не дефект».
|
||
|
||
### Вопросы по темам
|
||
|
||
Форма: `<тема>: <вопрос> (<провенанс>)`. Главный источник — журнал ниже. Вопрос
|
||
задаёт тот проход, который закрывает эту тему на текущем прогоне, дополнительно
|
||
к обязательным.
|
||
|
||
**Адресуй теме, а не имени прохода.** Проходы переезжают между метками и
|
||
упраздняются; вопрос, адресованный проходу, перестанет задаваться в тот день,
|
||
когда тот уедет в старшую метку, — и заметить это будет нечем. Тема переезд
|
||
переживает.
|
||
|
||
Темы ядра: `requirements`, `autotests`, `conventions`, `architecture`,
|
||
`security`, `operations`. Плюс любая своя — та, под которую проект завёл в
|
||
`docs/` **свой** документ. Документы категорий `источник` и `процессный` тем не
|
||
порождают, и адресовать вопрос `passport`, `database`, `adr`, `research` или
|
||
`review` нельзя — таких тем нет. Вопрос про границу домена адресуй
|
||
`architecture`, вопрос про хранилище и числа — `operations`.
|
||
|
||
### Триггеры метки
|
||
|
||
Проектная конкретизация правила выбора метки. **Списка три: по одному на
|
||
каждую ось вверх и один вниз** — поимённо, узлами или capability.
|
||
|
||
**Крупное здесь** — про объём: что трогает несколько узлов или слоёв, переносит
|
||
ответственность между ними, перекладывает существующий код в новую форму.
|
||
|
||
**Незнакомое здесь** — про форму решения: то, чего в проекте ещё не было и чью
|
||
форму предстоит нащупать по ходу. Признак простой: перед работой нельзя назвать,
|
||
какие узлы будут тронуты.
|
||
|
||
Любая из двух осей поднимает прогон до `large`, старшей метки: там `security`,
|
||
`operations` и `architecture` проверяют запуском, и там же единственные замеры.
|
||
Метка рассчитана на **5–10% задач**; если сюда попадает каждая третья, списки
|
||
написаны слишком широко.
|
||
|
||
**Мелкое здесь** — опускает до `small`. Ориентир по доле — до трети задач, и в
|
||
любом случае меньше, чем `medium`: перевес `small` значит, что рабочее умолчание
|
||
сместилось само. Помни отрицательный тест конвейера: что
|
||
после мерджа не откатывается обратной правкой (миграция, формат на диске,
|
||
публичный контракт, имя), — не `small`, каким бы маленьким ни был дифф.
|
||
|
||
Уточняет умолчания конвейера, не отменяет их; рабочее умолчание — `medium`.
|
||
|
||
### Недоступно проверке
|
||
|
||
Оба подраздела — **по темам**: «в теме `operations` не проверяется X» читается,
|
||
а «не проверяется X» через месяц не найдёт ни один проход.
|
||
|
||
**Не проверит ни один проход** — принципиальная граница; по факту промаха не
|
||
пересматривается.
|
||
|
||
**Перестали проверять сознательно** — что, когда и почему, со ссылкой на запись
|
||
журнала. Пересматривается **первым**, как только что-то проскочило.
|
||
|
||
Тему, у которой в проекте нет дома, сюда писать не надо: её называет план
|
||
каждого прогона, и это честнее разовой записи.
|
||
|
||
## Журнал дефектов
|
||
|
||
Запись на каждый воспроизведённый дефект, сразу, а не ретроспективно: со
|
||
временем теряется не факт, а то, почему дефект не поймали.
|
||
|
||
Форма:
|
||
|
||
<!-- копия: журнал-дефектов-форма из av-dev-code/skills/review/references/review-journal.md -->
|
||
## ГГГГ-ММ-ДД — <краткое последствие> [проскочил|пойман]
|
||
|
||
- **Где:** путь:строка либо «конвейер, а не код»
|
||
- **Симптом:** как обнаружилось, кем и когда
|
||
- **Причина:** что на самом деле было не так
|
||
- **Чем воспроизведён:** тест, команда, замер — с числами
|
||
- **Почему не поймали:** только для проскочивших — какой проход обязан был найти
|
||
и что ему помешало
|
||
- **Что меняем:** правило прохода, шаг гейта, конвенция, факт в документе
|
||
проекта — либо «ничего, цена поимки выше цены дефекта»
|
||
<!-- /копия: журнал-дефектов-форма -->
|
||
```
|
||
|
||
Пара маркеров `копия:` внутри — машинерия маркетплейса; в `docs/review.md`
|
||
проекта уезжает только содержимое между ними (см. выше).
|
||
|
||
Новый проект: «Дефектов пока не было. Настройка конвейера появится с первым
|
||
ревью.»
|
||
|
||
## `CLAUDE.md`
|
||
|
||
Лежит в корне, не в `docs/`. Единственный файл канона, который агент читает
|
||
**всегда**, поэтому в нём то, без чего нельзя сделать ни шага.
|
||
|
||
```markdown
|
||
# CLAUDE.md
|
||
|
||
Памятка для работы над <проект>. Перед задачей прочитай также
|
||
[docs/passport.md](docs/passport.md), [docs/architecture.md](docs/architecture.md)
|
||
и [docs/conventions/](docs/conventions/README.md).
|
||
|
||
## Что это
|
||
|
||
Абзац: что делает и чего **не** делает.
|
||
|
||
## Стек
|
||
|
||
## Инварианты
|
||
|
||
Что нарушать нельзя. Каждый пункт — три вещи: формулировка **как проверяемое
|
||
свойство**, а не лозунг; последствие нарушения и его обратимость; **severity**
|
||
рядом. По этим формулировкам проходы ревью присваивают `critical`, поэтому
|
||
severity стоит здесь, а не выводится каждым проходом заново.
|
||
|
||
## Команды
|
||
|
||
## Гейт
|
||
|
||
- Команда целиком и как определяется база диффа:
|
||
- Где логи шагов:
|
||
- Что означает каждый исход:
|
||
- **Что красит безусловно и почему:**
|
||
- Чего в гейте намеренно нет и **кто тогда обязан это гонять:**
|
||
|
||
## Запреты
|
||
|
||
Что запускать нельзя, **с путями**: рабочая БД, боевой каталог данных, внешние
|
||
сервисы. Плюс где `testdata` и куда писать временное.
|
||
|
||
## Работа
|
||
|
||
- **Основная ветка:** <имя>
|
||
- **Необратимое** (спрашивается у человека всегда):
|
||
- **Что считается сломанным** — какая красная проверка обгоняет развитие,
|
||
то есть останавливает текущую работу:
|
||
- **Ориентир по размеру порции:** своё число, если замерялось
|
||
- **Что такое «сделана»:** конвейер проекта пройден + критерии приёмки проверены
|
||
поимённо
|
||
|
||
## Язык
|
||
|
||
- Документация, комментарии, сообщения коммитов — русский.
|
||
- Код и идентификаторы — английский.
|
||
```
|
||
|
||
Имя основной ветки, запреты с путями и «что необратимо» — не украшение: без
|
||
первого падают git-операции батча и расчёт базы диффа, без второго проход может
|
||
тронуть рабочие данные, без третьего вся шкала ранжирования триажа держится на
|
||
догадке.
|
||
|
||
## `openspec/config.yaml`
|
||
|
||
**Образец переехал.** Файл заводит и заполняет плагин конвейера — скилл
|
||
`av-dev-code:openspec`, — потому что по OpenSpec работает он, а не канон
|
||
документов. Проект без конвейера каталога `openspec/` не имеет вовсе, и образец
|
||
файла, которого у него нет, в скелетах канона лежал бы мёртвым грузом.
|
||
|
||
**Форму не проверяет и `docs.py`** — с канона 10 он о файле молчит вовсе.
|
||
Проверяет её тот же владелец: скилл `av-dev-code:openspec`, команда
|
||
`openspec.py check`. Плагина конвейера в проекте может не быть — тогда форму не
|
||
смотрит никто, и это строка доклада, а не поломка. Что канон о файле всё же
|
||
говорит (единственный дом, а не форма) — [canon.md](canon.md), раздел
|
||
`openspec/config.yaml`.
|
||
|
||
## `docs/.docs.json`
|
||
|
||
```json
|
||
{
|
||
"canon": <текущая версия>
|
||
}
|
||
```
|
||
|
||
`<текущая версия>` подставляет `init` или `adopt`, целым числом; берётся она из
|
||
`docs.py version` (строка «канон скрипта»), а не из памяти. Литерал здесь
|
||
протухает при каждом повышении канона, поэтому его тут и нет: незамещённый
|
||
плейсхолдер ломает разбор JSON громко, а отставшее число дало бы дрейф молча.
|
||
|
||
Плюс `"migrations": "<путь>"`, если есть БД. Ключа `"tasks"` здесь **нет**:
|
||
настройки каталога задач и версия их формата переехали в свой файл `<каталог
|
||
задач>/.tasks.json`, потому что ведёт их другой плагин. Состав ключей —
|
||
[canon.md](canon.md).
|
||
|
||
Имя файла — по плагину-владельцу, `av-dev-docs`. До версии 13 он звался
|
||
`.pm.json`, по распавшемуся `av-dev-pm`; проект с прежним именем `docs.py check`
|
||
называет отдельной строкой и зовёт переименовать.
|