From 354a6b03d517b6023d681f470e92244054a01a10 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Wed, 5 Aug 2026 10:20:39 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=B0=D0=BD=D0=BE=D0=BD=204:=20=D1=81?= =?UTF-8?q?=D0=BB=D0=B0=D0=B3=20=D0=BF=D0=BE=D0=B4=D0=BA=D1=80=D0=B5=D0=BF?= =?UTF-8?q?=D0=BB=D1=91=D0=BD=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA?= =?UTF-8?q?=D0=BE=D0=B9,=20=D0=BE=D0=B1=D0=B5=D1=89=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=81=D1=83=D0=B4=D1=8C=D1=8F=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B2=D0=B5=D0=B4=D1=91=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Оба пункта заметок оказались одним классом: правило записано и никем не исполняется. Слаги. canon.md говорил «слаги файлов, capability и задач — английские, kebab-case» одной строкой в хвосте раскладки, а docs.py имён файлов не смотрел вовсе. Итог нашёлся в самом плагине: единственный пример ADR в скилле docs назывался ADR-2026-08-03-ochered-tablicej. Раскладка канона при этом приглашала к нарушению — в схеме стояли плейсхолдеры <тема>.md, то есть слово «тема» по-русски там, где надо писать . 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 показывала закрывающие маркеры как , а код требует . Нашлось первой же попыткой ими воспользоваться. DECISIONS тема 28 (ННОО–ХХЦЦ, следствия 105–108). Co-Authored-By: Claude Opus 5 (1M context) --- .gitignore | 3 + DECISIONS.md | 63 ++++++ README.md | 10 +- TODO.md | 8 + av-dev-pm/agents/doc-code-drift.md | 173 ++++++++++++++++ av-dev-pm/agents/doc-consistency.md | 188 ++++++++++++++++++ av-dev-pm/agents/doc-wording.md | 19 +- av-dev-pm/agents/task-form.md | 6 +- av-dev-pm/skills/canon/SKILL.md | 30 +-- av-dev-pm/skills/canon/references/canon.md | 66 ++++-- .../skills/canon/references/changelog.md | 24 ++- .../skills/canon/references/skeletons.md | 5 +- av-dev-pm/skills/canon/scripts/docs.py | 107 +++++++++- av-dev-pm/skills/docs/SKILL.md | 20 +- av-dev-pm/skills/session/SKILL.md | 3 +- .../skills/session/references/cadence.md | 17 ++ .../skills/tasks/references/from-review.md | 2 +- .../skills/tasks/references/task-research.md | 2 +- scripts/copies.py | 7 +- 19 files changed, 707 insertions(+), 46 deletions(-) create mode 100644 av-dev-pm/agents/doc-code-drift.md create mode 100644 av-dev-pm/agents/doc-consistency.md diff --git a/.gitignore b/.gitignore index c97f9b9..d6999d7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ __pycache__/ .venv/ .ruff_cache/ +tmp/ + +/NOTES.md diff --git a/DECISIONS.md b/DECISIONS.md index d8d7faf..fa8bd34 100644 --- a/DECISIONS.md +++ b/DECISIONS.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`, то есть слово +«тема» по-русски там, где надо было писать ``. + +Разрез проверки — по тому, что машина знает точно: кириллица в имени и не-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` + показывал закрывающие маркеры как ``, а требовал + ``; нашлось это первой же попыткой ими воспользоваться. + Пример в докстроке — тот же образец, что плейсхолдер в схеме. diff --git a/README.md b/README.md index b6a23c3..59054e9 100644 --- a/README.md +++ b/README.md @@ -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, от постановки до коммита; diff --git a/TODO.md b/TODO.md index 23c1d41..b1dbfac 100644 --- a/TODO.md +++ b/TODO.md @@ -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` без них откажет). diff --git a/av-dev-pm/agents/doc-code-drift.md b/av-dev-pm/agents/doc-code-drift.md new file mode 100644 index 0000000..cab9757 --- /dev/null +++ b/av-dev-pm/agents/doc-code-drift.md @@ -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` ограничили работу. Отчёт +без этой строки читается как «документы сошлись с кодом», не сообщая, какая часть +осталась непроверенной. + +Ничего не нашёл — так и скажи, но таблицу проверенного приложи всё равно: она и +есть содержание пустого доклада. diff --git a/av-dev-pm/agents/doc-consistency.md b/av-dev-pm/agents/doc-consistency.md new file mode 100644 index 0000000..b1460c4 --- /dev/null +++ b/av-dev-pm/agents/doc-consistency.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`, раздел «Правило единственного +дома», и правится она там. Здесь она стоит потому, что ты работаешь в +репозитории проекта, где плагина может не быть вовсе. + + +| Факт | Дом | +| --- | --- | +| поведение системы | `openspec/specs//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, а + не пересказывает их требования. Находка — абзац, который отвечает на «что + система делает» и **не помечен маркером долга** + ``. + + Помеченное **не находка**: маркеры считает `docs.py`, и это объявленный долг, + а не дефект. Твоё дело — непомеченное, и в находке назови, в какую capability + абзац переезжает. + +4. **Capability против обзора.** Что capability вообще упомянута, проверяет + машина. Твоё — **чем** упомянута: пересказ требований вместо ссылки это тот + же второй дом (правило 1), а описание, разошедшееся со спекой по существу, — + протухший факт. Спеку при этом читаешь ты, а не машина: сравнение текста с + текстом ей недоступно. + +5. **Число без провенанса в `research/`.** Замер — с командой или условиями, + которыми получен. Число без источника проход ревью обязан читать как условие, + а не как замер, и это уже записано в каноне; твоя находка — назвать такие + числа поимённо и предложить строку провенанса. **Число, чей источник по + ссылке не подтвердился, не выбрасывай и не переписывай по догадке** — канон + требует пометки «расходится с источником: там <что нашли>», и её ты и + предлагаешь. + +6. **ADR: промоут, а не второе сочинение.** Проверяешь три вещи, и все три + механически невидимы: + + - **ссылка на `openspec/changes/archive//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 и провенанс → пустые слоты. Первые ломают решения, +которые по документам принимают; последние — только цену чтения. + +``` +<файл> ↔ <файл> (или <файл> — для одиночных) + правило: <номер и короткое имя> + сейчас: <что утверждает каждый> + дом по канону: <адрес> — <почему он> + предложение: <готовая формулировка либо строка-ссылка на замену копии> +``` + +В конце — **границы покрытия**: сколько документов просмотрено из скольких, какие +не смотрел и почему, читались ли спеки и архив изменений. Отчёт без этой строки +читается как «канон сверен», не сообщая, какая его часть осталась нетронутой. +Туда же — строка «замечено не по моей части»; машинно проверяемое в неё **не +идёт**. + +Ничего не нашёл — так и скажи одной строкой. Пустой доклад с границами покрытия +полезнее выдуманного противоречия. diff --git a/av-dev-pm/agents/doc-wording.md b/av-dev-pm/agents/doc-wording.md index 2154287..6622ccf 100644 --- a/av-dev-pm/agents/doc-wording.md +++ b/av-dev-pm/agents/doc-wording.md @@ -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` (состав и написание секций, наличие разделов, число критериев, diff --git a/av-dev-pm/agents/task-form.md b/av-dev-pm/agents/task-form.md index 7da4826..9e76237 100644 --- a/av-dev-pm/agents/task-form.md +++ b/av-dev-pm/agents/task-form.md @@ -125,8 +125,10 @@ color: yellow Не своё бывает двух разных родов, и поступают с ними по-разному. **Чужому подрядчику — строкой в границах покрытия.** Язык у `doc-wording`; -увидел — назови в конце одной строкой, чтобы находка не пропала, но находкой не -оформляй. +согласованность документов канона между собой у `doc-consistency`, их +соответствие коду у `doc-code-drift` — до задач эти двое не доходят вовсе, но +если ты открыл цель и увидел расхождение в самом документе, оно их. Увидел — +назови в конце одной строкой, чтобы находка не пропала, но находкой не оформляй. **Машинной проверке — вообще ничего.** Всё, что ловит `tasks.py check` (наличие разделов, число критериев, состав и написание секций, теги, тег `question` при diff --git a/av-dev-pm/skills/canon/SKILL.md b/av-dev-pm/skills/canon/SKILL.md index efc9d5a..538012e 100644 --- a/av-dev-pm/skills/canon/SKILL.md +++ b/av-dev-pm/skills/canon/SKILL.md @@ -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`**: доклад, умолчавший об этом, читается как «с кодом сверено». Дрейф раскладки чинится переносом; смысловые находки — это либо правка документа, либо задача, если работы больше чем на абзац. diff --git a/av-dev-pm/skills/canon/references/canon.md b/av-dev-pm/skills/canon/references/canon.md index 685ab8d..4e29117 100644 --- a/av-dev-pm/skills/canon/references/canon.md +++ b/av-dev-pm/skills/canon/references/canon.md @@ -58,10 +58,10 @@ docs/ security.md периметр; недоверенный вход; что вне модели conventions/ README.md индекс, правило промоута, что механизировано - <тема>.md + .md research/ README.md как снималось, индекс - <тема>.md наблюдения и числа с провенансом + .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//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` diff --git a/av-dev-pm/skills/canon/references/changelog.md b/av-dev-pm/skills/canon/references/changelog.md index f25d225..ae40de8 100644 --- a/av-dev-pm/skills/canon/references/changelog.md +++ b/av-dev-pm/skills/canon/references/changelog.md @@ -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 diff --git a/av-dev-pm/skills/canon/references/skeletons.md b/av-dev-pm/skills/canon/references/skeletons.md index cb8a975..abb537d 100644 --- a/av-dev-pm/skills/canon/references/skeletons.md +++ b/av-dev-pm/skills/canon/references/skeletons.md @@ -216,8 +216,9 @@ ## Соглашения -- Имя файла — `ADR-ГГГГ-ММ-ДД-slug.md`, слаг английский, дата — когда решение - реально принято. +- Имя файла — `ADR-ГГГГ-ММ-ДД-slug.md`, дата — когда решение реально принято. + Слаг **английский по сути, а не транслитом**: `queue-as-table`, не + `ochered-tablicej`. Форму имени и слаг проверяет `docs.py check`. - Записи неизменяемы: передумали — новая запись, старой ставится статус. - Активная запись статуса не имеет. Значений два: `заменено на ADR-…` и `устарело`; ставятся полем меты записи — `- **Статус:** …` рядом с датой и diff --git a/av-dev-pm/skills/canon/scripts/docs.py b/av-dev-pm/skills/canon/scripts/docs.py index 2f96c9b..92b5230 100644 --- a/av-dev-pm/skills/canon/scripts/docs.py +++ b/av-dev-pm/skills/canon/scripts/docs.py @@ -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"") PLACEHOLDER = re.compile(r"") 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) diff --git a/av-dev-pm/skills/docs/SKILL.md b/av-dev-pm/skills/docs/SKILL.md index 4926376..e7147ef 100644 --- a/av-dev-pm/skills/docs/SKILL.md +++ b/av-dev-pm/skills/docs/SKILL.md @@ -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, и diff --git a/av-dev-pm/skills/session/SKILL.md b/av-dev-pm/skills/session/SKILL.md index 8dfd059..11e2089 100644 --- a/av-dev-pm/skills/session/SKILL.md +++ b/av-dev-pm/skills/session/SKILL.md @@ -116,7 +116,8 @@ description: "Ритуал между спринтами и ведение са Это зависимость, а не список. 1. **Разбор вопросов.** -2. **Разбор прошедшего спринта — про процесс, а не про задачи.** +2. **Разбор прошедшего спринта — про процесс, а не про задачи.** Здесь же + сверка документов канона с кодом — агент `doc-code-drift`, раз в спринт. 3. **Переоценка задач** порциями. 4. **Выбор цели и набор спринта.** Цель называет человек, набор собирает агент и показывает **до старта работ**. diff --git a/av-dev-pm/skills/session/references/cadence.md b/av-dev-pm/skills/session/references/cadence.md index 3cc1e84..e7ebe24 100644 --- a/av-dev-pm/skills/session/references/cadence.md +++ b/av-dev-pm/skills/session/references/cadence.md @@ -54,6 +54,21 @@ Отдельным ритуалом ретроспектива не выделяется: процесс личный, синхронизировать некого. +**Здесь же зовётся `doc-code-drift`** — сверка документов канона с кодом по +закрытому перечню фактов: имя основной ветки, команды, пути, внешние зависимости +поимённо, настройки с числовым значением, единые точки проекта, capability. + +Раз в спринт, а не чаще, и причина в цене: агент читает репозиторий и гоняет +читающие команды. Но и не реже — **спринт это ровно то, что двигает код под +документами**: переименованная цель сборки, ушедшая зависимость, второй способ +делать то, что обзор объявил единственным. Протухший факт неотличим от свежего, и +по нему принимают решения, пока кто-нибудь не наткнётся. + +Его находки — обычный материал переоценки: строка на замену идёт в документ сразу, +работа больше чем на абзац становится задачей типа `chore`. **Позвал — скажи в +докладе, что позвал, и приложи его таблицу проверенного**; не позвал — скажи и +это, иначе доклад читается как «с кодом сверено». + ## Шаг 3. Переоценка задач Цель — выкинуть то, что перестало быть задачей, и вернуть остальному честное @@ -216,6 +231,8 @@ - Что просмотрено: N из M, сколько порций, по какому признаку отобраны. - Вопросы: разобрано N, из них отвечено без человека N, снято тегов N. - Разбор процесса: что записано и куда. +- Сверка документов с кодом: звался ли `doc-code-drift`, что проверено из + названного, что разошлось. - Изменения списком: удалено как реализованное (со ссылками), ушло без реализации (с причинами), понижено до сырья, слито, сменило тип или цель. - Новый спринт: цель, набор со слагами, дата, состав по типам. diff --git a/av-dev-pm/skills/tasks/references/from-review.md b/av-dev-pm/skills/tasks/references/from-review.md index e9d77fe..2e2474d 100644 --- a/av-dev-pm/skills/tasks/references/from-review.md +++ b/av-dev-pm/skills/tasks/references/from-review.md @@ -61,7 +61,7 @@ заводиться и без поштучного вопроса — но карта пользователю предъявляется всё равно. 6. **Заводи утверждённое** через `tasks.py add`, с тремя добавками: - - **тег партии** — `--tag review-ГГГГ-ММ-ДД` (или `audit-<тема>`), чтобы весь + - **тег партии** — `--tag review-ГГГГ-ММ-ДД` (или `audit-`), чтобы весь заход разбора поднимался одной командой `list --tag …`; - **тип** — `--type`, и он **не по умолчанию `fix`**: починкой считается расхождение с заявленным поведением, а находка «этого свойства никто не diff --git a/av-dev-pm/skills/tasks/references/task-research.md b/av-dev-pm/skills/tasks/references/task-research.md index 605bff3..f7dd0ea 100644 --- a/av-dev-pm/skills/tasks/references/task-research.md +++ b/av-dev-pm/skills/tasks/references/task-research.md @@ -57,7 +57,7 @@ выводом в терминалах», а «Какими символами рамки печатаются одинаково в Терминале, iTerm и `tmux`». Вопроса ещё нет — запись заводится сырьём и лежит в конце секции, пока вопрос не появится. -2. **Назвать, куда ляжет ответ**: `docs/research/<тема>.md`, ADR, тело этой +2. **Назвать, куда ляжет ответ**: `docs/research/.md`, ADR, тело этой задачи. Место называется **заранее**, иначе ответ остаётся в переписке, а через квартал разведку заказывают заново. 3. **Ограничить рамками**, если разведка может утечь: сколько времени, какие diff --git a/scripts/copies.py b/scripts/copies.py index 261ce20..342b37f 100644 --- a/scripts/copies.py +++ b/scripts/copies.py @@ -15,11 +15,14 @@ …текст… - + …тот же текст… - + + +Закрывающий маркер несёт **тот же id**, что открывающий: без него не отличить +конец своего блока от конца соседнего, а вложенных блоков разметка не знает. Сверяется текст **между** маркерами: построчно, с отброшенными хвостовыми пробелами и пустыми строками по краям. Всё остальное вокруг копии — предисловие,