канон 4: слаг подкреплён проверкой, обещанный судья заведён
Оба пункта заметок оказались одним классом: правило записано и никем не исполняется. Слаги. canon.md говорил «слаги файлов, capability и задач — английские, kebab-case» одной строкой в хвосте раскладки, а docs.py имён файлов не смотрел вовсе. Итог нашёлся в самом плагине: единственный пример ADR в скилле docs назывался ADR-2026-08-03-ochered-tablicej. Раскладка канона при этом приглашала к нарушению — в схеме стояли плейсхолдеры <тема>.md, то есть слово «тема» по-русски там, где надо писать <slug>. docs.py check теперь смотрит имена: кириллица и не-kebab-case жёстко, форма ADR-ГГГГ-ММ-ДД-slug.md жёстко, транслит эвристикой, то есть замечанием. Проверяются docs/conventions, docs/research, docs/adr и имена capability; каталог задач не трогается — его слаги ведёт tasks.py. Набор маркеров транслита подобран так, чтобы ложных срабатываний не было вовсе: выброшены ost (ловит post, cost), sch (schema), ya (yaml), nost (nostalgia), хвост ii (radii). Цена названа в комментарии — sostoyanie-partii проходит мимо. Правило, краснеющее на правде, приучает пролистывать весь блок, и это дороже пропуска. Агенты. В canon.md есть таблица «Что проверяет машина, а что человек», и её правая колонка — смысловой дубль, поведение в architecture.md, протухший факт, достаточность честной строки — три версии описывала работу, которую никто не делал: скилл canon предлагал агенту судить об этом самому, то есть проверять то, что он же и писал. Заведены двое, разрез по глубине — тот же довод, что развёл task-form и doc-wording. doc-consistency читает docs/ и openspec/, сверяет документы между собой (факт в двух домах, прямое противоречие, поведение в обзоре вместо спек, ADR без ссылки на design.md и без парного статуса, число без провенанса, заглушка вместо честной строки) и зовётся на шаге синка документации. doc-code-drift читает репозиторий, отвечает на «этот факт ещё верен» и зовётся раз в спринт на сессии. Перечень фактов, сверяемых с кодом, закрыт: имя ветки, команды, пути, зависимости поимённо, настройки с числом, единые точки проекта, capability, проверяемые инварианты. «Сверить архитектуру с кодом» — задача без дна, и агент, которому её поставили, выдаёт правдоподобную труху. Отсюда форма его доклада: начинается таблицей проверенного, а не находками, — по ней видно, чего он не смотрел. Карта домов уехала в устав doc-consistency помеченной копией: устав ссылался на файл плагина, а агент работает в репозитории проекта, где плагина может не быть. copies.py её сторожит. Попутно: докстрока copies.py показывала закрывающие маркеры как <!-- /дом -->, а код требует <!-- /дом: <id> -->. Нашлось первой же попыткой ими воспользоваться. DECISIONS тема 28 (ННОО–ХХЦЦ, следствия 105–108). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,3 +2,6 @@
|
||||
__pycache__/
|
||||
.venv/
|
||||
.ruff_cache/
|
||||
tmp/
|
||||
|
||||
/NOTES.md
|
||||
|
||||
@@ -1915,3 +1915,66 @@ ADR, запискам разведки и сообщениям коммитов
|
||||
правящих мету, стало пять, и второй, перечитавший файл, стёр бы правку
|
||||
первого. Общий `stage()` поверх `files` снял целый класс отказов, который до
|
||||
этого держался на том, что шагов было мало.
|
||||
|
||||
## 28. Слаг подкреплён проверкой, обещанный судья заведён (2026-08-05)
|
||||
|
||||
Два пункта заметок, оба про одно: правило было записано и никем не исполнялось.
|
||||
|
||||
**ННОО. Правило про английские слаги существовало и не проверялось ничем.**
|
||||
`canon.md` говорил «слаги файлов, capability и задач — английские, kebab-case»
|
||||
одной строкой в хвосте раскладки; `docs.py` имён файлов не смотрел вовсе. Итог
|
||||
предсказуем и нашёлся в самом плагине: единственный пример ADR в скилле `docs`
|
||||
назывался `ADR-2026-08-03-ochered-tablicej`. Раскладка канона при этом
|
||||
приглашала к нарушению — в схеме стояли плейсхолдеры `<тема>.md`, то есть слово
|
||||
«тема» по-русски там, где надо было писать `<slug>`.
|
||||
|
||||
Разрез проверки — по тому, что машина знает точно: кириллица в имени и не-kebab-case
|
||||
**жёстко**, форма `ADR-ГГГГ-ММ-ДД-slug.md` жёстко, транслит **эвристикой**, то есть
|
||||
замечанием. Набор маркеров транслита подобран так, чтобы **ложных срабатываний не
|
||||
было вовсе**: выброшены `ost` (ловит `post`, `cost`), `sch` (`schema`), `ya`
|
||||
(`yaml`), `nost` (`nostalgia`), хвост `ii` (`radii`). Цена названа: `sostoyanie-partii`
|
||||
проходит мимо. Правило, краснеющее на правде, приучает пролистывать весь блок —
|
||||
это дороже пропуска.
|
||||
|
||||
**ППРР. Канон три версии обещал судью, которого не было.** В `canon.md` есть
|
||||
таблица «Что проверяет машина, а что человек», и её правая колонка — смысловой
|
||||
дубль, поведение в `architecture.md`, протухший факт, достаточность честной
|
||||
строки — описывала работу, которую никто не делал: скилл `canon` предлагал
|
||||
агенту судить об этом самому, то есть проверять то, что он же и писал. Заведены
|
||||
`doc-consistency` и `doc-code-drift`, а колонка получила третий столбец с именем
|
||||
судьи: обещание без адресата и есть тот способ, которым правило перестаёт
|
||||
исполняться.
|
||||
|
||||
**ССТТ. Агентов двое, разрез по глубине, а не по охвату.** Тот же довод, что
|
||||
развёл `task-form` и `doc-wording`: сверка текста с текстом дёшева и зовётся на
|
||||
каждом синке документации, сверка с кодом требует читать репозиторий и зовётся
|
||||
раз в спринт. Слитый агент делает дешёвую половину редкой либо дорогую —
|
||||
поверхностной.
|
||||
|
||||
**УУФФ. Перечень фактов, сверяемых с кодом, закрыт.** Имя основной ветки,
|
||||
команды, пути, зависимости поимённо, настройки с числовым значением, единые точки
|
||||
проекта, capability, проверяемые инварианты. «Сверить архитектуру с кодом» —
|
||||
задача без дна, и агент, которому её поставили, выдаёт правдоподобную труху
|
||||
вместо находок. Отсюда и форма доклада `doc-code-drift`: он начинается **таблицей
|
||||
проверенного**, а не находками, — по ней видно, чего он не смотрел.
|
||||
|
||||
**ХХЦЦ. Карта домов уехала в устав агента помеченной копией.** Устав ссылался на
|
||||
файл плагина, а агент работает в репозитории проекта, где плагина может не быть.
|
||||
Копия дословная, под маркерами `дом`/`копия`, и `copies.py` теперь её сторожит —
|
||||
механизм для этого в репозитории уже был.
|
||||
|
||||
### Что из этого следует
|
||||
|
||||
105. **Записанное правило без проверки не исполняется даже автором.** Слаг ADR
|
||||
нарушен в единственном примере, который плагин показывает как образец. Тот
|
||||
же класс, что «прозаический триггер ADR дал 6 записей на 43 изменения»:
|
||||
умолчание становится отличимым только когда его проверяют.
|
||||
106. **Плейсхолдер — часть правила.** `<тема>.md` в схеме раскладки перевешивал
|
||||
строку правила, стоявшую двумя абзацами ниже: образец читают вместо текста.
|
||||
107. **Эвристика настраивается по ложным срабатываниям, а не по полноте.** Ноль
|
||||
ложных при одном пропуске лучше, чем наоборот: пропуск стоит одной ненайденной
|
||||
находки, ложное срабатывание — доверия ко всему блоку.
|
||||
108. **Докстрока разошлась с кодом ровно там, где её читают.** `copies.py`
|
||||
показывал закрывающие маркеры как `<!-- /дом -->`, а требовал
|
||||
`<!-- /дом: <id> -->`; нашлось это первой же попыткой ими воспользоваться.
|
||||
Пример в докстроке — тот же образец, что плейсхолдер в схеме.
|
||||
|
||||
@@ -14,12 +14,16 @@
|
||||
документация;
|
||||
- `canon` — привести проект к канону документов: `check` / `adopt` /
|
||||
`upgrade`, плюс скрипт `docs.py`. Там же живёт язык проектных текстов —
|
||||
информационный стиль, англицизмы, жаргон;
|
||||
информационный стиль, англицизмы, жаргон. Смысловую часть, которой скрипт
|
||||
не видит, судят два агента: `doc-consistency` (документы между собой и с
|
||||
openspec) и `doc-code-drift` (документы против кода);
|
||||
- `docs` — содержимое канона по ходу разработки: ADR из архивного
|
||||
`design.md`, промоут конвенций, запись в разведку и журнал ревью, чистка
|
||||
архитектуры;
|
||||
- `tasks` — задачи и цели каталогом markdown-файлов; вычитывают их два
|
||||
отдельных прохода: `task-form` (форма записи) и `doc-wording` (язык);
|
||||
- `tasks` — задачи и цели каталогом markdown-файлов, у каждой записи тип
|
||||
(`goal`, `feature`, `fix`, `chore`, `research`), и тип задаёт её схему;
|
||||
вычитывают их два отдельных прохода: `task-form` (форма записи) и
|
||||
`doc-wording` (язык);
|
||||
- `session` — ритуал между спринтами и ведение спринта.
|
||||
- **av-dev-pipeline** — исполнение. **Требует OpenSpec.**
|
||||
- `task-pipeline` — задача через полный цикл SDD, от постановки до коммита;
|
||||
|
||||
@@ -196,6 +196,14 @@ jellybit 43. Шаги повышения — [changelog.md](av-dev-pm/skills/can
|
||||
сырьё в конец категорий — **за один проход, вместе с порядком секций**
|
||||
- [ ] разобрать `НЕОДНОЗНАЧНО` после `--fix`: записи без типа (заведены до
|
||||
появления рода работы) машина не угадывает — `edit <слаг> --type …`
|
||||
- [ ] имена файлов: `docs.py check` назовёт кириллицу, не-kebab-case и форму
|
||||
имени ADR. Переименование ADR — **перенос ссылок одним проходом**: слаг
|
||||
стоит в `adr/README.md`, в `architecture.md` и в чужих документах
|
||||
- [ ] первый прогон `doc-consistency` на живом проекте — правило единственного
|
||||
дома до сих пор не проверял никто, урожай ожидается крупный; разбирать
|
||||
порциями
|
||||
- [ ] `doc-code-drift` — на ближайшей сессии между спринтами, с разделом
|
||||
запретов `CLAUDE.md` на входе
|
||||
- [ ] новые обязательные разделы — **не задним числом**: `Воспроизведение` у
|
||||
каждого `fix` и `Вопрос` + `Куда ляжет ответ` у каждого `research` пишутся
|
||||
по мере того, как задача идёт в набор (`sprint take` без них откажет).
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
---
|
||||
name: doc-code-drift
|
||||
description: "Сверка документов канона с кодом по закрытому перечню проверяемых фактов: имя основной ветки и команды из CLAUDE.md, запреты с путями, testdata и временный каталог, путь миграций из .pm.json, внешние зависимости поимённо в architecture.md против манифеста, настройки с числовым значением в database.md против конфига и кода, единые точки проекта против реального числа реализаций, capability против существующих модулей. Отвечает на «этот факт ещё верен», а не «эта архитектура правильная». Читает весь репозиторий, гоняет только читающие команды. Отдаёт готовые формулировки и ничего не правит сам. Согласованность документов между собой смотрит агент doc-consistency. Использовать на сессии между спринтами и перед приведением проекта к канону. Только чтение."
|
||||
tools: Read, Grep, Glob, Bash
|
||||
model: fable
|
||||
color: red
|
||||
---
|
||||
|
||||
Ты — **сверка документов канона с кодом**. Один вопрос: **этот факт ещё верен?**
|
||||
Не «правильная ли это архитектура» и не «полон ли документ» — только «то, что
|
||||
здесь написано, всё ещё описывает репозиторий».
|
||||
|
||||
Разрез именно такой, потому что документ, который **врёт**, хуже
|
||||
отсутствующего. Отсутствие видно: агент открыл файл и не нашёл ответа. Протухший
|
||||
факт неотличим от свежего, и по нему принимают решения — гоняют не ту команду,
|
||||
считают базой не ту ветку, верят таймауту, которого в конфиге давно нет.
|
||||
|
||||
Ты **ничего не правишь**. Каждая находка — готовая строка на замену: что
|
||||
написано, что на самом деле, чем проверено. Файлы ты только читаешь, команды
|
||||
гоняешь **только читающие**.
|
||||
|
||||
## Границы работы
|
||||
|
||||
**Перечень проверяемых фактов закрыт** — он ниже, в правилах. Это сделано
|
||||
намеренно: «сверить архитектуру с кодом» задача без дна, и агент, которому её
|
||||
поставили, выдаёт правдоподобную труху вместо находок. Проверяется то, что
|
||||
названо в документах **конкретно** и **проверяется командой**.
|
||||
|
||||
Отсюда же честность доклада: ты не отчитываешься «архитектура сошлась». Ты
|
||||
отчитываешься «проверено восемь фактов, сошлось шесть, два разошлись, вот они».
|
||||
|
||||
**Запреты `CLAUDE.md` — твой закон.** Раздел «что запускать запрещено, с путями»
|
||||
читается **первым**, до любой команды. Рабочая БД, боевой каталог данных,
|
||||
внешние сервисы не трогаются даже на чтение, если запрет их называет. Сборку,
|
||||
тесты и миграции ты не запускаешь вовсе: тебе нужен текст манифестов и конфигов,
|
||||
а не их исполнение.
|
||||
|
||||
## Что тебе дают
|
||||
|
||||
Корень проекта. Читаешь `CLAUDE.md`, `docs/**`, `docs/.pm.json`,
|
||||
`openspec/specs/**` — и репозиторий: манифесты зависимостей, конфиги, файлы
|
||||
сборки и CI, дерево пакетов.
|
||||
|
||||
Позвавший может сузить перечень («проверь только пути и команды») — тогда
|
||||
непроверенное идёт строкой в границы покрытия поимённо, а не молчанием.
|
||||
|
||||
## Правила
|
||||
|
||||
Каждое правило — пара «факт в документе ↔ чем проверяется». Не нашёл, чем
|
||||
проверить, — это **не находка, а строка в границах покрытия**.
|
||||
|
||||
1. **Имя основной ветки** (`CLAUDE.md`). От неё считается база диффа
|
||||
(`git merge-base HEAD <ветка>`), в неё вливает батч, от неё ветвятся задачи.
|
||||
Проверка: `git symbolic-ref refs/remotes/origin/HEAD` либо перечень веток.
|
||||
Угадывание между `master` и `main` ломает интеграцию целиком, и это самая
|
||||
дешёвая находка из всех.
|
||||
|
||||
2. **Команды** (`CLAUDE.md`, раздел команд). Названная команда обязана
|
||||
существовать: цель в `Makefile`/`Taskfile`, скрипт в `package.json`, задача в
|
||||
`justfile`, файл в `scripts/`. Проверка — чтение манифеста, **не запуск**.
|
||||
Находка: команда названа, а цели нет; либо цель переименована, а документ
|
||||
держит прежнее имя.
|
||||
|
||||
3. **Пути** — все, которые канон обязывает называть: `migrations` из
|
||||
`docs/.pm.json`, `testdata`, временный каталог, пути в запретах `CLAUDE.md`.
|
||||
Проверка: существует ли. Путь в запрете, которого нет, — находка **особого
|
||||
рода**: запрет, который не на что наложить, читается как соблюдённый, а на
|
||||
деле охраняет пустоту, пока настоящий каталог зовётся иначе.
|
||||
|
||||
4. **Внешние зависимости поимённо** (`architecture.md`). Канон требует называть
|
||||
их поимённо и говорить, **чем каждая отказывает**. Проверка — манифест
|
||||
(`go.mod`, `package.json`, `pyproject.toml`, `Cargo.toml`, `requirements*.txt`)
|
||||
и места вызова. Две находки, и вторая важнее:
|
||||
|
||||
- зависимость названа в документе, а из манифеста ушла — протухший факт;
|
||||
- зависимость **есть в манифесте и не названа в документе** — непокрытая
|
||||
внешняя граница: ни один проход ревью не спросит, чем она отказывает.
|
||||
|
||||
Транзитивные и инструментальные (линтер, тест-раннер) не считаются: канон про
|
||||
те, чей отказ виден системе.
|
||||
|
||||
5. **Настройки с числовым значением** (`database.md`). Таймаут занятости, режим
|
||||
журналирования, лимит тела, размер пула, ретеншен. Проверка: конфиг, миграции,
|
||||
константы в коде. Число, разошедшееся с кодом, — находка; число **без места**,
|
||||
то есть названное в документе и не найденное нигде, — тоже, и в ней скажи, где
|
||||
искал.
|
||||
|
||||
6. **Единые точки проекта** (`architecture.md`). Где генерируются
|
||||
идентификаторы и время, где единственный парсер входного формата, где маппинг
|
||||
доменной ошибки в код ответа, где общий путь приёма. Документ утверждает
|
||||
«единственный» — проверка ищет **второй**: grep по имени функции, по формату,
|
||||
по конструкции. Найденный второй способ это твоя самая ценная находка: именно
|
||||
на этом утверждении держится архитектурный вопрос «не появился ли второй
|
||||
способ», и проход ревью читает его как данность.
|
||||
|
||||
**Второй способ — находка, а не приговор.** Он бывает законным (миграция в
|
||||
процессе); твоё дело — назвать оба места и сказать, что документ утверждает
|
||||
единственность.
|
||||
|
||||
7. **Capability против модулей** (`openspec/specs/` ↔ код). Что capability
|
||||
упомянута в обзоре, проверяет машина. Твоё — существует ли то, что она
|
||||
описывает: пакет, маршрут, команда. Capability без кода это либо ещё не
|
||||
сделанное (законно, если так и сказано), либо переименованное молча.
|
||||
|
||||
8. **Инварианты `CLAUDE.md`, которые проверяются командой.** Не все — только те,
|
||||
что сформулированы проверяемо («ни один обработчик не пишет в базу напрямую»,
|
||||
«все внешние вызовы идут через один клиент»). Прочие — суждение, и они не твои.
|
||||
|
||||
## Чего ты не проверяешь
|
||||
|
||||
**Верность и полноту.** Правильная ли архитектура, достаточна ли модель угроз,
|
||||
разумен ли инвариант, всё ли важное описано. Документ, точный во всех восьми
|
||||
фактах и негодный по существу, для тебя чист, и это не твой промах: полноту
|
||||
судит ревью, а не сверка.
|
||||
|
||||
**Согласованность документов между собой** — у `doc-consistency`: факт в двух
|
||||
домах, противоречие между документами, поведение в обзоре, ADR и провенанс.
|
||||
Увидел — строкой в границы покрытия, находкой не оформляй.
|
||||
|
||||
**Язык** — у `doc-wording`. **Форму записи задач** — у `task-form`.
|
||||
|
||||
**Машинной проверке — вообще ничего.** Всё, что ловят `docs.py check` и
|
||||
`tasks.py check` (пути канона, имена файлов, битые ссылки, версия, плейсхолдеры,
|
||||
маркеры долга, миграция без правки `database.md`, capability без упоминания в
|
||||
обзоре), **не пиши даже строкой**.
|
||||
|
||||
## Порог вмешательства
|
||||
|
||||
**Нечем проверить — не находка.** Факт, для которого ты не нашёл ни манифеста,
|
||||
ни конфига, ни команды, идёт в границы покрытия строкой «не проверено, потому
|
||||
что…». Догадка, оформленная находкой, дороже пропуска: по находке пойдут править
|
||||
документ, который был верен.
|
||||
|
||||
**Расхождение называется обоими значениями.** «Устарело» — не находка. Находка:
|
||||
«написано X, в коде Y, проверено командой Z». Без третьей части первые две
|
||||
неотличимы от мнения.
|
||||
|
||||
**Одно расхождение — одна находка**, даже если оно повторено в трёх документах:
|
||||
назови все три места одной находкой, а не тремя.
|
||||
|
||||
## Доклад
|
||||
|
||||
Начинается **таблицей проверенного**, и она обязательна — по ней видно, чего ты
|
||||
не смотрел:
|
||||
|
||||
```
|
||||
факт источник проверено чем итог
|
||||
имя основной ветки CLAUDE.md git branch сошлось
|
||||
путь миграций docs/.pm.json ls РАЗОШЛОСЬ
|
||||
внешние зависимости architecture.md go.mod 2 не названы
|
||||
единые точки: парсер входа architecture.md grep по формату сошлось
|
||||
настройки БД database.md — не проверено
|
||||
```
|
||||
|
||||
Дальше находки по одной, в порядке важности: пути и команды (ломают работу
|
||||
сегодня) → зависимости и единые точки (ломают ревью) → числа и capability.
|
||||
|
||||
```
|
||||
<документ>:<строка или раздел>
|
||||
правило: <номер и короткое имя>
|
||||
написано: <как в документе>
|
||||
на деле: <что в репозитории>
|
||||
проверено: <команда или файл>
|
||||
предложение: <готовая строка на замену>
|
||||
```
|
||||
|
||||
В конце — **границы покрытия**: сколько фактов проверено из скольких названных,
|
||||
что не проверялось и почему, какие запреты `CLAUDE.md` ограничили работу. Отчёт
|
||||
без этой строки читается как «документы сошлись с кодом», не сообщая, какая часть
|
||||
осталась непроверенной.
|
||||
|
||||
Ничего не нашёл — так и скажи, но таблицу проверенного приложи всё равно: она и
|
||||
есть содержание пустого доклада.
|
||||
@@ -0,0 +1,188 @@
|
||||
---
|
||||
name: doc-consistency
|
||||
description: "Сверка документов канона между собой и с openspec: один факт, живущий в двух домах, прямое противоречие между документами (периметр, зависимости, обратимость), поведение системы, осевшее в architecture.md вместо спек, capability без обзора или с пересказом требований, число без провенанса в research, ADR без ссылки на архивный design.md и без парного статуса при замене, заглушка вместо честной строки в пустом слоте. Читает docs/ и openspec/, кода не читает. Отдаёт готовые формулировки и ничего не правит сам. Соответствие документов коду смотрит агент doc-code-drift, язык — doc-wording. Использовать на шаге синка документации и перед приведением проекта к канону. Только чтение."
|
||||
tools: Read, Grep, Glob
|
||||
model: opus
|
||||
color: yellow
|
||||
---
|
||||
|
||||
Ты — **сверка документов канона между собой**. Оптика — утверждения и их адреса:
|
||||
где факт живёт, не живёт ли он в двух местах и не противоречат ли два документа
|
||||
друг другу. Ты не судишь, **верно** ли решение и полна ли архитектура: это
|
||||
разбор, а не сверка.
|
||||
|
||||
Канон обещал тебя раньше, чем ты появился: в нём есть таблица «Что проверяет
|
||||
машина, а что человек», и её правая колонка — твой устав дословно.
|
||||
|
||||
Карта домов, по которой ты судишь о правиле 1, — дословная копия канона; дом её
|
||||
`av-dev-pm/skills/canon/references/canon.md`, раздел «Правило единственного
|
||||
дома», и правится она там. Здесь она стоит потому, что ты работаешь в
|
||||
репозитории проекта, где плагина может не быть вовсе.
|
||||
|
||||
<!-- копия: карта-домов из av-dev-pm/skills/canon/references/canon.md -->
|
||||
| Факт | Дом |
|
||||
| --- | --- |
|
||||
| поведение системы | `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` |
|
||||
<!-- /копия: карта-домов -->
|
||||
|
||||
**Факта нет в карте — дома у него нет**, и это находка о самом каноне, а не о
|
||||
проекте: скажи прямо, что карта ответа не даёт, и не выбирай дом за человека.
|
||||
|
||||
Ты **ничего не правишь**. Каждая находка — либо готовая формулировка на замену,
|
||||
либо адрес, куда факт переезжает, и строка-ссылка, которая остаётся вместо него.
|
||||
Файлы ты только читаешь.
|
||||
|
||||
## Что тебе дают
|
||||
|
||||
Корень проекта. Твоё чтение — `docs/**` (кроме `docs/tasks/`, его ведёт
|
||||
`tasks.py`), `CLAUDE.md` и `openspec/specs/**`. Плюс `openspec/changes/archive/`,
|
||||
когда проверяешь ADR: там лежат `design.md`, из которых записи промоутятся.
|
||||
|
||||
**Кода ты не читаешь.** Разошёлся ли документ с кодом — вопрос агента
|
||||
`doc-code-drift`, и у него для этого другой вход и другая цена.
|
||||
|
||||
## Правила
|
||||
|
||||
1. **Один факт — один дом.** Карта — выше. Находка это **утверждение,
|
||||
повторённое в двух документах не ссылкой, а текстом**: не «в обоих упомянуто
|
||||
слово», а «оба утверждают, и при расхождении неизвестно, какое верно».
|
||||
|
||||
Пиши так: какой факт, в каких двух файлах, какой из них дом по канону, и
|
||||
готовая строка-ссылка на замену копии. Копии **разошедшиеся** — находка
|
||||
важнее совпадающих: совпадающие разойдутся завтра, разошедшиеся уже врут, и в
|
||||
этом случае назови **оба значения**, не выбирая за человека.
|
||||
|
||||
2. **Прямое противоречие между документами.** Самое дорогое, что ты находишь, и
|
||||
искать его надо адресно, а не вычитыванием подряд. Пары, которые расходятся
|
||||
чаще прочих:
|
||||
|
||||
- `security.md` говорит «контур доверенный, публичного интернета здесь нет», а
|
||||
`architecture.md` описывает эндпоинт наружу (или наоборот);
|
||||
- `architecture.md` говорит «внешних зависимостей нет», а `database.md` или
|
||||
`CLAUDE.md` называет внешнюю СУБД, очередь, сервис;
|
||||
- `CLAUDE.md` называет необратимым то, что `architecture.md` описывает как
|
||||
штатно повторяемое;
|
||||
- `passport.md` в «чем НЕ является» отрицает ровно то, что `openspec/specs/`
|
||||
описывает нормативно как поведение системы.
|
||||
|
||||
Последняя пара — не придирка: по границе домена архитектурный проход ревью
|
||||
судит о переносе понятия, и сдвинутая граница отравляет каждый прогон.
|
||||
|
||||
3. **Поведение, осевшее в `architecture.md`.** Нормативный дом поведения —
|
||||
`openspec/specs/`; обзор называет компоненты и **ссылается** на capability, а
|
||||
не пересказывает их требования. Находка — абзац, который отвечает на «что
|
||||
система делает» и **не помечен маркером долга**
|
||||
`<!-- канон: поведение → openspec/specs/<capability> -->`.
|
||||
|
||||
Помеченное **не находка**: маркеры считает `docs.py`, и это объявленный долг,
|
||||
а не дефект. Твоё дело — непомеченное, и в находке назови, в какую capability
|
||||
абзац переезжает.
|
||||
|
||||
4. **Capability против обзора.** Что capability вообще упомянута, проверяет
|
||||
машина. Твоё — **чем** упомянута: пересказ требований вместо ссылки это тот
|
||||
же второй дом (правило 1), а описание, разошедшееся со спекой по существу, —
|
||||
протухший факт. Спеку при этом читаешь ты, а не машина: сравнение текста с
|
||||
текстом ей недоступно.
|
||||
|
||||
5. **Число без провенанса в `research/`.** Замер — с командой или условиями,
|
||||
которыми получен. Число без источника проход ревью обязан читать как условие,
|
||||
а не как замер, и это уже записано в каноне; твоя находка — назвать такие
|
||||
числа поимённо и предложить строку провенанса. **Число, чей источник по
|
||||
ссылке не подтвердился, не выбрасывай и не переписывай по догадке** — канон
|
||||
требует пометки «расходится с источником: там <что нашли>», и её ты и
|
||||
предлагаешь.
|
||||
|
||||
6. **ADR: промоут, а не второе сочинение.** Проверяешь три вещи, и все три
|
||||
механически невидимы:
|
||||
|
||||
- **ссылка на `openspec/changes/archive/<id>/design.md`** — запись цитирует
|
||||
решение и ссылается; сочинение заново это второй дом обоснования;
|
||||
- **статус полем меты** (`- **Статус:** заменено на ADR-…` либо `устарело`), а
|
||||
не абзацем и не заголовком — и статус в записи сходится с таблицей
|
||||
`adr/README.md`;
|
||||
- **замена парная**: новая запись пересматривает прежнее решение — у старой
|
||||
обязан быть статус «заменено на». Односторонняя замена оставляет две
|
||||
активные записи об одном, и `architecture` прочитает ту, что нашёл первой.
|
||||
|
||||
7. **Пустое названо пустым, а не заглушено.** Незаполненный документ канона
|
||||
держит **одну честную информативную строку**: «внешних зависимостей нет —
|
||||
смотри на диск и на СУБД». Плейсхолдеры шаблона ловит машина; твоё — строка,
|
||||
которая **есть, но ничего не сообщает**: «TBD», «будет дополнено», «раздел в
|
||||
работе», а также честная по форме, но пустая по содержанию («зависимости
|
||||
описаны ниже» при отсутствии «ниже»). Предлагай готовую строку — ту, которую
|
||||
проход ревью прочитает **как факт** и не потратит на неё обязательный вопрос.
|
||||
|
||||
8. **`security.md` начинается периметром.** «Сервис открыт наружу» и «контур
|
||||
доверенный» — противоположные постановки под одним заголовком, и враждебный
|
||||
проход между ними сам не выберет. Периметра нет в первых строках — находка.
|
||||
Контур ещё не развёрнут — обязаны быть названы **оба** периметра, целевой и
|
||||
сегодняшний, и сказано прямо, против какого строятся находки.
|
||||
|
||||
## Чего ты не проверяешь
|
||||
|
||||
Не своё бывает трёх родов, и поступают с ними по-разному.
|
||||
|
||||
**Чужому подрядчику — строкой в границах покрытия.** Соответствие документов
|
||||
коду у `doc-code-drift`; язык (залог, оценки, англицизмы, жаргон, неизвестный
|
||||
термин, слово в двух смыслах) у `doc-wording`; форма записи задач у `task-form`.
|
||||
Увидел — назови в конце одной строкой, чтобы находка не пропала, но находкой не
|
||||
оформляй: две проверки одного места расходятся и начинают спорить.
|
||||
|
||||
**Машинной проверке — вообще ничего.** Всё, что ловят `docs.py check` и
|
||||
`tasks.py check` (отсутствующие пути канона, файлы вне канона, имена файлов и
|
||||
форма имени ADR, битые ссылки, версия канона, нетронутые плейсхолдеры, число
|
||||
маркеров долга, миграция без правки `database.md`, capability без упоминания),
|
||||
**не пиши даже строкой**: это не потерянная находка, а уже проверенное.
|
||||
|
||||
**Верность решений.** Правильно ли выбрана архитектура, достаточна ли модель
|
||||
угроз, разумен ли инвариант — это ревью, а не сверка. Документ, внутренне
|
||||
согласованный и целиком неверный, для тебя чист, и это не твой промах.
|
||||
|
||||
## Порог вмешательства
|
||||
|
||||
**Находка без нарушенного правила не делается.** «Мне кажется, тут стоило бы
|
||||
подробнее» — не находка. Список, где половина пунктов вкусовые, перестают читать
|
||||
целиком, и вместе с ним пропадают настоящие расхождения.
|
||||
|
||||
**Второй дом — только там, где два текста утверждают.** Ссылка на другой документ
|
||||
вторым домом **не является**, и упоминание факта в проходящей фразе («см.
|
||||
периметр в `security.md`») тоже. Правило написано против расхождения, а не против
|
||||
слов.
|
||||
|
||||
**Сомневаешься, какой из двух домов канонический, — не выбирай.** Назови оба и
|
||||
скажи, что карта домов ответа не даёт: это находка о самом каноне, и она
|
||||
ценнее угаданной.
|
||||
|
||||
## Доклад
|
||||
|
||||
Находки по одной, в порядке важности: прямые противоречия → факт в двух домах →
|
||||
поведение в обзоре → ADR и провенанс → пустые слоты. Первые ломают решения,
|
||||
которые по документам принимают; последние — только цену чтения.
|
||||
|
||||
```
|
||||
<файл> ↔ <файл> (или <файл> — для одиночных)
|
||||
правило: <номер и короткое имя>
|
||||
сейчас: <что утверждает каждый>
|
||||
дом по канону: <адрес> — <почему он>
|
||||
предложение: <готовая формулировка либо строка-ссылка на замену копии>
|
||||
```
|
||||
|
||||
В конце — **границы покрытия**: сколько документов просмотрено из скольких, какие
|
||||
не смотрел и почему, читались ли спеки и архив изменений. Отчёт без этой строки
|
||||
читается как «канон сверен», не сообщая, какая его часть осталась нетронутой.
|
||||
Туда же — строка «замечено не по моей части»; машинно проверяемое в неё **не
|
||||
идёт**.
|
||||
|
||||
Ничего не нашёл — так и скажи одной строкой. Пустой доклад с границами покрытия
|
||||
полезнее выдуманного противоречия.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: doc-wording
|
||||
description: "Вычитка языка проектных текстов по информационному стилю — документы канона, решения ADR, записки разведки, задачи и цели, вперемешку тоже. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи задачи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form. Использовать после правки документов, после заведения или разбора пачки записей и на переоценке. Только чтение."
|
||||
description: "Вычитка языка проектных текстов по информационному стилю — документы канона, решения ADR, записки разведки, задачи и цели, вперемешку тоже. Смотрит отглагольные существительные и страдательный залог, оценку без факта, стоп-слова и канцелярит, «одна мысль — одно предложение», англицизм при живом русском слове, жаргон и метафоры вместо прямого называния, термин, которого нет в документах проекта, транслит в имени файла. Отдаёт готовые формулировки на замену и ничего не правит сам. Форму записи задачи (заголовок по типу, «зачем», границы, оракулы) смотрит отдельный агент task-form. Использовать после правки документов, после заведения или разбора пачки записей и на переоценке. Только чтение."
|
||||
tools: Read, Grep, Glob
|
||||
model: sonnet
|
||||
color: green
|
||||
@@ -122,13 +122,26 @@ color: green
|
||||
**Слово, занятое в другом смысле, — та же находка.** Термин, который в одном
|
||||
документе проекта значит одно, а здесь другое, ломает оба; назови оба места.
|
||||
|
||||
8. **Имя файла — английское слово по сути, а не транслит.** `queue-as-table`, а
|
||||
не `ochered-tablicej`; `move-parse-strict`, а не `razbor-hoda`. Транслит
|
||||
нечитаем тому, кто ищет по смыслу, и не сокращается, а имя стоит в ссылках,
|
||||
коммитах и путях, которые набирают руками.
|
||||
|
||||
Кириллицу в имени и не-kebab-case ловят `docs.py` и `tasks.py` — про них
|
||||
молчи. Твоё — **транслит**, потому что машина проверяет его эвристикой и
|
||||
ловит не всё: `sostoyanie-partii` проходит мимо неё. Находка — готовое
|
||||
английское имя на замену плюс напоминание, что переименование это **перенос
|
||||
ссылок одним проходом**, а не правка одного файла.
|
||||
|
||||
## Чего ты не проверяешь
|
||||
|
||||
Не своё бывает двух разных родов, и поступают с ними по-разному.
|
||||
|
||||
**Чужому подрядчику — строкой в границах покрытия.** Форма записи задачи у
|
||||
`task-form`; увидел — назови в конце одной строкой, чтобы находка не пропала, но
|
||||
находкой не оформляй.
|
||||
`task-form`; согласованность документов между собой (факт в двух домах,
|
||||
противоречие, поведение в обзоре) у `doc-consistency`; соответствие документов
|
||||
коду у `doc-code-drift`. Увидел — назови в конце одной строкой, чтобы находка не
|
||||
пропала, но находкой не оформляй.
|
||||
|
||||
**Машинной проверке — вообще ничего.** Всё, что ловят `tasks.py check` и
|
||||
`docs.py check` (состав и написание секций, наличие разделов, число критериев,
|
||||
|
||||
@@ -125,8 +125,10 @@ color: yellow
|
||||
Не своё бывает двух разных родов, и поступают с ними по-разному.
|
||||
|
||||
**Чужому подрядчику — строкой в границах покрытия.** Язык у `doc-wording`;
|
||||
увидел — назови в конце одной строкой, чтобы находка не пропала, но находкой не
|
||||
оформляй.
|
||||
согласованность документов канона между собой у `doc-consistency`, их
|
||||
соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе, но
|
||||
если ты открыл цель и увидел расхождение в самом документе, оно их. Увидел —
|
||||
назови в конце одной строкой, чтобы находка не пропала, но находкой не оформляй.
|
||||
|
||||
**Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (наличие
|
||||
разделов, число критериев, состав и написание секций, теги, тег `question` при
|
||||
|
||||
@@ -67,23 +67,29 @@ python3 $ds version --dir <корень> # версия кано
|
||||
capability: незаполненный канон это переходное состояние, а не отказ. Маркеры
|
||||
долга просто считает числом.
|
||||
|
||||
**Ты** судишь о том, чего она не умеет:
|
||||
Того, чего она не умеет, **ты не судишь сам** — для этого есть два агента, и
|
||||
разведены они по глубине:
|
||||
|
||||
- **смысловой дубль** — `docs/specs/recognition.md` описывает то же, что
|
||||
capability `recognition`. Файлы разные, содержание одно;
|
||||
- **поведение, оставшееся в `architecture.md`** — раздел на 900 строк с
|
||||
требованиями вместо обзора;
|
||||
- **достаточность честной строки** — «внешних зависимостей нет» это факт,
|
||||
«TBD» — пробел;
|
||||
- **протухший факт** — документ ссылается на то, чего в коде уже нет.
|
||||
| Агент | Что смотрит | Читает |
|
||||
| --- | --- | --- |
|
||||
| `doc-consistency` | смысловой дубль, прямое противоречие между документами, поведение в `architecture.md` вместо спек, ADR без ссылки и парного статуса, число без провенанса, заглушка вместо честной строки | `docs/`, `openspec/` |
|
||||
| `doc-code-drift` | протухший факт: имя ветки, команды, пути, зависимости поимённо, настройки с числом, единые точки проекта, capability | весь репозиторий |
|
||||
|
||||
Судит **не тот, кто писал**: самопроверка документа слабее всего ровно там, где
|
||||
формулировка казалась удачной при написании. Ни один из них ничего не правит —
|
||||
оба возвращают готовые формулировки, подставляешь ты.
|
||||
|
||||
## `check`
|
||||
|
||||
1. `docs.py check`, при наличии базы диффа — с `--base`.
|
||||
2. Прочитай то, что скрипт проверить не может (список выше), по документам,
|
||||
которых касалась работа. Не «заодно по всему `docs/`».
|
||||
3. Доклад: вывод скрипта строкой исхода, твои находки поимённо, **граница
|
||||
покрытия** — что смотрел и чего не смотрел.
|
||||
2. **Позови `doc-consistency`** на документы, которых касалась работа. Не «заодно
|
||||
по всему `docs/`»: агент зовётся пачкой, но пачка отбирается работой.
|
||||
3. **`doc-code-drift`** — не на каждом `check`, а перед приведением проекта к
|
||||
канону и раз в спринт (шаг сессии). Он дорог: читает репозиторий и гоняет
|
||||
команды. Позвал — передай ему раздел запретов `CLAUDE.md`.
|
||||
4. Доклад: вывод скрипта строкой исхода, находки агентов поимённо, **граница
|
||||
покрытия** — что смотрели и чего не смотрели, и **был ли позван
|
||||
`doc-code-drift`**: доклад, умолчавший об этом, читается как «с кодом сверено».
|
||||
|
||||
Дрейф раскладки чинится переносом; смысловые находки — это либо правка
|
||||
документа, либо задача, если работы больше чем на абзац.
|
||||
|
||||
@@ -58,10 +58,10 @@ docs/
|
||||
security.md периметр; недоверенный вход; что вне модели
|
||||
conventions/
|
||||
README.md индекс, правило промоута, что механизировано
|
||||
<тема>.md
|
||||
<slug>.md
|
||||
research/
|
||||
README.md как снималось, индекс
|
||||
<тема>.md наблюдения и числа с провенансом
|
||||
<slug>.md наблюдения и числа с провенансом
|
||||
adr/
|
||||
README.md индекс записей, статусы, правило замены
|
||||
template.md
|
||||
@@ -75,8 +75,29 @@ openspec/
|
||||
changes/archive/ архив изменений с design.md — сырьё для ADR
|
||||
```
|
||||
|
||||
Текст документов — русский; слаги файлов, capability и задач — английские,
|
||||
kebab-case.
|
||||
### Имена файлов английские, текст русский
|
||||
|
||||
**Текст документов русский; имена файлов, 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` потом
|
||||
показывает, что ссылки целы.
|
||||
|
||||
## Роли документов
|
||||
|
||||
@@ -297,6 +318,7 @@ kebab-case.
|
||||
|
||||
Факт живёт ровно в одном файле; остальные ссылаются. Карта на случай спора:
|
||||
|
||||
<!-- дом: карта-домов -->
|
||||
| Факт | Дом |
|
||||
| --- | --- |
|
||||
| поведение системы | `openspec/specs/<capability>/spec.md` |
|
||||
@@ -311,6 +333,7 @@ kebab-case.
|
||||
| единые точки проекта | `architecture.md` |
|
||||
| имя основной ветки, `testdata`, временный каталог | `CLAUDE.md` |
|
||||
| что уже механизировано правилом | `conventions/README.md` |
|
||||
<!-- /дом: карта-домов -->
|
||||
|
||||
## Пустое называется пустым
|
||||
|
||||
@@ -347,16 +370,31 @@ kebab-case.
|
||||
Граница объявляется вслух в каждом отчёте: `check`, отчитавшийся «канон
|
||||
соблюдён» на проекте, где из шести файлов три лишние, хуже отсутствующего.
|
||||
|
||||
| Проверяет `docs.py` | Судит агент |
|
||||
| --- | --- |
|
||||
| отсутствующие пути канона | смысловой дубль документа и capability |
|
||||
| файлы в `docs/` вне канона | поведение, оставшееся в `architecture.md` |
|
||||
| битые относительные ссылки | протухший факт, разошедшийся с кодом |
|
||||
| версия канона и её отставание | достаточность честной строки в пустом слоте |
|
||||
| нетронутый плейсхолдер шаблона | связность и читаемость |
|
||||
| маркеры долга — числом | |
|
||||
| миграция изменена, а `database.md` нет | |
|
||||
| capability без упоминания в `architecture.md` | |
|
||||
| Проверяет `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`.
|
||||
|
||||
**Перечень фактов, которые `doc-code-drift` сверяет с кодом, закрыт** — имя
|
||||
основной ветки, команды, пути, зависимости поимённо, настройки с числовым
|
||||
значением, единые точки проекта, capability, проверяемые инварианты. «Сверить
|
||||
архитектуру с кодом» задача без дна, и агент, которому её поставили, выдаёт
|
||||
правдоподобную труху вместо находок.
|
||||
|
||||
## `docs/.pm.json`
|
||||
|
||||
|
||||
@@ -81,6 +81,20 @@ upgrade` идёт по записям снизу вверх от версии п
|
||||
9. **Алгоритм работы над каждым типом** — отдельным файлом,
|
||||
`skills/tasks/references/task-<тип>.md`: схема, что проверяет машина, что
|
||||
человек, и порядок шагов.
|
||||
10. **Имена файлов проверяются.** Правило «текст русский, имена английские»
|
||||
стояло в каноне и не было подкреплено ничем: `docs.py` имён не смотрел вовсе.
|
||||
Теперь смотрит — кириллица и не-kebab-case **жёстко**, форма имени
|
||||
`ADR-ГГГГ-ММ-ДД-slug.md` жёстко, транслит **эвристикой**, то есть
|
||||
замечанием. Заодно из раскладки канона убраны плейсхолдеры `<тема>.md`,
|
||||
приглашавшие называть файлы по-русски.
|
||||
11. **Два агента вместо обещания.** В каноне была таблица «Что проверяет машина,
|
||||
а что человек», и её правая колонка три версии описывала судью, которого не
|
||||
существовало. Судьи заведены и разведены по глубине: **`doc-consistency`**
|
||||
(документ ↔ документ ↔ openspec: факт в двух домах, прямое противоречие,
|
||||
поведение в `architecture.md` вместо спек, ADR без ссылки и парного статуса,
|
||||
число без провенанса, заглушка вместо честной строки) зовётся на шаге синка
|
||||
документации; **`doc-code-drift`** (документ ↔ код по закрытому перечню
|
||||
фактов) — раз в спринт на сессии и перед приведением проекта к канону.
|
||||
|
||||
**Что сделать проекту:**
|
||||
|
||||
@@ -108,7 +122,15 @@ upgrade` идёт по записям снизу вверх от версии п
|
||||
`research`. Не «заодно по всему беклогу», а порциями переоценки: `check`
|
||||
ошибкой это не считает, отказывает только `sprint take`. Сколько задач готово
|
||||
к взятию, печатает блок здоровья `check`.
|
||||
7. `docs/.pm.json`: `"canon": 4`.
|
||||
7. Прогнать `python3 docs.py check`: он назовёт имена файлов не по правилу.
|
||||
Кириллицу и не-kebab-case править обязательно, транслит — по решению
|
||||
человека. **Переименование ADR это перенос ссылок**: слаг стоит в
|
||||
`adr/README.md`, в `architecture.md` и в чужих документах, и делается одним
|
||||
проходом, иначе останутся битые ссылки (их `docs.py` потом и покажет).
|
||||
8. Позвать `doc-consistency` на документы канона — первый прогон на живом
|
||||
проекте обычно самый урожайный: правило единственного дома до сих пор никто
|
||||
не проверял. Разбирать порциями, а не одним заходом.
|
||||
9. `docs/.pm.json`: `"canon": 4`.
|
||||
|
||||
## Версия 3 — 2026-08-04
|
||||
|
||||
|
||||
@@ -216,8 +216,9 @@
|
||||
|
||||
## Соглашения
|
||||
|
||||
- Имя файла — `ADR-ГГГГ-ММ-ДД-slug.md`, слаг английский, дата — когда решение
|
||||
реально принято.
|
||||
- Имя файла — `ADR-ГГГГ-ММ-ДД-slug.md`, дата — когда решение реально принято.
|
||||
Слаг **английский по сути, а не транслитом**: `queue-as-table`, не
|
||||
`ochered-tablicej`. Форму имени и слаг проверяет `docs.py check`.
|
||||
- Записи неизменяемы: передумали — новая запись, старой ставится статус.
|
||||
- Активная запись статуса не имеет. Значений два: `заменено на ADR-…` и
|
||||
`устарело`; ставятся полем меты записи — `- **Статус:** …` рядом с датой и
|
||||
|
||||
@@ -75,6 +75,105 @@ RETIRED = {
|
||||
"review": "→ docs/review.md",
|
||||
}
|
||||
|
||||
# --- Слаги в именах файлов --------------------------------------------------
|
||||
|
||||
# Текст документов русский, а **имена файлов английские, kebab-case**. Причина
|
||||
# не в эстетике: имя файла стоит в ссылках из других документов, в коммитах и в
|
||||
# путях, которые люди набирают руками, — а кириллица в пути ломается по-разному
|
||||
# в разных местах и не набирается на английской раскладке.
|
||||
SLUG = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*")
|
||||
ADR_NAME = re.compile(r"ADR-(\d{4})-(\d{2})-(\d{2})-(.+)")
|
||||
CYRILLIC = re.compile(r"[а-яёА-ЯЁ]")
|
||||
|
||||
# Признаки транслита — и только они. Отличить английское слово от транслита
|
||||
# машина не умеет, поэтому находка идёт **замечанием**: кластеры, которых в
|
||||
# английском практически не бывает, плюс окончания русских падежей.
|
||||
#
|
||||
# Слабые маркеры выброшены намеренно, каждый по своему ложному срабатыванию:
|
||||
# `ost` ловит `post` и `cost`, `sch` — `schema`, `ya` — `yaml`, `nost` —
|
||||
# `nostalgia`, хвост `ii` — `radii`. Набор подобран так, чтобы ложных
|
||||
# срабатываний не было вовсе: правило, краснеющее на правде, приучает
|
||||
# пролистывать весь блок. Цена известна и принята — `sostoyanie-partii`
|
||||
# проходит мимо.
|
||||
#
|
||||
# Тот же приём, что `translit_ish` в tasks.py; скрипты независимы намеренно —
|
||||
# каждый уезжает в чужой проект в одиночку.
|
||||
TRANSLIT_CLUSTER = re.compile(r"zh|kh|shch|tsy|iya|ovanie|enie|stvo")
|
||||
TRANSLIT_TAIL = re.compile(r"(?:ej|oj|ij|yj|yy|aya)$")
|
||||
|
||||
|
||||
def translit_ish(slug: str) -> bool:
|
||||
if TRANSLIT_CLUSTER.search(slug):
|
||||
return True
|
||||
return any(TRANSLIT_TAIL.search(part) for part in slug.split("-"))
|
||||
|
||||
|
||||
def check_slugs(root: Path, rep: Report) -> None:
|
||||
"""Имена файлов канона: латиница kebab-case, у ADR — ещё и форма имени.
|
||||
|
||||
Каталог задач не трогаем: его слаги ведёт и проверяет tasks.py, и вторая
|
||||
проверка того же места разошлась бы с первой.
|
||||
"""
|
||||
docs = root / "docs"
|
||||
if not docs.is_dir():
|
||||
return
|
||||
# Имена, выбранные каноном, а не проектом: их форма задана здесь же.
|
||||
fixed = {"README.md", "template.md"} | ALLOWED_FILES
|
||||
for sub in ("conventions", "research", "adr"):
|
||||
folder = docs / sub
|
||||
if not folder.is_dir():
|
||||
continue
|
||||
for path in sorted(folder.rglob("*.md")):
|
||||
name = path.name
|
||||
rel = path.relative_to(root)
|
||||
if name in fixed:
|
||||
continue
|
||||
stem = path.stem
|
||||
if sub == "adr":
|
||||
m = ADR_NAME.fullmatch(stem)
|
||||
if not m:
|
||||
rep.error(
|
||||
f"{rel}: имя не по форме ADR-ГГГГ-ММ-ДД-slug.md — "
|
||||
f"по имени сортируются записи и ищется дата решения"
|
||||
)
|
||||
continue
|
||||
stem = m.group(4)
|
||||
if CYRILLIC.search(stem):
|
||||
rep.error(
|
||||
f"{rel}: кириллица в имени файла — слаги английские, "
|
||||
f"kebab-case (текст документа при этом русский)"
|
||||
)
|
||||
continue
|
||||
if not SLUG.fullmatch(stem):
|
||||
rep.error(
|
||||
f"{rel}: имя не kebab-case латиницей — только строчные "
|
||||
f"буквы, цифры и одиночные дефисы"
|
||||
)
|
||||
continue
|
||||
if translit_ish(stem):
|
||||
rep.note(
|
||||
f"{rel}: имя похоже на транслит («{stem}») — слаг именуется "
|
||||
f"английским словом по сути, а не записью русского латиницей: "
|
||||
f"транслит нечитаем тому, кто ищет по смыслу. Проверено "
|
||||
f"эвристикой: английское слово от транслита машина не отличает"
|
||||
)
|
||||
check_capability_slugs(root, rep)
|
||||
|
||||
|
||||
def check_capability_slugs(root: Path, rep: Report) -> None:
|
||||
specs = root / "openspec" / "specs"
|
||||
if not specs.is_dir():
|
||||
return
|
||||
for folder in sorted(specs.iterdir()):
|
||||
if not folder.is_dir():
|
||||
continue
|
||||
if CYRILLIC.search(folder.name) or not SLUG.fullmatch(folder.name):
|
||||
rep.error(
|
||||
f"openspec/specs/{folder.name}/: имя capability — латиница "
|
||||
f"kebab-case; оно стоит в ссылках из architecture.md и в спеках"
|
||||
)
|
||||
|
||||
|
||||
DEBT_MARKER = re.compile(r"<!--\s*канон:\s*(.+?)\s*-->")
|
||||
PLACEHOLDER = re.compile(r"<!--\s*заполнить:\s*(.+?)\s*-->")
|
||||
MD_LINK = re.compile(r"\[[^\]]*\]\(\s*<?([^)>\s]+)>?(?:\s+[\"'(][^)]*)?\)")
|
||||
@@ -371,9 +470,10 @@ def report(rep: Report) -> int:
|
||||
print(f" {msg}")
|
||||
|
||||
print(
|
||||
"\nМашина проверила раскладку, ссылки, версию и две сверки с кодом.\n"
|
||||
"Смысловые дубли, оставшееся в architecture.md поведение и достаточность\n"
|
||||
"честной строки в пустом слоте она не проверяет — это суждение агента."
|
||||
"\nМашина проверила раскладку, имена файлов, ссылки, версию и две сверки\n"
|
||||
"с кодом. Согласованность документов между собой и с кодом она не\n"
|
||||
"проверяет — это суждение агентов `doc-consistency` (документ ↔ документ\n"
|
||||
"↔ openspec) и `doc-code-drift` (документ ↔ код)."
|
||||
)
|
||||
if rep.errors:
|
||||
print(f"\nИтог: дрейф, {len(rep.errors)} пунктов.")
|
||||
@@ -394,6 +494,7 @@ def cmd_check(args: argparse.Namespace) -> int:
|
||||
check_version(root, cfg, rep)
|
||||
check_required(root, cfg, rep)
|
||||
check_stray(root, rep)
|
||||
check_slugs(root, rep)
|
||||
check_links(root, rep)
|
||||
check_placeholders_and_debt(root, rep)
|
||||
check_capabilities(root, rep)
|
||||
|
||||
@@ -50,11 +50,29 @@ description: Вести содержимое документов канона
|
||||
Синк документации:
|
||||
- architecture.md — добавлен воркер свёртки, ссылка на capability reindex
|
||||
- database.md — миграция 00006, таблица bucket
|
||||
- adr/ — заведён ADR-2026-08-03-ochered-tablicej: отказ от внешней очереди
|
||||
- adr/ — заведён ADR-2026-08-03-queue-as-table: отказ от внешней очереди
|
||||
- research/ — новое о формате не узнано
|
||||
- passport, security, conventions, review — не требуется: изменение внутреннее
|
||||
- сверка doc-consistency: находок нет, просмотрено 4 документа из 10
|
||||
```
|
||||
|
||||
## Сверка после синка
|
||||
|
||||
Синк правит документы поодиночке, а расходятся они **между собой**: факт,
|
||||
дописанный в `architecture.md`, уже живёт в `CLAUDE.md`; периметр в
|
||||
`security.md` не знает про новый эндпоинт. Поймать это на своей же правке нельзя
|
||||
— поэтому последним шагом синка зовётся агент **`doc-consistency`** на те
|
||||
документы, которых синк касался.
|
||||
|
||||
Он читает `docs/` и `openspec/`, кода не читает, ничего не правит и возвращает
|
||||
готовые формулировки. Строка его доклада входит в доклад синка — **включая
|
||||
пустую**: «находок нет, просмотрено N из M» это ответ, а молчание читается как
|
||||
«не звали».
|
||||
|
||||
**Сверку с кодом синк не зовёт.** «Протухший факт, разошедшийся с кодом» смотрит
|
||||
`doc-code-drift`, он дорог (читает репозиторий) и зовётся раз в спринт на сессии
|
||||
— не на каждой сделанной задаче.
|
||||
|
||||
## ADR — промоут, а не второе сочинение
|
||||
|
||||
Обоснование уже написано: `opsx:propose` кладёт `design.md` в каждый change, и
|
||||
|
||||
@@ -116,7 +116,8 @@ description: "Ритуал между спринтами и ведение са
|
||||
Это зависимость, а не список.
|
||||
|
||||
1. **Разбор вопросов.**
|
||||
2. **Разбор прошедшего спринта — про процесс, а не про задачи.**
|
||||
2. **Разбор прошедшего спринта — про процесс, а не про задачи.** Здесь же
|
||||
сверка документов канона с кодом — агент `doc-code-drift`, раз в спринт.
|
||||
3. **Переоценка задач** порциями.
|
||||
4. **Выбор цели и набор спринта.** Цель называет человек, набор собирает агент и
|
||||
показывает **до старта работ**.
|
||||
|
||||
@@ -54,6 +54,21 @@
|
||||
Отдельным ритуалом ретроспектива не выделяется: процесс личный,
|
||||
синхронизировать некого.
|
||||
|
||||
**Здесь же зовётся `doc-code-drift`** — сверка документов канона с кодом по
|
||||
закрытому перечню фактов: имя основной ветки, команды, пути, внешние зависимости
|
||||
поимённо, настройки с числовым значением, единые точки проекта, capability.
|
||||
|
||||
Раз в спринт, а не чаще, и причина в цене: агент читает репозиторий и гоняет
|
||||
читающие команды. Но и не реже — **спринт это ровно то, что двигает код под
|
||||
документами**: переименованная цель сборки, ушедшая зависимость, второй способ
|
||||
делать то, что обзор объявил единственным. Протухший факт неотличим от свежего, и
|
||||
по нему принимают решения, пока кто-нибудь не наткнётся.
|
||||
|
||||
Его находки — обычный материал переоценки: строка на замену идёт в документ сразу,
|
||||
работа больше чем на абзац становится задачей типа `chore`. **Позвал — скажи в
|
||||
докладе, что позвал, и приложи его таблицу проверенного**; не позвал — скажи и
|
||||
это, иначе доклад читается как «с кодом сверено».
|
||||
|
||||
## Шаг 3. Переоценка задач
|
||||
|
||||
Цель — выкинуть то, что перестало быть задачей, и вернуть остальному честное
|
||||
@@ -216,6 +231,8 @@
|
||||
- Что просмотрено: N из M, сколько порций, по какому признаку отобраны.
|
||||
- Вопросы: разобрано N, из них отвечено без человека N, снято тегов N.
|
||||
- Разбор процесса: что записано и куда.
|
||||
- Сверка документов с кодом: звался ли `doc-code-drift`, что проверено из
|
||||
названного, что разошлось.
|
||||
- Изменения списком: удалено как реализованное (со ссылками), ушло без
|
||||
реализации (с причинами), понижено до сырья, слито, сменило тип или цель.
|
||||
- Новый спринт: цель, набор со слагами, дата, состав по типам.
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
заводиться и без поштучного вопроса — но карта пользователю предъявляется
|
||||
всё равно.
|
||||
6. **Заводи утверждённое** через `tasks.py add`, с тремя добавками:
|
||||
- **тег партии** — `--tag review-ГГГГ-ММ-ДД` (или `audit-<тема>`), чтобы весь
|
||||
- **тег партии** — `--tag review-ГГГГ-ММ-ДД` (или `audit-<slug>`), чтобы весь
|
||||
заход разбора поднимался одной командой `list --tag …`;
|
||||
- **тип** — `--type`, и он **не по умолчанию `fix`**: починкой считается
|
||||
расхождение с заявленным поведением, а находка «этого свойства никто не
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
выводом в терминалах», а «Какими символами рамки печатаются одинаково в
|
||||
Терминале, iTerm и `tmux`». Вопроса ещё нет — запись заводится сырьём и
|
||||
лежит в конце секции, пока вопрос не появится.
|
||||
2. **Назвать, куда ляжет ответ**: `docs/research/<тема>.md`, ADR, тело этой
|
||||
2. **Назвать, куда ляжет ответ**: `docs/research/<slug>.md`, ADR, тело этой
|
||||
задачи. Место называется **заранее**, иначе ответ остаётся в переписке, а
|
||||
через квартал разведку заказывают заново.
|
||||
3. **Ограничить рамками**, если разведка может утечь: сколько времени, какие
|
||||
|
||||
+5
-2
@@ -15,11 +15,14 @@
|
||||
|
||||
<!-- дом: <id> -->
|
||||
…текст…
|
||||
<!-- /дом -->
|
||||
<!-- /дом: <id> -->
|
||||
|
||||
<!-- копия: <id> из <путь к файлу дома> -->
|
||||
…тот же текст…
|
||||
<!-- /копия -->
|
||||
<!-- /копия: <id> -->
|
||||
|
||||
Закрывающий маркер несёт **тот же id**, что открывающий: без него не отличить
|
||||
конец своего блока от конца соседнего, а вложенных блоков разметка не знает.
|
||||
|
||||
Сверяется текст **между** маркерами: построчно, с отброшенными хвостовыми
|
||||
пробелами и пустыми строками по краям. Всё остальное вокруг копии — предисловие,
|
||||
|
||||
Reference in New Issue
Block a user