границы плагинов: путь в чужое дерево, безымянные стыки, звонящий у вычитки
Правило границы моё, копий восемь — и нарушал его я же. - путь в дерево чужого плагина снят из пяти мест; маркер копии, уезжающий в проект скелетом, оставлен, но сказано, что сама пара маркеров не едет - короткое имя чужого скилла в четырёх местах стало полным - стык «урожай ревью → задачи» не был назван ни с одной стороны, хотя механика написана с обеих; теперь назван, с веткой «плагина нет» - resolve звал av-dev-git:commit без строки доклада и пересказывал формат коммита, нарушая собственное «ссылайся, не пересказывай» - doc-wording обещал момент вызова, которого не исполнял никто. Правило: звонящий — тот, кто только что писал текст. Вызов появился шагом в docs, init, adopt и upgrade; healthcheck по-прежнему его не зовёт - openspec.py искал SHALL по всему файлу, а образец даёт его в context — проверка молчала ровно в том случае, ради которого написана - фаза 2 review-rubric была недостижима; проход стал судить задуманное, а не код, и это сходится с тем, что о нём говорит конвейер - rules.tasks в образце конфига, ветка «записи задачи нет» у review-scope, возвраты на чекпоинт в схеме resolve, старшинство правила дельта-спек Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -78,11 +78,14 @@ python3 $os form # слепок формы против жив
|
||||
Различать 1 и 3 обязательно: «форма разошлась» — рабочая ситуация, «openspec не
|
||||
отвечает» — нерабочая.
|
||||
|
||||
`check` проверяет пять вещей, и каждая — про молчащий пробел, а не про вкус:
|
||||
`check` проверяет форму, и каждая проверка — про молчащий пробел, а не про вкус:
|
||||
каталог есть; имя именно `config.yaml` (`config.yml` OpenSpec не читает и об этом
|
||||
не сообщает); `context` и `rules.specs` не остались примером, а правила называют
|
||||
`SHALL`; `context` называет паспорт и `CLAUDE.md`; ключи под `rules:` — имена
|
||||
артефактов схемы, а не свободные слова.
|
||||
не сообщает); ключ `schema` называет ту схему, для которой форма описана;
|
||||
`context` и `rules.specs` не остались примером, **а `SHALL` назван именно внутри
|
||||
`rules.specs`** (в `context` он стоит и в образце, поэтому греп по файлу здесь
|
||||
ничего не значит); `context` называет паспорт и `CLAUDE.md`; ключи под `rules:` —
|
||||
имена артефактов схемы, а не свободные слова. Числа проверок здесь нет намеренно:
|
||||
оно протухает от каждой добавленной.
|
||||
|
||||
**Адреса требуются только к тем документам, которые в проекте есть.** Канон
|
||||
документов ставится отдельным плагином и может быть не подключён; требовать
|
||||
@@ -116,6 +119,9 @@ python3 $os form # слепок формы против жив
|
||||
- `av-dev-docs:init` — шагом заведения нового проекта, до первого документа;
|
||||
- `av-dev-docs:canon` в режиме `adopt` — если на переводимом проекте каталога нет
|
||||
или `config.yaml` остался примером;
|
||||
- `av-dev-code:resolve` и `av-dev-code:review` — не вызовом по ходу, а отсылкой:
|
||||
OpenSpec у обоих жёсткая предпосылка, и на проекте без каталога оба посылают
|
||||
сюда вместо того, чтобы заводить его руками;
|
||||
- человек — когда конвейер отказался работать без источника требований.
|
||||
|
||||
**Копия.** Дом правила — `shared/plugin-boundary.md` в репозитории плагинов.
|
||||
|
||||
@@ -67,6 +67,10 @@ rules:
|
||||
- "Сценарий — ровно #### (четыре решётки); три или список молча теряются"
|
||||
- "SHALL/MUST должно стоять в ПЕРВОМ абзаце требования: валидатор смотрит только его"
|
||||
- "Заголовки и WHEN/THEN/GIVEN — на английском, остальной текст на русском"
|
||||
tasks:
|
||||
- "Критерии приёмки задачи — отдельным блоком и дословно: файл задачи закрытие удалит, критерии обязаны его пережить"
|
||||
- "Рубрика ревью дизайна, если оно её дало, идёт в тот же блок: приёмка судится по одному списку, а не по двум"
|
||||
- "Шаг плана формулируется проверяемо — по нему видно «сделано / не сделано» без суждения"
|
||||
```
|
||||
|
||||
**Четыре правила для `specs` сняты отказами валидатора, а не выведены из
|
||||
@@ -79,7 +83,14 @@ rules:
|
||||
артефактов**, а не сочиняется заново: третий пересказ одного и того же разошёлся
|
||||
бы с обоими. Требование поэтому стоит здесь, в момент порождения артефакта, а не
|
||||
в скилле, который спохватится позже. `design.md` при этом ещё и **сырьё для
|
||||
ADR** — отвергнутый вариант с названной причиной и есть половина будущей записи. Блок `context` проект
|
||||
ADR** — отвергнутый вариант с названной причиной и есть половина будущей записи.
|
||||
|
||||
**Правила для `tasks` держит тот же скилл, и по той же причине — момент
|
||||
порождения.** `tasks.md` — единственное, что переживает задачу: файл задачи
|
||||
закрытие удаляет, а приёмка потом судится по критериям, которые в него
|
||||
скопированы. Туда же ложится рубрика ревью дизайна, если оно её дало. Записанное
|
||||
в момент порождения не приходится вспоминать шагом позже, когда артефакт уже
|
||||
написан. Блок `context` проект
|
||||
дополняет своим (стек, разведка, особенности домена), но **адреса паспорта и
|
||||
`CLAUDE.md` обязательны** — отсутствие адреса к существующему документу
|
||||
`openspec.py check` называет отказом.
|
||||
|
||||
@@ -134,6 +134,35 @@ def rules_keys(live: str) -> list[str]:
|
||||
return out
|
||||
|
||||
|
||||
def rules_block(live: str, name: str) -> str:
|
||||
"""Строки правил, адресованных одному артефакту.
|
||||
|
||||
Обход тот же, что у `rules_keys`, и по той же причине: искать по всему файлу
|
||||
нельзя. Литеральный скаляр `context` называет `SHALL` уже в образце, поэтому
|
||||
проверка «правила называют SHALL» грепом по файлу проходила при **пустом**
|
||||
`rules.specs` — то есть молчала ровно в том случае, ради которого написана.
|
||||
"""
|
||||
out: list[str] = []
|
||||
in_rules = False
|
||||
in_name = False
|
||||
for line in live.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
if not line[0].isspace():
|
||||
in_rules = line.startswith("rules:")
|
||||
in_name = False
|
||||
continue
|
||||
if not in_rules:
|
||||
continue
|
||||
m = re.fullmatch(r" ([A-Za-z_-]+):\s*", line)
|
||||
if m:
|
||||
in_name = m.group(1) == name
|
||||
continue
|
||||
if in_name:
|
||||
out.append(line)
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
def check_form(root: Path, rep: Report) -> None:
|
||||
"""Настройка заведена и не осталась примером из коробки."""
|
||||
os_dir = root / "openspec"
|
||||
@@ -197,12 +226,12 @@ def check_form(root: Path, rep: Report) -> None:
|
||||
if pointer not in live:
|
||||
rep.error(f"openspec/config.yaml не называет {pointer} — {why}")
|
||||
|
||||
if "rules" not in keys or "specs:" not in live:
|
||||
if "rules" not in keys or "specs" not in rules_keys(live):
|
||||
rep.error(
|
||||
"в openspec/config.yaml нет rules.specs — придирки валидатора "
|
||||
"нигде не записаны, и каждое предложение узнаёт их отказом"
|
||||
)
|
||||
elif "SHALL" not in live:
|
||||
elif "SHALL" not in rules_block(live, "specs"):
|
||||
rep.error(
|
||||
"rules.specs в openspec/config.yaml не называет SHALL — "
|
||||
"требование без этого литерала валидатор отвергает, а правило "
|
||||
|
||||
@@ -17,11 +17,13 @@ description: "Решить одну задачу от постановки до
|
||||
## Предпосылки
|
||||
|
||||
- **OpenSpec и скиллы `opsx:*` — жёсткая предпосылка, а не опция.** На них стоят
|
||||
шаги 2, 6 и 8, проход `review-specs` и ревью дизайна (они завязаны на
|
||||
`openspec/changes/<id>/specs/*/spec.md` и на `openspec validate --strict`).
|
||||
**Проект без OpenSpec этим скиллом не ведётся** — подключай OpenSpec, а не
|
||||
вырождай цикл: ветка деградации здесь не пишется, потому что непроверенная
|
||||
ветка деградации хуже честного отказа.
|
||||
шаги 2, 6 и 8, шаг Р2 разведки, проход `review-specs` и ревью дизайна (они
|
||||
завязаны на `openspec/changes/<id>/specs/*/spec.md` и на
|
||||
`openspec validate --strict`). **Проект без OpenSpec этим скиллом не ведётся** —
|
||||
подключай OpenSpec, а не вырождай цикл: ветка деградации здесь не пишется,
|
||||
потому что непроверенная ветка деградации хуже честного отказа. Заводить
|
||||
руками не надо: каталог и настройку в `config.yaml` делает скилл
|
||||
`av-dev-code:openspec`.
|
||||
- **Проектные копии этих скиллов и агентов удаляются при установке плагина**
|
||||
(`.claude/skills/` — голые имена `resolve`, `review`, а у проектов прошлого
|
||||
поколения ещё `task-pipeline`, `review-pipeline`, `task-batch`, и с префиксом
|
||||
@@ -136,7 +138,8 @@ flowchart TD
|
||||
r3 -.->|"кода не будет"| rout
|
||||
s1 --> s2 --> s3 --> s4 --> s5 --> s6 --> s7 --> s8 --> s9 --> s10 --> s11
|
||||
s3 -.->|"план задачи: та же метка"| s7
|
||||
s7 -.->|"находка отменяет дизайн"| s5
|
||||
s5 -.->|"скорректировать:<br/>меняются дельта-спеки"| s3
|
||||
s7 -.->|"находка отменяет дизайн:<br/>меняются дельта-спеки"| s3
|
||||
```
|
||||
|
||||
Схема — **сводка**: содержание каждого шага в его разделе ниже, и при
|
||||
@@ -213,7 +216,9 @@ flowchart TD
|
||||
возвращает задачу `reopen` с причиной, а доклад по критериям приёмки становится
|
||||
единственным, по чему приёмка вообще возможна.
|
||||
- **Заведением задач из урожая ревью.** Отложенные находки отдаются **списком**;
|
||||
превращать их в задачи — работа того, кто ведёт задачи проекта.
|
||||
превращать их в задачи — работа `av-dev-tasks:tasks`, у него на этот вход
|
||||
отдельный сценарий «задачи из ревью и аудита». Плагина нет — урожай остаётся
|
||||
списком в докладе, и это говорится строкой.
|
||||
- **Определением ценности.** «Нужна ли эта функциональность» — не вопрос этого
|
||||
скилла ни на одном шаге. Чекпоинт спрашивает «так ли решаем», а не «надо ли».
|
||||
|
||||
@@ -519,6 +524,11 @@ flowchart TD
|
||||
(разметка выведена из дельта-спек) и **вернись на чекпоинт шага 5** с тем, что
|
||||
изменилось и почему.
|
||||
|
||||
**Это правило старше правила о развилке.** Находка класса `развилка`, чьё
|
||||
основание — «надо менять спеку», подпадает под оба; побеждает возврат на
|
||||
чекпоинт, а вопрос в запись при нём остаётся, но возврата не заменяет. Иначе
|
||||
одобренный дизайн переделывался бы записанным вопросом, то есть молча.
|
||||
|
||||
Тихо переделать утверждённое нельзя. Чекпоинт, который можно обойти находкой
|
||||
ревью, не значит ничего, а человек при этом уверен, что одобрил именно то, что
|
||||
уехало в коммит.
|
||||
@@ -526,8 +536,9 @@ flowchart TD
|
||||
**Урожай — списком, не задачами.** Отложенные находки (реальный `major` не для
|
||||
этого мерджа, развилка, решённая «потом», пачка `nit`) собери в секцию доклада
|
||||
`Урожай`: формулировка, оракул, провенанс. Задачи из него **заводит не этот
|
||||
скилл** — у того, кто ведёт задачи проекта, своя нарезка, свой формат и свои
|
||||
правила дублей. Твоя обязанность — не потерять и передать.
|
||||
скилл** — их заводит `av-dev-tasks:tasks` своим сценарием «задачи из ревью и
|
||||
аудита»: своя нарезка, свой формат, свои правила дублей. Твоя обязанность — не
|
||||
потерять и передать.
|
||||
|
||||
**Границы покрытия из отчёта не выбрасывай** — они уезжают в финальный доклад
|
||||
сжатой строкой. Отчёт, из которого исчезло «что проверить было невозможно»,
|
||||
@@ -564,6 +575,13 @@ flowchart TD
|
||||
[references/project-facts.md](../review/references/project-facts.md)
|
||||
конвейера ревью перечисляет все документы канона с их предметом. Пройди по этому
|
||||
перечню — каждый документ получает строку, отрицание остаётся обязательным.
|
||||
|
||||
**Двух домов в том перечне нет намеренно, а синку они нужны: `docs/adr/` и
|
||||
`docs/research/`.** Проход ревью их не открывает — процессные, — но ADR как раз
|
||||
**главный выход синка**: решение, принятое по ходу задачи, без этой строки
|
||||
теряется систематически. Добавь их к перечню руками: `adr/` — решение с ценой,
|
||||
принятое в этой задаче; `research/` — записка разведки, если ветка была
|
||||
исследовательской.
|
||||
Триггеры при этом ты знаешь хуже, и это называется в докладе строкой: «синк
|
||||
сделан по перечню документов, без списка триггеров — плагина `av-dev-docs` нет».
|
||||
Канона в проекте тоже нет — назови это исходом и предложи `av-dev-docs:canon`.
|
||||
@@ -573,9 +591,10 @@ flowchart TD
|
||||
Коммить **в текущую ветку** (`git rev-parse --abbrev-ref HEAD`), сам ветку не
|
||||
создавай и не переключай, ничего не пушь.
|
||||
|
||||
Сообщение — по-русски, скиллом `av-dev-git:commit`, если он подключён (первая
|
||||
строка «что сделано», тело списком 1–3 пункта, без трейлеров). Одна задача — один
|
||||
осмысленный коммит.
|
||||
Сообщение — по-русски, **скиллом `av-dev-git:commit`**: форму сообщения держит
|
||||
он, и здесь она не пересказывается. Вызов не разрешился — плагина в проекте нет:
|
||||
напиши сообщение сам и скажи строкой доклада, что форму коммита не сверял никто.
|
||||
Одна задача — один осмысленный коммит.
|
||||
|
||||
### 11. Закрыть задачу — **после коммита, не раньше**
|
||||
|
||||
|
||||
@@ -54,12 +54,12 @@ description: "Конвейер ревью изменения, устроенны
|
||||
непроверенная ветка деградации хуже честного отказа. Заводить руками не надо:
|
||||
этим владеет скилл `av-dev-code:openspec` — он заводит каталог и заменяет
|
||||
пример в `config.yaml` настройкой. Его же зовут `av-dev-docs:init` на новом
|
||||
проекте и `canon adopt` на переводимом.
|
||||
проекте и `av-dev-docs:canon` в режиме `adopt` — на переводимом.
|
||||
- **Документы канона** — см. следующий раздел.
|
||||
- **Проектные копии этих скиллов и агентов удаляются при установке.** Если в
|
||||
проекте уже лежат свои `.claude/skills/review`,
|
||||
`.claude/skills/review-pipeline`, `.claude/skills/task-pipeline`,
|
||||
`.claude/skills/resolve` или
|
||||
`.claude/skills/task-batch`, `.claude/skills/resolve` или
|
||||
`.claude/agents/<проект>-review-*.md` — снеси их. Иначе короткое имя разрешится
|
||||
в устаревшую проектную копию, молча и без признаков подмены.
|
||||
|
||||
@@ -121,8 +121,8 @@ description: "Конвейер ревью изменения, устроенны
|
||||
критерий, по которому судит изменение. `adr.*`, `research.*` и `tasks/` не
|
||||
открывает никто.
|
||||
|
||||
Дом канона этой раскладки — `av-dev-docs`, `references/canon.md`, раздел «Три
|
||||
категории документов». Конвейер её **читатель**: категории и имена тем он берёт
|
||||
Дом канона этой раскладки — скилл `av-dev-docs:canon`, раздел «Три категории
|
||||
документов». Конвейер её **читатель**: категории и имена тем он берёт
|
||||
оттуда и своих не заводит.
|
||||
|
||||
Отсюда то, ради чего правило и заведено: **`docs/` перестаёт быть просто
|
||||
@@ -866,8 +866,9 @@ Recall темы `conventions` равен длине конвенций прое
|
||||
- **всегда** — `review-specs` в режиме «дизайн ДО кода». Дельта-спеки сверяются
|
||||
на каждой задаче: это самый дешёвый чекпоинт конвейера, и он ловит то, что на
|
||||
готовом коде уже не чинят;
|
||||
- **со `medium`** — `review-rubric` (фаза 1 без фазы 2: рубрика на задуманный
|
||||
узел становится приёмочными критериями и уезжает в `tasks.md`);
|
||||
- **со `medium`** — `review-rubric`: рубрика на задуманный узел, по ней же
|
||||
разбирается дельта-спека, а сами пункты уезжают приёмочными критериями в
|
||||
`tasks.md`;
|
||||
- **только в `large`** — `review-architecture` на предложении: можно ли выразить
|
||||
существующими понятиями — **включая конструкции стандартной библиотеки**, — не
|
||||
появляется ли второй способ. Вопрос «не изобретаем ли то, что уже есть в
|
||||
@@ -900,7 +901,7 @@ flowchart TD
|
||||
plan[/"план разметки задачи:<br/>размер, сложность, метка"/]
|
||||
proposal["предложение: proposal.md + дельта-спеки"]
|
||||
specs["specs (режим «дизайн ДО кода») — всегда"]
|
||||
rubric["rubric, фаза 1 → приёмочные критерии в tasks.md"]
|
||||
rubric["rubric → приёмочные критерии в tasks.md"]
|
||||
arch["architecture на предложении"]
|
||||
author["вопрос автору: три формы решения и компромисс каждой"]
|
||||
fix["шаг пайплайна: правка спек, развилки — вопросом в запись"]
|
||||
@@ -942,8 +943,11 @@ flowchart TD
|
||||
- Находка не для этого мерджа, но реальная (отложенный `major`, развилка,
|
||||
решённая «потом»), — не теряется, но **и не заводится здесь**. Конвейер отдаёт
|
||||
её **списком урожая** в отчёте: формулировка, оракул, провенанс (какой проход,
|
||||
какой change). Заведение задач принадлежит тому, кто ведёт задачи проекта, —
|
||||
у него свой формат, своя нарезка и свои правила дублей. Мелочь класса `nit`
|
||||
какой change). Заведение задач принадлежит `av-dev-tasks:tasks` — зови его со
|
||||
списком урожая, у него на этот вход отдельный сценарий «задачи из ревью и
|
||||
аудита»: свой формат, кластеризация по причине, дедуп против беклога и
|
||||
кладбища. Плагина нет — урожай остаётся списком в отчёте, и это говорится
|
||||
строкой доклада: задачи из него не заведёт никто. Мелочь класса `nit`
|
||||
идёт в урожай одной пачкой, а не записью на находку.
|
||||
- `Promote candidates` — по процедуре [references/promote.md](references/promote.md):
|
||||
находка → конвенция → правило линтера → **удаление формулировки из конвенций**.
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
`av-dev-docs`, и проход читает их напрямую: пути жёсткие, посредник не нужен, а
|
||||
второй дом для тех же фактов разошёлся бы и выглядел актуальным.
|
||||
|
||||
Определение канона — в плагине `av-dev-docs`,
|
||||
`skills/canon/references/canon.md`. Здесь только карта «тема → её дом → что
|
||||
оттуда берётся».
|
||||
Определение канона держит скилл `av-dev-docs:canon`. Здесь только карта «тема →
|
||||
её дом → что оттуда берётся».
|
||||
|
||||
## Карта тем
|
||||
|
||||
|
||||
@@ -41,8 +41,7 @@
|
||||
## Форма записи
|
||||
|
||||
**Это дом формы, и у него есть копия.** Скелет `docs/review.md`, который кладёт
|
||||
в проект `av-dev-docs` (`skills/canon/references/skeletons.md`), повторяет её
|
||||
дословно — он уезжает в репозиторий и обязан там что-то говорить. Правка формы
|
||||
в проект `av-dev-docs:canon`, повторяет её дословно — он уезжает в репозиторий и обязан там что-то говорить. Правка формы
|
||||
здесь **обязана** тянуть правку скелета и запись в журнал версий канона; иначе
|
||||
проекты продолжат писать по старой форме, а конвейер — ждать поля, которого нет.
|
||||
Дословность сверяет `scripts/copies.py` маркетплейса по маркерам ниже — но
|
||||
|
||||
@@ -94,9 +94,9 @@
|
||||
костяк — гейт, спеки, код, триаж**. Разрезать задачу, обе половины которой
|
||||
остаются в одной метке, значит заплатить костяк дважды за ту же проверку.
|
||||
Резать стоит там, где разрез **снимает доказательство с большей части диффа**.
|
||||
Шов и правило нарезки живут у того, кто ведёт задачи, — скилл `av-dev-tasks:tasks`,
|
||||
его `references/split.md`. Пути туда конвейер не выносит: за пределы своего
|
||||
плагина он ходит вызовом скилла, а не файлом.
|
||||
Шов и правило нарезки живут у того, кто ведёт задачи, — скилл
|
||||
`av-dev-tasks:tasks`, его раздел о нарезке. Пути туда конвейер не выносит: за
|
||||
пределы своего плагина он ходит вызовом скилла, а не файлом.
|
||||
|
||||
Разметка в костяк не входит — она платится один раз на задачу, а не один раз на
|
||||
прогон, и потому **разрез задачи её не удваивает**. Это единственное, что стало
|
||||
|
||||
Reference in New Issue
Block a user