канон 13: файл версии зовётся по владельцу, у задач появилась своя версия формата
Имя `.pm.json` пережило плагин `av-dev-pm` на два месяца и указывало в пустоту. Правило, которое из этого вынуто: имя служебного файла — имя плагина, который его завёл, и по нему же владельца узнают. - `docs/.pm.json` → `docs/.docs.json`, запись 13 журнала. Прежнее имя docs.py не читает намеренно: по этому числу upgrade решает, какие записи применять, и два дома разъехались бы молча ровно там, где это дороже всего. Вместо совместимости — узнавание: check видит старый файл и печатает готовую git mv - у каталога задач появилась своя версия формата — ключ `tasks` в `.tasks.json`, свой журнал версий и своё повышение. До сих пор её не было вовсе, хотя docs.py в комментарии уверенно на неё ссылался: описание опережало механику ровно так, как сказано в решении 195 - число своё, а не копия канонического: плагин ставится в одиночку, и у проекта без docs/ версии канона нет — сверять было бы не с чем - конфиг задач стал обязательным (init и adopt apply пишут его всегда), check сверяет число, `check --fix` его не приписывает: приписанное объявляло бы каталог приведённым к формату, шагов которого никто не делал - переезды 11 и 12 в новый журнал задним числом не переписаны — версия 1 велит догнать формат по журналу канона, называя признаки отставания поимённо (каталог в docs/tasks/, живой SPRINT.md) - запись 60 в DECISIONS со следствиями 200–203; отдельно разведено с решением F, где `.docs.json` отвергался как указатель путей: отвергнут был указатель, а не имя
This commit is contained in:
@@ -3647,3 +3647,69 @@ change нет — берём источником актуальные спек
|
|||||||
которую никто не набрал, может не существовать вовсе — и именно так и было.
|
которую никто не набрал, может не существовать вовсе — и именно так и было.
|
||||||
199. **Два дома у факта расходятся не когда-нибудь, а сразу.** Из четырёх пар
|
199. **Два дома у факта расходятся не когда-нибудь, а сразу.** Из четырёх пар
|
||||||
описаний плагина совпала одна — та, которую с момента заведения не правили.
|
описаний плагина совпала одна — та, которую с момента заведения не правили.
|
||||||
|
|
||||||
|
## 60. Служебный файл зовётся по плагину-владельцу; у задач появилась своя версия формата (2026-08-11)
|
||||||
|
|
||||||
|
Файл версии канона звался `docs/.pm.json` — по плагину `av-dev-pm`, который
|
||||||
|
распался на четыре ещё в решении 56 и которого больше нет. Имя пережило
|
||||||
|
владельца на два месяца и указывало в пустоту: читающий его искал плагин, о
|
||||||
|
котором в репозитории не осталось ни строки. Переименован в `docs/.docs.json`
|
||||||
|
записью 13 журнала канона.
|
||||||
|
|
||||||
|
**Правило, которое из этого вынуто и теперь держит все три файла:** имя
|
||||||
|
служебного файла — имя плагина, который его завёл. `.docs.json` — канон,
|
||||||
|
`.tasks.json` — задачи, `openspec/config.yaml` — конвейер. По этому же следу
|
||||||
|
скиллы узнают, что сосед в проекте работал, и правило перестало быть просто
|
||||||
|
перечнем — оно выводимо.
|
||||||
|
|
||||||
|
**Прежнее имя `docs.py` не читает.** Соблазн «прочитать оба и не мешать людям»
|
||||||
|
здесь стоит дороже, чем везде: по этому числу `upgrade` решает, какие записи
|
||||||
|
журнала применять, и два дома для него разъехались бы молча в том самом месте,
|
||||||
|
где расхождение и вредно. Вместо совместимости — узнавание: `check` видит файл
|
||||||
|
под старым именем и печатает готовую команду `git mv`.
|
||||||
|
|
||||||
|
**У каталога задач появилась своя версия формата** — ключ `tasks` в
|
||||||
|
`<каталог задач>/.tasks.json` и свой журнал версий в скилле `av-dev-tasks:tasks`.
|
||||||
|
До сих пор её не было вовсе, хотя `docs.py` в комментарии уверенно ссылался на
|
||||||
|
«свою версию формата» соседа: описание опережало механику ровно так, как описано
|
||||||
|
в решении 195. Формат задач при этом менялся — записями 8, 11 и 12 чужого
|
||||||
|
журнала.
|
||||||
|
|
||||||
|
**Число именно своё, а не копия канонического.** Плагин ставится в одиночку:
|
||||||
|
проект, взявший учёт работ без канона документов, каталога `docs/` не имеет
|
||||||
|
вовсе, а значит не имеет и версии канона — сверять было бы не с чем. Копия
|
||||||
|
чужого числа в `tasks.py` была бы вторым домом одной версии и разъехалась бы при
|
||||||
|
первом же обновлении одного плагина без другого.
|
||||||
|
|
||||||
|
**Переезды, случившиеся до появления числа, задним числом в новый журнал не
|
||||||
|
переписаны.** Версия 1 — это формат на день её появления; что проекту нужно было
|
||||||
|
пройти до неё, названо шагом «догнать формат по журналу канона» с поимёнными
|
||||||
|
признаками отставания (каталог в `docs/tasks/`, живой `SPRINT.md`). Второй
|
||||||
|
перечень тех же шагов разошёлся бы с первым — это ровно та ошибка, из-за которой
|
||||||
|
план однажды повторял записи версий 3, 4 и 5 построчно.
|
||||||
|
|
||||||
|
**Конфиг задач стал обязательным.** Раньше он заводился только ради имён,
|
||||||
|
отличных от умолчания, и проект с умолчаниями жил без файла вовсе. Версия — не
|
||||||
|
настройка, от которой можно отказаться, поэтому `init` и `adopt apply` пишут его
|
||||||
|
всегда, а `check` требует числа.
|
||||||
|
|
||||||
|
Отдельно стоит сказать, чтобы не спутали при чтении журнала: `.docs.json`
|
||||||
|
однажды уже был отвергнут — решением F, но **как указатель путей**. Отвергнут
|
||||||
|
был указатель, а не имя; сегодняшний файл путями проекта не распоряжается, он
|
||||||
|
объявляет версию и называет то немногое, чего из раскладки не вывести.
|
||||||
|
|
||||||
|
### Что из этого следует
|
||||||
|
|
||||||
|
200. **Имя служебного файла — часть границы плагинов, а не деталь.** Оно
|
||||||
|
называет владельца, и по нему же владельца узнают. Пережившее владельца имя
|
||||||
|
врёт дважды: указывает на несуществующее и прячет того, кто файл ведёт на
|
||||||
|
самом деле.
|
||||||
|
201. **Версия нужна каждому формату, который живёт в чужом репозитории.** Без
|
||||||
|
числа «приведён ли проект» не имеет определённого ответа, и отставший
|
||||||
|
каталог выглядит здоровым до первой команды, которая об него споткнётся.
|
||||||
|
202. **Своя версия — у своего плагина, всегда.** Общее число на два плагина
|
||||||
|
переживает ровно до первого проекта, где поставлен один из них.
|
||||||
|
203. **Версию двигают руками, и это не слабость проверки.** Число отвечает
|
||||||
|
на вопрос «по какой записи повышать», а не «сделаны ли шаги по существу».
|
||||||
|
Машина, приписывающая недостающее число сама, объявляет проект приведённым
|
||||||
|
к формату, которого никто не проходил.
|
||||||
|
|||||||
@@ -143,7 +143,16 @@ OpenSpec скиллом `av-dev-code:openspec`, `av-dev-docs:docs` берёт у
|
|||||||
|
|
||||||
Прийти в старый проект и перевести его на канон — `/av-dev-docs:canon`. Канон
|
Прийти в старый проект и перевести его на канон — `/av-dev-docs:canon`. Канон
|
||||||
версионируется, и проекты повышаются по [журналу
|
версионируется, и проекты повышаются по [журналу
|
||||||
версий](av-dev-docs/skills/canon/references/changelog.md).
|
версий](av-dev-docs/skills/canon/references/changelog.md); версия проекта живёт в
|
||||||
|
`docs/.docs.json`.
|
||||||
|
|
||||||
|
**Версий две, и они независимы.** У каталога задач своя — ключ `tasks` в
|
||||||
|
`<каталог задач>/.tasks.json`, свой [журнал
|
||||||
|
версий](av-dev-tasks/skills/tasks/references/changelog.md) и своё повышение
|
||||||
|
скиллом `/av-dev-tasks:tasks`. Плагины ставятся порознь: у проекта, взявшего учёт
|
||||||
|
работ без канона документов, `docs/` нет вовсе, и общее число оказалось бы домом,
|
||||||
|
которого у половины проектов не существует. Имя служебного файла при этом
|
||||||
|
называет владельца — `.docs.json`, `.tasks.json`, `openspec/config.yaml`.
|
||||||
|
|
||||||
## Подключение
|
## Подключение
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
цикл SDD, конвейер ревью, OpenSpec), `av-dev-git`. Общее, что нужно нескольким
|
цикл SDD, конвейер ревью, OpenSpec), `av-dev-git`. Общее, что нужно нескольким
|
||||||
дословно, живёт домом в `shared/` и уезжает копиями.
|
дословно, живёт домом в `shared/` и уезжает копиями.
|
||||||
|
|
||||||
Канон документов — **версия 12**. Живые проекты стоят на 2–3 и на плагине
|
Канон документов — **версия 13**; формат задач — **версия 1**, своя и со своим журналом. Живые проекты стоят на 2–3 и на плагине
|
||||||
`av-dev-pm`, которого больше нет.
|
`av-dev-pm`, которого больше нет.
|
||||||
|
|
||||||
Бумажная часть закрыта аудитом четырёх плагинов и четырьмя пропусками правок
|
Бумажная часть закрыта аудитом четырёх плагинов и четырьмя пропусками правок
|
||||||
@@ -42,11 +42,12 @@
|
|||||||
- [ ] удалить проектные копии: `.claude/skills/healthlog-{task,review}-pipeline`
|
- [ ] удалить проектные копии: `.claude/skills/healthlog-{task,review}-pipeline`
|
||||||
и девять `.claude/agents/healthlog-review-*.md`. Они прошлого поколения и
|
и девять `.claude/agents/healthlog-review-*.md`. Они прошлого поколения и
|
||||||
после переезда указывают на документы, которых уже не будет
|
после переезда указывают на документы, которых уже не будет
|
||||||
- [ ] `av-dev-docs:canon` в режиме `adopt` — он приведёт проект к канону 12
|
- [ ] `av-dev-docs:canon` в режиме `adopt` — он приведёт проект к канону 13
|
||||||
сразу, картой и с подтверждением. Файл-в-файл здесь не расписан: раскладку
|
сразу, картой и с подтверждением. Файл-в-файл здесь не расписан: раскладку
|
||||||
знает скилл, и второй перечень разошёлся бы с ним
|
знает скилл, и второй перечень разошёлся бы с ним
|
||||||
- [ ] каталог задач — в `tasks/` корня (канон 11), не в `docs/tasks/`, и без
|
- [ ] каталог задач — в `tasks/` корня (канон 11), не в `docs/tasks/`, без
|
||||||
`SPRINT.md` (канон 12). Скилл задач зовётся из `adopt` сам
|
`SPRINT.md` (канон 12) и с версией формата в `tasks/.tasks.json` (журнал
|
||||||
|
задач, версия 1). Скилл задач зовётся из `adopt` сам
|
||||||
- [ ] гейт проекта: три шага вместо одного — `docs.py check`, `tasks.py check
|
- [ ] гейт проекта: три шага вместо одного — `docs.py check`, `tasks.py check
|
||||||
--dir tasks`, `openspec.py check`. **Второй и третий раньше не были
|
--dir tasks`, `openspec.py check`. **Второй и третий раньше не были
|
||||||
нужны:** согласованность задач тянул за собой `docs.py`, форму `config.yaml`
|
нужны:** согласованность задач тянул за собой `docs.py`, форму `config.yaml`
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ color: green
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **тема** | `conventions.*`, `security.*`, `architecture.*`, любой свой документ проекта | заводишь строку темы и назначаешь исполнителя |
|
| **тема** | `conventions.*`, `security.*`, `architecture.*`, любой свой документ проекта | заводишь строку темы и назначаешь исполнителя |
|
||||||
| **источник темы** | `passport.*`, `database.*` | называешь адресом **внутри** строки чужой темы, своей строки не заводишь |
|
| **источник темы** | `passport.*`, `database.*` | называешь адресом **внутри** строки чужой темы, своей строки не заводишь |
|
||||||
| **процессный** | `tasks/`, `review.*`, `adr.*`, `research.*`, `.pm.json` | называешь строкой «процессный», исполнителя нет и не должно быть |
|
| **процессный** | `tasks/`, `review.*`, `adr.*`, `research.*`, `.docs.json` | называешь строкой «процессный», исполнителя нет и не должно быть |
|
||||||
|
|
||||||
`docs/review.*` при этом ты читаешь — но как **настройку конвейера**, откуда
|
`docs/review.*` при этом ты читаешь — но как **настройку конвейера**, откуда
|
||||||
берутся вопросы по темам и триггеры метки, а не как тему. `adr.*` и `research.*`
|
берутся вопросы по темам и триггеры метки, а не как тему. `adr.*` и `research.*`
|
||||||
@@ -128,7 +128,7 @@ color: green
|
|||||||
**Каждая запись в `docs/` обязана попасть в план строкой своей категории.** Не «я
|
**Каждая запись в `docs/` обязана попасть в план строкой своей категории.** Не «я
|
||||||
посмотрел и решил» — перечислением. Это и есть проверка твоей работы: план
|
посмотрел и решил» — перечислением. Это и есть проверка твоей работы: план
|
||||||
сверяется с `ls docs/` за секунду, и пропущенный документ виден без рассуждения.
|
сверяется с `ls docs/` за секунду, и пропущенный документ виден без рассуждения.
|
||||||
`docs/.pm.json` — единственное исключение: служебный файл, не документ, в плане
|
`docs/.docs.json` — единственное исключение: служебный файл, не документ, в плане
|
||||||
не упоминается.
|
не упоминается.
|
||||||
|
|
||||||
**Категории `источник` и `процессный` закрыты — они перечислены выше поимённо.**
|
**Категории `источник` и `процессный` закрыты — они перечислены выше поимённо.**
|
||||||
|
|||||||
@@ -147,8 +147,10 @@ python3 $os form # слепок формы против жив
|
|||||||
|
|
||||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||||
|
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||||
|
|
||||||
<!-- /копия: граница-плагинов -->
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,10 @@ description: "Решить одну задачу от постановки до
|
|||||||
|
|
||||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||||
|
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||||
|
|
||||||
<!-- /копия: граница-плагинов -->
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
|||||||
@@ -88,8 +88,10 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
|
|
||||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||||
|
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||||
|
|
||||||
<!-- /копия: граница-плагинов -->
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
@@ -113,7 +115,7 @@ description: "Конвейер ревью изменения, устроенны
|
|||||||
|---|---|---|
|
|---|---|---|
|
||||||
| **тема** | заводит направление проверки и требует исполнителя | `conventions.*`, `security.*`, `architecture.*`, свои документы проекта |
|
| **тема** | заводит направление проверки и требует исполнителя | `conventions.*`, `security.*`, `architecture.*`, свои документы проекта |
|
||||||
| **источник темы** | читает как материал чужой темы, своей не порождает | `passport.*`, `database.*`, `CLAUDE.md`/`AGENTS.md`, `openspec/specs/` |
|
| **источник темы** | читает как материал чужой темы, своей не порождает | `passport.*`, `database.*`, `CLAUDE.md`/`AGENTS.md`, `openspec/specs/` |
|
||||||
| **процессный документ** | не судит по нему изменение | `tasks/`, `docs/review.*`, `docs/adr.*`, `docs/research.*`, `docs/.pm.json` |
|
| **процессный документ** | не судит по нему изменение | `tasks/`, `docs/review.*`, `docs/adr.*`, `docs/research.*`, `docs/.docs.json` |
|
||||||
|
|
||||||
**Одна процессная запись всё же читается — `docs/review.*`.** В ней лежит
|
**Одна процессная запись всё же читается — `docs/review.*`.** В ней лежит
|
||||||
настройка самого конвейера: вопросы по темам, журнал дефектов, типовые узлы,
|
настройка самого конвейера: вопросы по темам, журнал дефектов, типовые узлы,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: doc-code-drift
|
name: doc-code-drift
|
||||||
description: "Сверка документов канона с кодом по закрытому перечню проверяемых фактов: имя основной ветки и команды из CLAUDE.md, запреты с путями, testdata и временный каталог, путь миграций из .pm.json, внешние зависимости поимённо в architecture.md против манифеста, настройки с числовым значением в database.md против конфига и кода, единые точки проекта против реального числа реализаций, capability против существующих модулей. Отвечает на «этот факт ещё верен», а не «эта архитектура правильная». Читает весь репозиторий, гоняет только читающие команды. Отдаёт готовые формулировки и ничего не правит сам. Согласованность документов между собой смотрит агент doc-consistency. Зовётся скиллом av-dev-docs:healthcheck — на весь канон разом; он же зовётся шагом adopt и шагом upgrade. Только чтение."
|
description: "Сверка документов канона с кодом по закрытому перечню проверяемых фактов: имя основной ветки и команды из CLAUDE.md, запреты с путями, testdata и временный каталог, путь миграций из .docs.json, внешние зависимости поимённо в architecture.md против манифеста, настройки с числовым значением в database.md против конфига и кода, единые точки проекта против реального числа реализаций, capability против существующих модулей. Отвечает на «этот факт ещё верен», а не «эта архитектура правильная». Читает весь репозиторий, гоняет только читающие команды. Отдаёт готовые формулировки и ничего не правит сам. Согласованность документов между собой смотрит агент doc-consistency. Зовётся скиллом av-dev-docs:healthcheck — на весь канон разом; он же зовётся шагом adopt и шагом upgrade. Только чтение."
|
||||||
tools: Read, Grep, Glob, Bash
|
tools: Read, Grep, Glob, Bash
|
||||||
model: sonnet
|
model: sonnet
|
||||||
color: green
|
color: green
|
||||||
@@ -37,7 +37,7 @@ color: green
|
|||||||
|
|
||||||
## Что тебе дают
|
## Что тебе дают
|
||||||
|
|
||||||
Корень проекта. Читаешь `CLAUDE.md`, `docs/**`, `docs/.pm.json`,
|
Корень проекта. Читаешь `CLAUDE.md`, `docs/**`, `docs/.docs.json`,
|
||||||
`openspec/specs/**` — и репозиторий: манифесты зависимостей, конфиги, файлы
|
`openspec/specs/**` — и репозиторий: манифесты зависимостей, конфиги, файлы
|
||||||
сборки и CI, дерево пакетов.
|
сборки и CI, дерево пакетов.
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ color: green
|
|||||||
держит прежнее имя.
|
держит прежнее имя.
|
||||||
|
|
||||||
3. **Пути** — все, которые канон обязывает называть: `migrations` из
|
3. **Пути** — все, которые канон обязывает называть: `migrations` из
|
||||||
`docs/.pm.json`, `testdata`, временный каталог, пути в запретах `CLAUDE.md`.
|
`docs/.docs.json`, `testdata`, временный каталог, пути в запретах `CLAUDE.md`.
|
||||||
Проверка: существует ли. Путь в запрете, которого нет, — находка **особого
|
Проверка: существует ли. Путь в запрете, которого нет, — находка **особого
|
||||||
рода**: запрет, который не на что наложить, читается как соблюдённый, а на
|
рода**: запрет, который не на что наложить, читается как соблюдённый, а на
|
||||||
деле охраняет пустоту, пока настоящий каталог зовётся иначе.
|
деле охраняет пустоту, пока настоящий каталог зовётся иначе.
|
||||||
@@ -147,7 +147,7 @@ color: green
|
|||||||
```
|
```
|
||||||
факт источник проверено чем итог
|
факт источник проверено чем итог
|
||||||
имя основной ветки CLAUDE.md git branch сошлось
|
имя основной ветки CLAUDE.md git branch сошлось
|
||||||
путь миграций docs/.pm.json ls РАЗОШЛОСЬ
|
путь миграций docs/.docs.json ls РАЗОШЛОСЬ
|
||||||
внешние зависимости architecture.md go.mod 2 не названы
|
внешние зависимости architecture.md go.mod 2 не названы
|
||||||
единые точки: парсер входа architecture.md grep по формату сошлось
|
единые точки: парсер входа architecture.md grep по формату сошлось
|
||||||
настройки БД database.md — не проверено
|
настройки БД database.md — не проверено
|
||||||
|
|||||||
@@ -116,8 +116,10 @@ capability: незаполненный канон это переходное с
|
|||||||
|
|
||||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||||
|
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||||
|
|
||||||
<!-- /копия: граница-плагинов -->
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
@@ -176,7 +178,7 @@ capability), `openspec/config.yaml`.
|
|||||||
|
|
||||||
Порядок важен — он минимизирует окно, в котором ссылки битые:
|
Порядок важен — он минимизирует окно, в котором ссылки битые:
|
||||||
|
|
||||||
1. `docs/.pm.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
|
1. `docs/.docs.json` с `{"canon": <текущая версия>}` и путём миграций, если БД есть;
|
||||||
2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**:
|
2. каталоги канона и скелет **по [references/skeletons.md](references/skeletons.md)**:
|
||||||
незаполненное — одной честной информативной строкой, а не «TBD»;
|
незаполненное — одной честной информативной строкой, а не «TBD»;
|
||||||
3. **OpenSpec, если его нет или `config.yaml` остался примером** — **вызови
|
3. **OpenSpec, если его нет или `config.yaml` остался примером** — **вызови
|
||||||
@@ -265,7 +267,7 @@ capability), `openspec/config.yaml`.
|
|||||||
3. Иначе иди по [changelog.md](references/changelog.md) снизу вверх от версии
|
3. Иначе иди по [changelog.md](references/changelog.md) снизу вверх от версии
|
||||||
проекта до текущей и делай названное в каждой записи. Записи независимы и
|
проекта до текущей и делай названное в каждой записи. Записи независимы и
|
||||||
применяются по порядку.
|
применяются по порядку.
|
||||||
4. Подними `canon` в `docs/.pm.json` до текущей.
|
4. Подними `canon` в `docs/.docs.json` до текущей.
|
||||||
5. `docs.py check`.
|
5. `docs.py check`.
|
||||||
6. **Позови судей** — Skill `av-dev-docs:healthcheck`.
|
6. **Позови судей** — Skill `av-dev-docs:healthcheck`.
|
||||||
7. **Позови вычитку** — агент `doc-wording`, но **только по тем документам,
|
7. **Позови вычитку** — агент `doc-wording`, но **только по тем документам,
|
||||||
@@ -276,7 +278,15 @@ capability), `openspec/config.yaml`.
|
|||||||
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
|
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
|
||||||
это дефект журнала, и о нём надо сказать, а не догадываться.
|
это дефект журнала, и о нём надо сказать, а не догадываться.
|
||||||
|
|
||||||
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.pm.json` с
|
**Каталог задач повышается своим журналом, а не этим.** У него своя версия
|
||||||
|
формата — ключ `tasks` в `<каталог задач>/.tasks.json`, — и двигает её плагин
|
||||||
|
`av-dev-tasks`. Запись канона вправе сказать «позови соседа», но не вправе
|
||||||
|
двигать чужое число: две версии, которые ходят по одному журналу, разъезжаются
|
||||||
|
на первом же проекте, поставившем один плагин без другого. Отстал каталог
|
||||||
|
задач — это скажет `tasks.py check` своей строкой гейта, а повысит скилл
|
||||||
|
`av-dev-tasks:tasks`.
|
||||||
|
|
||||||
|
**Шаг 6 обязателен, и вот почему.** `check` сверяет **число** в `.docs.json` с
|
||||||
версией скрипта — и только его. Применена ли запись журнала **по существу**, он
|
версией скрипта — и только его. Применена ли запись журнала **по существу**, он
|
||||||
не знает: проект несёт `"canon": 6` и может не иметь того, чего требовала любая
|
не знает: проект несёт `"canon": 6` и может не иметь того, чего требовала любая
|
||||||
из пройденных версий. Записи применяются руками (переименовать секцию, проставить
|
из пройденных версий. Записи применяются руками (переименовать секцию, проставить
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ CLAUDE.md памятка агенту: что это, ст
|
|||||||
severity, команды, семантика гейта, запреты
|
severity, команды, семантика гейта, запреты
|
||||||
AGENTS.md необязателен, лежит рядом; читается теми же
|
AGENTS.md необязателен, лежит рядом; читается теми же
|
||||||
docs/
|
docs/
|
||||||
.pm.json версия канона и пути, нужные проверкам
|
.docs.json версия канона и пути, нужные проверкам
|
||||||
passport.md | passport/ зачем и для кого; чем НЕ является; сценарии
|
passport.md | passport/ зачем и для кого; чем НЕ является; сценарии
|
||||||
architecture.md | architecture/ как сложено — обзор; окружение и эксплуатация
|
architecture.md | architecture/ как сложено — обзор; окружение и эксплуатация
|
||||||
database.md | database/ схема хранилища; представление данных и настройки
|
database.md | database/ схема хранилища; представление данных и настройки
|
||||||
@@ -119,7 +119,7 @@ openspec/
|
|||||||
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
|
| `review.*` | процессный | — (настройка самого конвейера, слой **над** темами) |
|
||||||
| `adr.*` | процессный | — |
|
| `adr.*` | процессный | — |
|
||||||
| `research.*` | процессный | — |
|
| `research.*` | процессный | — |
|
||||||
| `.pm.json` | процессный | — (служебный файл, не документ) |
|
| `.docs.json` | процессный | — (служебный файл, не документ) |
|
||||||
|
|
||||||
**Список тем открытый, и это не послабление, а механизм.** Категории
|
**Список тем открытый, и это не послабление, а механизм.** Категории
|
||||||
`источник` и `процессный` **закрыты** — они перечислены здесь поимённо и
|
`источник` и `процессный` **закрыты** — они перечислены здесь поимённо и
|
||||||
@@ -354,8 +354,10 @@ kebab-case.** Причина не эстетическая: имя файла с
|
|||||||
### `tasks/`
|
### `tasks/`
|
||||||
|
|
||||||
**Каталог задач канону не принадлежит.** Его ведёт отдельный плагин
|
**Каталог задач канону не принадлежит.** Его ведёт отдельный плагин
|
||||||
`av-dev-tasks` — своим скриптом, своим конфигом `<каталог>/.tasks.json` и своей
|
`av-dev-tasks` — своим скриптом, своим конфигом `<каталог>/.tasks.json`, своей
|
||||||
версией формата. Канон **резервирует место** в `docs/` и внутрь не смотрит:
|
версией формата в нём же и своим журналом версий. Канон о том числе не
|
||||||
|
высказывается и его не двигает: повышает каталог задач тот, кто его ведёт.
|
||||||
|
Канон **резервирует место** в `docs/` и внутрь не смотрит:
|
||||||
`docs.py` каталог не открывает, его отсутствия не считает дрейфом и согласованность
|
`docs.py` каталог не открывает, его отсутствия не считает дрейфом и согласованность
|
||||||
задач не проверяет. Проект, поставивший только канон документов, задач не ведёт
|
задач не проверяет. Проект, поставивший только канон документов, задач не ведёт
|
||||||
вовсе, и отказом это быть не может.
|
вовсе, и отказом это быть не может.
|
||||||
@@ -546,7 +548,7 @@ kebab-case.** Причина не эстетическая: имя файла с
|
|||||||
архитектуру с кодом» задача без дна, и агент, которому её поставили, выдаёт
|
архитектуру с кодом» задача без дна, и агент, которому её поставили, выдаёт
|
||||||
правдоподобную труху вместо находок.
|
правдоподобную труху вместо находок.
|
||||||
|
|
||||||
## `docs/.pm.json`
|
## `docs/.docs.json`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -562,12 +564,23 @@ kebab-case.** Причина не эстетическая: имя файла с
|
|||||||
`migrations` — путь каталога миграций, если БД есть; по нему `docs.py` делает
|
`migrations` — путь каталога миграций, если БД есть; по нему `docs.py` делает
|
||||||
сверку с `database.md`.
|
сверку с `database.md`.
|
||||||
|
|
||||||
|
**Имя файла — имя плагина, который его завёл.** Канон документов ведёт
|
||||||
|
`av-dev-docs`, поэтому `.docs.json`; у каталога задач по тому же правилу
|
||||||
|
`.tasks.json`, у конвейера — `openspec/config.yaml`. До версии 13 файл звался
|
||||||
|
`.pm.json` — по плагину `av-dev-pm`, который распался на четыре и которого
|
||||||
|
больше нет; имя пережило владельца и указывало в пустоту. Прежнее имя `docs.py`
|
||||||
|
не читает: два дома для одной версии канона расходятся молча, а переименование
|
||||||
|
стоит одну команду (версия 13 журнала, и `check` называет её сам, когда видит
|
||||||
|
старый файл).
|
||||||
|
|
||||||
**Ключа `tasks` здесь больше нет.** Настройки каталога задач вернулись в свой
|
**Ключа `tasks` здесь больше нет.** Настройки каталога задач вернулись в свой
|
||||||
файл `<каталог задач>/.tasks.json`, потому что ведёт их другой плагин: конфиг,
|
файл `<каталог задач>/.tasks.json`, потому что ведёт их другой плагин: конфиг,
|
||||||
лежащий в `docs/`, был бы домом, которого нет у проекта, поставившего учёт работ
|
лежащий в `docs/`, был бы домом, которого нет у проекта, поставившего учёт работ
|
||||||
без канона документов. Состав ключей описывает тот плагин, а не канон. Прежний
|
без канона документов. Состав ключей описывает тот плагин, а не канон. Там же —
|
||||||
ключ читается, пока живы непереехавшие проекты, и `tasks.py` говорит о нём
|
**версия формата задач**, и она своя: у проекта без `docs/` версии канона нет
|
||||||
замечанием на каждом прогоне — версия 8 журнала просит его убрать.
|
вовсе, сверять её было бы не с чем. Прежний ключ читается, пока живы
|
||||||
|
непереехавшие проекты, и `tasks.py` говорит о нём замечанием на каждом
|
||||||
|
прогоне — версия 8 журнала просит его убрать.
|
||||||
|
|
||||||
Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py`
|
Ключей будет больше по мере роста проверок; неизвестный ключ `docs.py`
|
||||||
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
|
игнорирует, отсутствующий — считает «проверка неприменима» и говорит об этом
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
# Журнал версий канона
|
# Журнал версий канона
|
||||||
|
|
||||||
Одна запись на версию. Проект знает свою версию из `docs/.pm.json`; `canon
|
Одна запись на версию. Проект знает свою версию из `docs/.docs.json`; `canon
|
||||||
upgrade` идёт по записям снизу вверх от версии проекта до текущей и делает то,
|
upgrade` идёт по записям снизу вверх от версии проекта до текущей и делает то,
|
||||||
что в них названо.
|
что в них названо. Записи ниже версии 13 зовут этот файл прежним именем,
|
||||||
|
`docs/.pm.json`, — так и было на день записи, и переписывать историю мы не
|
||||||
|
станем; переименование делает запись 13.
|
||||||
|
|
||||||
|
**Каталог задач этим журналом не повышается.** У него своя версия формата и свой
|
||||||
|
журнал — `references/changelog.md` скилла `av-dev-tasks:tasks`. Записи 8, 11 и 12
|
||||||
|
трогали его в те времена, когда своего числа у него не было; впредь запись канона
|
||||||
|
вправе позвать соседа, но не двигать его версию.
|
||||||
|
|
||||||
Правило записи: **что добавилось, что переехало, что удалено, что сделать
|
Правило записи: **что добавилось, что переехало, что удалено, что сделать
|
||||||
проекту**. Без последнего пункта запись бесполезна — по ней и работает
|
проекту**. Без последнего пункта запись бесполезна — по ней и работает
|
||||||
@@ -13,6 +20,51 @@ upgrade` идёт по записям снизу вверх от версии п
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Версия 13 — 2026-08-11
|
||||||
|
|
||||||
|
Служебный файл канона переименован: `docs/.pm.json` → `docs/.docs.json`. Имя
|
||||||
|
досталось от плагина `av-dev-pm`, который распался на четыре и которого больше
|
||||||
|
нет: файл пережил владельца и указывал в пустоту. Правило простое и теперь
|
||||||
|
соблюдается всеми тремя: **имя служебного файла — имя плагина, который его
|
||||||
|
завёл**, `.docs.json` — канон, `.tasks.json` — задачи, `openspec/config.yaml` —
|
||||||
|
конвейер.
|
||||||
|
|
||||||
|
**Что изменилось.** `docs.py` читает только новое имя. Прежнее он не читает
|
||||||
|
намеренно: два дома для одной версии канона расходятся молча, а тут расхождение
|
||||||
|
стоило бы дорого — по этому числу `upgrade` решает, какие записи применять.
|
||||||
|
Файл под старым именем `check` узнаёт и называет отдельной строкой с готовой
|
||||||
|
командой, а не жалуется на пропажу.
|
||||||
|
|
||||||
|
**Что появилось у соседа.** У каталога задач теперь есть **своя версия
|
||||||
|
формата** — ключ `tasks` в `<каталог задач>/.tasks.json`, — и свой журнал версий
|
||||||
|
в скилле `av-dev-tasks:tasks`. До сих пор её не было вовсе: формат задач менялся
|
||||||
|
записями этого журнала (8, 11, 12), хотя каталог принадлежит другому плагину и
|
||||||
|
ставится без канона документов. Канон это число не двигает.
|
||||||
|
|
||||||
|
**Что сделать проекту.**
|
||||||
|
|
||||||
|
1. `git mv docs/.pm.json docs/.docs.json` — одним коммитом с шагом 2. Содержимое
|
||||||
|
не меняется: ключи те же.
|
||||||
|
2. **Поправить упоминания прежнего имени** в своих файлах — `CLAUDE.md`, гейт,
|
||||||
|
`README.md`, `docs/**`. Битой ссылкой это чаще всего не выглядит (файл
|
||||||
|
служебный, на него ссылаются прозой), поэтому `docs.py check` таких упоминаний
|
||||||
|
не ловит: ищи `grep -rn '\.pm\.json'` по репозиторию.
|
||||||
|
3. **Объявить версию формата задач**, если каталог задач в проекте есть:
|
||||||
|
`<каталог задач>/.tasks.json` с ключом `"tasks": <версия>`. Файла нет вовсе —
|
||||||
|
заведи, он теперь обязателен: версия не настройка, от которой можно
|
||||||
|
отказаться. Какое число ставить и что сделать перед этим, говорит журнал
|
||||||
|
владельца — **позови скилл `av-dev-tasks:tasks`**, здесь этих шагов нет
|
||||||
|
намеренно: второй перечень чужих шагов разошёлся бы с первым.
|
||||||
|
4. Гейт не меняется: шаги те же, версию задач сторожит `tasks.py check`, который
|
||||||
|
в нём уже стоит.
|
||||||
|
5. `docs/.docs.json`: `"canon": 13`.
|
||||||
|
|
||||||
|
**Чего делать не надо.** Ключи в файле не трогаются, документы не переезжают,
|
||||||
|
записи задач не меняются: версия 13 — про имена служебных файлов и про то, кто
|
||||||
|
чью версию двигает.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Версия 12 — 2026-08-09
|
## Версия 12 — 2026-08-09
|
||||||
|
|
||||||
Спринты отменены. Работа идёт задача за задачей, и замороженный набор перестал
|
Спринты отменены. Работа идёт задача за задачей, и замороженный набор перестал
|
||||||
|
|||||||
@@ -117,7 +117,7 @@
|
|||||||
со строкой «запись лежит сжатой и распаковывается целиком».
|
со строкой «запись лежит сжатой и распаковывается целиком».
|
||||||
```
|
```
|
||||||
|
|
||||||
Нет БД — файла нет, и в `docs/.pm.json` нет ключа `migrations`.
|
Нет БД — файла нет, и в `docs/.docs.json` нет ключа `migrations`.
|
||||||
|
|
||||||
## `docs/security.md`
|
## `docs/security.md`
|
||||||
|
|
||||||
@@ -438,7 +438,7 @@ severity стоит здесь, а не выводится каждым прох
|
|||||||
говорит (единственный дом, а не форма) — [canon.md](canon.md), раздел
|
говорит (единственный дом, а не форма) — [canon.md](canon.md), раздел
|
||||||
`openspec/config.yaml`.
|
`openspec/config.yaml`.
|
||||||
|
|
||||||
## `docs/.pm.json`
|
## `docs/.docs.json`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -452,5 +452,10 @@ severity стоит здесь, а не выводится каждым прох
|
|||||||
плейсхолдер ломает разбор JSON громко, а отставшее число дало бы дрейф молча.
|
плейсхолдер ломает разбор JSON громко, а отставшее число дало бы дрейф молча.
|
||||||
|
|
||||||
Плюс `"migrations": "<путь>"`, если есть БД. Ключа `"tasks"` здесь **нет**:
|
Плюс `"migrations": "<путь>"`, если есть БД. Ключа `"tasks"` здесь **нет**:
|
||||||
настройки каталога задач переехали в свой файл `<каталог задач>/.tasks.json`,
|
настройки каталога задач и версия их формата переехали в свой файл `<каталог
|
||||||
потому что ведёт их другой плагин. Состав ключей — [canon.md](canon.md).
|
задач>/.tasks.json`, потому что ведёт их другой плагин. Состав ключей —
|
||||||
|
[canon.md](canon.md).
|
||||||
|
|
||||||
|
Имя файла — по плагину-владельцу, `av-dev-docs`. До версии 13 он звался
|
||||||
|
`.pm.json`, по распавшемуся `av-dev-pm`; проект с прежним именем `docs.py check`
|
||||||
|
называет отдельной строкой и зовёт переименовать.
|
||||||
|
|||||||
@@ -25,10 +25,19 @@ from dataclasses import dataclass, field
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
|
|
||||||
CANON_VERSION = 12
|
CANON_VERSION = 13
|
||||||
|
|
||||||
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||||
|
|
||||||
|
# Дом версии канона и путей, нужных проверкам. Имя — от плагина, который файл
|
||||||
|
# завёл: настройки канона документов ведёт `av-dev-docs`, и файл называется по
|
||||||
|
# нему. Прежнее имя досталось от `av-dev-pm` — плагина, который распался на
|
||||||
|
# четыре и которого больше нет; читать его скрипт не умеет намеренно, потому что
|
||||||
|
# два дома для версии канона расходятся молча, а переименование стоит одну
|
||||||
|
# команду и названо записью 13 журнала.
|
||||||
|
CONFIG = "docs/.docs.json"
|
||||||
|
LEGACY_CONFIG = "docs/.pm.json"
|
||||||
|
|
||||||
# --- Раскладка канона -------------------------------------------------------
|
# --- Раскладка канона -------------------------------------------------------
|
||||||
|
|
||||||
# Документ канона: имя → (категория, на какой вопрос отвечает).
|
# Документ канона: имя → (категория, на какой вопрос отвечает).
|
||||||
@@ -59,7 +68,7 @@ DOCS = {
|
|||||||
"review": ("процессный", "настройка конвейера + журнал дефектов"),
|
"review": ("процессный", "настройка конвейера + журнал дефектов"),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Документ, обязательный только при условии: имя → (ключ .pm.json, категория,
|
# Документ, обязательный только при условии: имя → (ключ .docs.json, категория,
|
||||||
# пояснение).
|
# пояснение).
|
||||||
CONDITIONAL_DOCS = {
|
CONDITIONAL_DOCS = {
|
||||||
"database": ("migrations", "источник", "схема хранилища и настройки"),
|
"database": ("migrations", "источник", "схема хранилища и настройки"),
|
||||||
@@ -68,7 +77,7 @@ CONDITIONAL_DOCS = {
|
|||||||
# Обязательные файлы вне раскладки docs/.
|
# Обязательные файлы вне раскладки docs/.
|
||||||
REQUIRED = {
|
REQUIRED = {
|
||||||
"CLAUDE.md": "памятка агенту: инварианты с severity, команды, семантика гейта",
|
"CLAUDE.md": "памятка агенту: инварианты с severity, команды, семантика гейта",
|
||||||
"docs/.pm.json": "версия канона и пути, нужные проверкам",
|
CONFIG: "версия канона и пути, нужные проверкам",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Файлы, которые документ-каталог обязан держать сверх README.md.
|
# Файлы, которые документ-каталог обязан держать сверх README.md.
|
||||||
@@ -77,15 +86,19 @@ DOC_EXTRA = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Служебное в docs/ и каталог задач, оставшийся там от прежней раскладки. Формы
|
# Служебное в docs/ и каталог задач, оставшийся там от прежней раскладки. Формы
|
||||||
# у них скрипт не проверяет, и по разным причинам: `.pm.json` не markdown, а
|
# у них скрипт не проверяет, и по разным причинам: `.docs.json` не markdown, а
|
||||||
# задачи **принадлежат другому плагину** — `av-dev-tasks`, со своим скриптом,
|
# задачи **принадлежат другому плагину** — `av-dev-tasks`, со своим скриптом,
|
||||||
# своим конфигом и своей версией формата.
|
# своим конфигом и своей версией формата (её сторожит `tasks.py check`).
|
||||||
#
|
#
|
||||||
# Дом задач с версии 11 — `tasks/` в корне репозитория, то есть вне `docs/`
|
# Дом задач с версии 11 — `tasks/` в корне репозитория, то есть вне `docs/`
|
||||||
# вовсе. `docs/tasks/` здесь терпится потому, что непереехавший проект не должен
|
# вовсе. `docs/tasks/` здесь терпится потому, что непереехавший проект не должен
|
||||||
# получать «файл вне канона» вдобавок к записи журнала, которая и так велит ему
|
# получать «файл вне канона» вдобавок к записи журнала, которая и так велит ему
|
||||||
# переехать. Внутрь скрипт не смотрит ни в том, ни в другом случае.
|
# переехать. Внутрь скрипт не смотрит ни в том, ни в другом случае.
|
||||||
NOT_DOCS = {".pm.json", "tasks"}
|
#
|
||||||
|
# Прежнее имя конфига терпится ровно за тем же: про переименование проект
|
||||||
|
# слышит одну строку — от `check_required`, — а не две, из которых вторая ещё и
|
||||||
|
# зовёт файл лишним.
|
||||||
|
NOT_DOCS = {".docs.json", ".pm.json", "tasks"}
|
||||||
|
|
||||||
# Слоты, которых в каноне нет, — с адресом, куда уезжает содержимое. Имена,
|
# Слоты, которых в каноне нет, — с адресом, куда уезжает содержимое. Имена,
|
||||||
# совпадающие с темой, отсюда убраны намеренно: `docs/conventions.md` и
|
# совпадающие с темой, отсюда убраны намеренно: `docs/conventions.md` и
|
||||||
@@ -251,15 +264,15 @@ def fail(code: int, msg: str) -> NoReturn:
|
|||||||
|
|
||||||
|
|
||||||
def read_config(root: Path, rep: Report) -> dict:
|
def read_config(root: Path, rep: Report) -> dict:
|
||||||
path = root / "docs" / ".pm.json"
|
path = root / CONFIG
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return {}
|
return {}
|
||||||
try:
|
try:
|
||||||
data = json.loads(path.read_text(encoding="utf-8"))
|
data = json.loads(path.read_text(encoding="utf-8"))
|
||||||
except json.JSONDecodeError as exc:
|
except json.JSONDecodeError as exc:
|
||||||
fail(ENV, f"docs/.pm.json не разбирается: {exc}")
|
fail(ENV, f"{CONFIG} не разбирается: {exc}")
|
||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
fail(ENV, "docs/.pm.json должен быть объектом")
|
fail(ENV, f"{CONFIG} должен быть объектом")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
@@ -267,14 +280,14 @@ def read_config(root: Path, rep: Report) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def check_version(root: Path, cfg: dict, rep: Report) -> None:
|
def check_version(root: Path, cfg: dict, rep: Report) -> None:
|
||||||
if not (root / "docs" / ".pm.json").exists():
|
if not (root / CONFIG).exists():
|
||||||
return # об отсутствии файла скажет check_required, второй раз не нужно
|
return # об отсутствии файла скажет check_required, второй раз не нужно
|
||||||
if "canon" not in cfg:
|
if "canon" not in cfg:
|
||||||
rep.error("в docs/.pm.json нет ключа canon — версия канона не объявлена")
|
rep.error(f"в {CONFIG} нет ключа canon — версия канона не объявлена")
|
||||||
return
|
return
|
||||||
got = cfg["canon"]
|
got = cfg["canon"]
|
||||||
if not isinstance(got, int):
|
if not isinstance(got, int):
|
||||||
rep.error(f"canon в docs/.pm.json должен быть целым числом, а не {got!r}")
|
rep.error(f"canon в {CONFIG} должен быть целым числом, а не {got!r}")
|
||||||
return
|
return
|
||||||
if got < CANON_VERSION:
|
if got < CANON_VERSION:
|
||||||
rep.error(
|
rep.error(
|
||||||
@@ -316,8 +329,21 @@ def doc_home(root: Path, name: str) -> tuple[Path | None, str | None]:
|
|||||||
|
|
||||||
def check_required(root: Path, cfg: dict, rep: Report) -> None:
|
def check_required(root: Path, cfg: dict, rep: Report) -> None:
|
||||||
for rel, what in REQUIRED.items():
|
for rel, what in REQUIRED.items():
|
||||||
if not (root / rel).exists():
|
if (root / rel).exists():
|
||||||
rep.error(f"нет {rel} — {what}")
|
continue
|
||||||
|
# Файл под прежним именем — это не «нет файла», а незаконченный переезд,
|
||||||
|
# и чинится он одной командой. Без этой ветки проект услышал бы «нет
|
||||||
|
# версии канона» и пошёл заводить второй файл рядом с первым.
|
||||||
|
if rel == CONFIG and (root / LEGACY_CONFIG).exists():
|
||||||
|
rep.error(
|
||||||
|
f"нет {rel} — {what}. Настройки лежат под прежним именем"
|
||||||
|
f" {LEGACY_CONFIG} (от плагина av-dev-pm, которого больше нет):"
|
||||||
|
f" `git mv {LEGACY_CONFIG} {rel}` — журнал канона, версия 13."
|
||||||
|
f" Прежнее имя не читается, поэтому в этом прогоне всё"
|
||||||
|
f" остальное проверено так, будто настроек нет вовсе"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
rep.error(f"нет {rel} — {what}")
|
||||||
|
|
||||||
for name, (kind, what) in DOCS.items():
|
for name, (kind, what) in DOCS.items():
|
||||||
home, complaint = doc_home(root, name)
|
home, complaint = doc_home(root, name)
|
||||||
@@ -342,10 +368,10 @@ def check_required(root: Path, cfg: dict, rep: Report) -> None:
|
|||||||
rep.error(
|
rep.error(
|
||||||
f"нет документа {name} (docs/{name}.md или docs/{name}/),"
|
f"нет документа {name} (docs/{name}.md или docs/{name}/),"
|
||||||
f" категория «{kind}» — {what}"
|
f" категория «{kind}» — {what}"
|
||||||
f" (обязателен: в .pm.json объявлен {key})"
|
f" (обязателен: в .docs.json объявлен {key})"
|
||||||
)
|
)
|
||||||
elif key not in cfg and home is None:
|
elif key not in cfg and home is None:
|
||||||
rep.skip(f"{name} — в .pm.json нет ключа {key}, проверка неприменима")
|
rep.skip(f"{name} — в .docs.json нет ключа {key}, проверка неприменима")
|
||||||
|
|
||||||
|
|
||||||
def check_stray(root: Path, rep: Report) -> None:
|
def check_stray(root: Path, rep: Report) -> None:
|
||||||
@@ -527,7 +553,7 @@ def changed_files(root: Path, base: str, rep: Report) -> list[str] | None:
|
|||||||
def check_migrations(root: Path, cfg: dict, base: str | None, rep: Report) -> None:
|
def check_migrations(root: Path, cfg: dict, base: str | None, rep: Report) -> None:
|
||||||
migrations = cfg.get("migrations")
|
migrations = cfg.get("migrations")
|
||||||
if not migrations:
|
if not migrations:
|
||||||
rep.skip("в .pm.json нет ключа migrations — сверка со схемой неприменима")
|
rep.skip("в .docs.json нет ключа migrations — сверка со схемой неприменима")
|
||||||
return
|
return
|
||||||
if not base:
|
if not base:
|
||||||
rep.skip("база диффа не названа (--base) — сверка миграций со схемой не гонялась")
|
rep.skip("база диффа не названа (--base) — сверка миграций со схемой не гонялась")
|
||||||
|
|||||||
@@ -159,8 +159,10 @@ description: Вести содержимое документов канона
|
|||||||
|
|
||||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||||
|
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||||
|
|
||||||
<!-- /копия: граница-плагинов -->
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,10 @@ check` и его скрипт; здесь начинается там, где к
|
|||||||
|
|
||||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||||
|
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||||
|
|
||||||
<!-- /копия: граница-плагинов -->
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ description: "Завести новый проект — сессия вопро
|
|||||||
| `passport.md` | `architecture.md` |
|
| `passport.md` | `architecture.md` |
|
||||||
| `CLAUDE.md` | `database.md` |
|
| `CLAUDE.md` | `database.md` |
|
||||||
| `security.md` | `conventions/` |
|
| `security.md` | `conventions/` |
|
||||||
| `docs/.pm.json` | `research/`, `adr/` |
|
| `docs/.docs.json` | `research/`, `adr/` |
|
||||||
| | `review.md` — журнал пуст, настройка появится с первым ревью |
|
| | `review.md` — журнал пуст, настройка появится с первым ревью |
|
||||||
|
|
||||||
Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет,
|
Честная строка информативна, а не «TBD»: «архитектуры пока нет: кода нет,
|
||||||
@@ -97,8 +97,10 @@ description: "Завести новый проект — сессия вопро
|
|||||||
|
|
||||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||||
|
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||||
|
|
||||||
<!-- /копия: граница-плагинов -->
|
<!-- /копия: граница-плагинов -->
|
||||||
|
|
||||||
@@ -117,7 +119,8 @@ description: "Завести новый проект — сессия вопро
|
|||||||
|
|
||||||
**Вызов не разрешился** — проект без конвейера живёт без OpenSpec законно:
|
**Вызов не разрешился** — проект без конвейера живёт без OpenSpec законно:
|
||||||
строка доклада, и дальше; `docs.py check` о каталоге тоже промолчит.
|
строка доклада, и дальше; `docs.py check` о каталоге тоже промолчит.
|
||||||
4. Заведи `docs/.pm.json` с текущей версией канона.
|
4. Заведи `docs/.docs.json` с текущей версией канона — число берётся из
|
||||||
|
`docs.py version`, а не из памяти.
|
||||||
5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и
|
5. Напиши заполняемые документы. **Бриф переезжает в `passport.md`** и
|
||||||
отдельным файлом не остаётся: два дома для одного замысла разойдутся на
|
отдельным файлом не остаётся: два дома для одного замысла разойдутся на
|
||||||
первом же уточнении.
|
первом же уточнении.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: tasks
|
name: tasks
|
||||||
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Расстановка приоритетов и разбор накопившегося — скилл groom. Не реализует задачи — этим занимается скилл решения задачи.
|
description: Ведение задач и целей как каталога markdown-файлов (одна запись = один файл в items/ + строка в одном из индексов). У каждой записи есть тип (goal, feature, fix, chore, research), и тип решает, каких разделов она требует и что с ней можно делать. Заведение записи из диалога, разбор находок аудита/ревью, декомпозиция на независимо полезные части, штурм сырья, гигиена полей и проверка согласованности индексов. Использовать, когда просят добавить задачу/идею/цель, превратить находки ревью в задачи, разбить задачу, проработать идею, поправить формат или проверить беклог. Он же повышает каталог до текущей версии формата по своему журналу версий, когда tasks.py check говорит, что каталог отстал. Расстановка приоритетов и разбор накопившегося — скилл groom. Не реализует задачи — этим занимается скилл решения задачи.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Задачи
|
# Задачи
|
||||||
@@ -399,7 +399,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
| 0 | сошлось / сделано | дальше по сценарию |
|
| 0 | сошлось / сделано | дальше по сценарию |
|
||||||
| 1 | **только `check`:** найден дрейф индексов и файлов | `check --fix`, остаток разобрать |
|
| 1 | **только `check`:** найден дрейф индексов и файлов | `check --fix`, остаток разобрать |
|
||||||
| 2 | ошибка употребления: аргументы или нарушенное правило | читать сообщение, это отказ по существу |
|
| 2 | ошибка употребления: аргументы или нарушенное правило | читать сообщение, это отказ по существу |
|
||||||
| 3 | окружение: каталог не найден, конфиг битый или мимо диска | чинится путём или `docs/.pm.json`, повтор не поможет |
|
| 3 | окружение: каталог не найден, конфиг битый или мимо диска | чинится путём или `<каталог задач>/.tasks.json`, повтор не поможет |
|
||||||
| 4 | внутренний сбой | дефект скрипта, доложить |
|
| 4 | внутренний сбой | дефект скрипта, доложить |
|
||||||
|
|
||||||
Различать 1 и 3 обязательно: «дрейф в беклоге» — рабочая ситуация, «каталога
|
Различать 1 и 3 обязательно: «дрейф в беклоге» — рабочая ситуация, «каталога
|
||||||
@@ -486,6 +486,39 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
[fix](references/task-fix.md) · [chore](references/task-chore.md) ·
|
[fix](references/task-fix.md) · [chore](references/task-chore.md) ·
|
||||||
[research](references/task-research.md).
|
[research](references/task-research.md).
|
||||||
|
|
||||||
|
## Версия формата
|
||||||
|
|
||||||
|
Формат каталога задач меняется, и проект должен знать, к какой его версии
|
||||||
|
приведён. Число живёт ключом `tasks` в `<каталог задач>/.tasks.json`, журнал
|
||||||
|
версий — [references/changelog.md](references/changelog.md), сверяет их
|
||||||
|
`tasks.py check`: отстало — строка расхождения, ушло вперёд — устарел плагин.
|
||||||
|
|
||||||
|
**Версия своя, а не канона документов.** Плагин ставится в одиночку: проект,
|
||||||
|
взявший учёт работ без `av-dev-docs`, каталога `docs/` не имеет вовсе, а значит
|
||||||
|
не имеет и версии канона — сверять было бы не с чем. Обратной совместимости у
|
||||||
|
формата нет: есть «приведён» и «не приведён».
|
||||||
|
|
||||||
|
**`upgrade` — повысить каталог до текущего формата:**
|
||||||
|
|
||||||
|
1. `python3 $tk check --dir D` — первая же строка расхождений называет версию
|
||||||
|
проекта и версию скрипта. Проект новее скрипта — **обнови маркетплейс**, а не
|
||||||
|
проект: это отстал плагин.
|
||||||
|
2. Иди по [журналу](references/changelog.md) снизу вверх от версии проекта до
|
||||||
|
текущей и делай названное в каждой записи. Записи независимы и применяются по
|
||||||
|
порядку.
|
||||||
|
3. Подними `tasks` в `.tasks.json` до текущей — руками, последним шагом. Раньше
|
||||||
|
времени поднятое число объявляет каталог приведённым к формату, шагов
|
||||||
|
которого никто не делал; `check --fix` этого не пишет намеренно.
|
||||||
|
4. `check --dir D` ещё раз — до отсутствия расхождений.
|
||||||
|
|
||||||
|
Записи журнала описывают **что сделать проекту**. Если запись этого не говорит —
|
||||||
|
это дефект журнала, и о нём надо сказать, а не догадываться.
|
||||||
|
|
||||||
|
**Канон документов сюда не вмешивается.** Его журнал двигает своё число в
|
||||||
|
`docs/.docs.json` и вправе сказать «позови этот скилл», но не двигать версию
|
||||||
|
формата задач: две версии, ходящие по одному журналу, разъедутся на первом же
|
||||||
|
проекте, где стоит один плагин без другого.
|
||||||
|
|
||||||
## Сценарии
|
## Сценарии
|
||||||
|
|
||||||
### Завести запись из диалога
|
### Завести запись из диалога
|
||||||
@@ -647,17 +680,19 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
|
|||||||
действительно новый, а перевод чужой раскладки делает `av-dev-docs:canon`.
|
действительно новый, а перевод чужой раскладки делает `av-dev-docs:canon`.
|
||||||
У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и
|
У скрипта поиск вверх по дереву ещё жив — он для непереведённых проектов, и
|
||||||
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
|
полагаться на него скилл не должен: молча найденный чужой каталог это дрейф.
|
||||||
- **Настройки живут в `<каталог задач>/.tasks.json`** — свой файл у своего
|
- **Версия формата и настройки живут в `<каталог задач>/.tasks.json`** — свой
|
||||||
плагина: **имена** файлов и заголовков, и только если они отличаются от
|
файл у своего плагина: ключ `tasks` с версией формата плюс **имена** файлов и
|
||||||
умолчания. Неизвестный ключ — код 3 на любой команде, так что лишнее слово в
|
заголовков, и последние — только если отличаются от умолчания. Неизвестный
|
||||||
этом объекте останавливает работу с задачами целиком.
|
ключ — код 3 на любой команде, так что лишнее слово в этом объекте
|
||||||
|
останавливает работу с задачами целиком.
|
||||||
|
|
||||||
Дом именно свой, а не `docs/.pm.json`, потому что `docs/` принадлежит плагину
|
Дом именно свой, а не `docs/.docs.json`, потому что `docs/` принадлежит
|
||||||
канона: проект, поставивший учёт работ без него, каталога `docs/` не имеет
|
плагину канона: проект, поставивший учёт работ без него, каталога `docs/` не
|
||||||
вовсе. Прежний ключ `tasks` в `docs/.pm.json` читается, **только когда своего
|
имеет вовсе. Прежний ключ `tasks` в `docs/.pm.json` читается, **только когда
|
||||||
файла нет** — для проектов, заведённых до раскола плагинов; скрипт при этом
|
своего файла нет** — для проектов, заведённых до раскола плагинов; скрипт при
|
||||||
говорит замечанием, куда его перенести. Есть оба — побеждает свой, и об этом
|
этом говорит замечанием, куда его перенести. Есть оба — побеждает свой, и об
|
||||||
тоже говорится вслух: молча выбранный из двух конфиг это дрейф.
|
этом тоже говорится вслух: молча выбранный из двух конфиг это дрейф. Версию
|
||||||
|
прежний дом не знает и знать не может — она читается только из своего файла.
|
||||||
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
- **Секции беклога** берутся из заголовков `##` индекса как есть; их количество
|
||||||
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
|
и названия — дело проекта (умолчание `Ядро` / `Инфра`). **В конфиге их нет** —
|
||||||
второй список разошёлся бы с заголовками молча.
|
второй список разошёлся бы с заголовками молча.
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
|
|||||||
всегда `tasks`. Секции беклога (`--sections`) — по умолчанию
|
всегда `tasks`. Секции беклога (`--sections`) — по умолчанию
|
||||||
`Ядро,Инфра`; если у проекта деление другое по существу, оно называется
|
`Ядро,Инфра`; если у проекта деление другое по существу, оно называется
|
||||||
здесь, а не подгоняется под умолчание, и становится **заголовками `##`
|
здесь, а не подгоняется под умолчание, и становится **заголовками `##`
|
||||||
индекса** — их единственным домом. В `docs/.pm.json` секции не пишутся.
|
индекса** — их единственным домом. В `.tasks.json` секции не пишутся: там
|
||||||
|
версия формата и имена частей, а второй список секций разошёлся бы с
|
||||||
|
заголовками молча.
|
||||||
2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два
|
2. **`adopt scan`** по всем источникам разом. Один прогон, одна карта: два
|
||||||
прохода дадут два несогласованных состояния.
|
прохода дадут два несогласованных состояния.
|
||||||
3. **Заполни карту**: `slug` (английский), `section`, `goal` у каждой записи;
|
3. **Заполни карту**: `slug` (английский), `section`, `goal` у каждой записи;
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
# Журнал версий формата задач
|
||||||
|
|
||||||
|
Одна запись на версию. Проект знает свою версию из ключа `tasks` в `<каталог
|
||||||
|
задач>/.tasks.json`; повышение (`upgrade` в [SKILL.md](../SKILL.md), раздел
|
||||||
|
«Версия формата») идёт по записям снизу вверх от версии проекта до текущей и
|
||||||
|
делает то, что в них названо.
|
||||||
|
|
||||||
|
Правило записи: **что добавилось, что переехало, что удалено, что сделать
|
||||||
|
проекту**. Без последнего пункта запись бесполезна — по ней и работает
|
||||||
|
повышение.
|
||||||
|
|
||||||
|
Версия — целое число. Обратной совместимости у формата нет: есть «приведён» и «не
|
||||||
|
приведён».
|
||||||
|
|
||||||
|
**Это журнал формата задач, а не канона документов.** Числа у них разные и
|
||||||
|
двигаются порознь: плагин `av-dev-tasks` ставится в одиночку, и у проекта без
|
||||||
|
`av-dev-docs` версии канона нет вовсе. Журнал канона —
|
||||||
|
`references/changelog.md` скилла `av-dev-docs:canon`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Версия 1 — 2026-08-11
|
||||||
|
|
||||||
|
Первая объявленная версия формата. До неё каталог задач версии не имел вовсе:
|
||||||
|
формат менялся, а сказать, к какому его состоянию приведён конкретный проект,
|
||||||
|
было нечем — `tasks.py` о расхождении молчал, и отставший каталог выглядел
|
||||||
|
здоровым ровно до первой команды, которая об него спотыкалась.
|
||||||
|
|
||||||
|
**Что появилось.** Ключ `tasks` в `<каталог задач>/.tasks.json` — целое число,
|
||||||
|
версия формата. Сам файл стал **обязательным**: до сих пор он заводился только
|
||||||
|
ради имён, отличных от умолчания, и проект с умолчаниями жил без него. Версия —
|
||||||
|
не настройка, от которой можно отказаться, поэтому `init` и `adopt apply` теперь
|
||||||
|
пишут файл всегда, а `check` требует числа и сверяет его со своим.
|
||||||
|
|
||||||
|
**Что версия значит, а что нет.** Она отвечает на один вопрос — «по какой записи
|
||||||
|
журнала повышать каталог». Что записи применены **по существу**, из числа не
|
||||||
|
следует: двигают его руками, и соврать им так же легко, как любой другой
|
||||||
|
строкой. `check --fix` недостающее число не приписывает намеренно — это было бы
|
||||||
|
объявлением каталога приведённым к формату, шагов которого никто не делал.
|
||||||
|
|
||||||
|
**Чего в этой записи нет.** Переезды, случившиеся до появления числа, — каталог
|
||||||
|
из `docs/` в корень (канон 11) и отмена спринтов (канон 12) — задним числом сюда
|
||||||
|
не переписаны. Они уже названы журналом канона, и второй перечень тех же шагов
|
||||||
|
разошёлся бы с первым. Версия 1 — это формат на день её появления, что бы
|
||||||
|
проекту ни пришлось пройти до неё.
|
||||||
|
|
||||||
|
**Что сделать проекту.**
|
||||||
|
|
||||||
|
1. **Догнать формат по журналу канона, если каталог отстал.** Признаки известны
|
||||||
|
поимённо: каталог лежит в `docs/tasks/` (канон 11 велит `git mv docs/tasks
|
||||||
|
tasks` и починку относительных ссылок внутри записей), в нём есть `SPRINT.md`
|
||||||
|
или теги `sprint:<слаг>` (канон 12 велит снести файл, вернуть строки в беклог
|
||||||
|
через `check --fix` и расставить порядок грумингом). Ничего из этого нет —
|
||||||
|
каталог уже в сегодняшнем формате, и шаг пропускается.
|
||||||
|
2. **Завести `<каталог задач>/.tasks.json`**, если его нет. Имена частей в него
|
||||||
|
не переписываются: там только то, что отличается от умолчания.
|
||||||
|
3. **Записать версию**: `"tasks": 1` первым ключом.
|
||||||
|
4. `tasks.py check --dir <каталог задач>` — до отсутствия расхождений.
|
||||||
|
|
||||||
|
**Что при этом не трогается.** Записи в `items/`, индексы и `REJECTED.md` не
|
||||||
|
меняются ни строкой: версия 1 объявляет то, что уже есть, а не переделывает его.
|
||||||
@@ -10,7 +10,8 @@
|
|||||||
Раскладка. Путь каталога — `tasks/` в корне репозитория, жёстко. Каталог
|
Раскладка. Путь каталога — `tasks/` в корне репозитория, жёстко. Каталог
|
||||||
принадлежит этому плагину, а не канону документов: `docs/` ведёт другой плагин, и
|
принадлежит этому плагину, а не канону документов: `docs/` ведёт другой плагин, и
|
||||||
проект, поставивший учёт работ без него, каталога `docs/` не имеет вовсе. Имена
|
проект, поставивший учёт работ без него, каталога `docs/` не имеет вовсе. Имена
|
||||||
внутри настраиваются через `tasks/.tasks.json`.
|
внутри и **версия формата** живут в `tasks/.tasks.json`; журнал версий —
|
||||||
|
references/changelog.md рядом со скриптом.
|
||||||
|
|
||||||
tasks/
|
tasks/
|
||||||
items/ задачи и цели файлами, <slug>.md
|
items/ задачи и цели файлами, <slug>.md
|
||||||
@@ -107,8 +108,26 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
CONFIG_NAME = ".tasks.json" # дом настроек: свой файл в каталоге задач
|
CONFIG_NAME = ".tasks.json" # дом настроек и версии: свой файл в каталоге
|
||||||
PM_CONFIG_REL = "../.pm.json" # прежний дом: docs/.pm.json, ключ "tasks"
|
PM_CONFIG_REL = "../.pm.json" # прежний дом настроек: docs/.pm.json, ключ "tasks"
|
||||||
|
|
||||||
|
# Версия формата задач — **своя, а не канона документов**. Число живёт ключом
|
||||||
|
# `tasks` в `.tasks.json`, журнал версий — references/changelog.md рядом со
|
||||||
|
# скриптом, повышает его операция `upgrade` скилла `av-dev-tasks:tasks`.
|
||||||
|
#
|
||||||
|
# Число именно своё, потому что плагин ставится в одиночку: проект, взявший учёт
|
||||||
|
# работ без канона документов, каталога `docs/` не имеет вовсе, а значит не имеет
|
||||||
|
# и версии канона — сверять было бы не с чем. Копия чужого числа в этом скрипте
|
||||||
|
# была бы вторым домом для одной версии и разъехалась бы молча при обновлении
|
||||||
|
# одного плагина без другого.
|
||||||
|
#
|
||||||
|
# Переезды каталога задач, случившиеся до появления этого числа (в корень —
|
||||||
|
# канон 11, отмена спринтов — канон 12), задним числом сюда не переписаны: они
|
||||||
|
# уже названы журналом канона, и второй перечень тех же шагов разошёлся бы с
|
||||||
|
# первым. Версия 1 — формат на день её появления, что бы проекту ни пришлось
|
||||||
|
# пройти до неё.
|
||||||
|
FORMAT_VERSION = 1
|
||||||
|
VERSION_KEY = "tasks"
|
||||||
|
|
||||||
EXIT_OK = 0
|
EXIT_OK = 0
|
||||||
EXIT_DRIFT = 1
|
EXIT_DRIFT = 1
|
||||||
@@ -433,7 +452,7 @@ class Layout:
|
|||||||
|
|
||||||
|
|
||||||
def load_config(root: Path) -> dict:
|
def load_config(root: Path) -> dict:
|
||||||
"""Настройки каталога задач.
|
"""Настройки каталога задач и версия его формата.
|
||||||
|
|
||||||
Дом — `<каталог задач>/.tasks.json`: **свой файл у своего плагина**. Ключ
|
Дом — `<каталог задач>/.tasks.json`: **свой файл у своего плагина**. Ключ
|
||||||
`tasks` в `docs/.pm.json` читается, пока живы проекты, заведённые до раскола
|
`tasks` в `docs/.pm.json` читается, пока живы проекты, заведённые до раскола
|
||||||
@@ -445,6 +464,10 @@ def load_config(root: Path) -> dict:
|
|||||||
плагину. Проект, поставивший учёт задач без канона документов, каталога
|
плагину. Проект, поставивший учёт задач без канона документов, каталога
|
||||||
`docs/` не имеет вовсе, и дом настроек, лежащий в чужом дереве, был бы домом,
|
`docs/` не имеет вовсе, и дом настроек, лежащий в чужом дереве, был бы домом,
|
||||||
которого у половины проектов нет.
|
которого у половины проектов нет.
|
||||||
|
|
||||||
|
Версия формата (ключ `tasks`) читается **только из своего файла**: прежний
|
||||||
|
дом её не знал и знать не может, и молча выведенная из его отсутствия версия
|
||||||
|
была бы догадкой о том, что чинится одной строкой.
|
||||||
"""
|
"""
|
||||||
path = root / CONFIG_NAME
|
path = root / CONFIG_NAME
|
||||||
pm = (root / PM_CONFIG_REL).resolve()
|
pm = (root / PM_CONFIG_REL).resolve()
|
||||||
@@ -485,7 +508,7 @@ def _read_json(path: Path) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def _validate_config(data: dict, path: Path) -> dict:
|
def _validate_config(data: dict, path: Path) -> dict:
|
||||||
unknown = set(data) - set(DEFAULTS)
|
unknown = set(data) - set(DEFAULTS) - {VERSION_KEY}
|
||||||
# Ключ «plan» был домом оглавления целей до того, как файл стал ROADMAP.md.
|
# Ключ «plan» был домом оглавления целей до того, как файл стал ROADMAP.md.
|
||||||
# Без этой ветки проект со старым конфигом получал бы «неизвестный ключ» и
|
# Без этой ветки проект со старым конфигом получал бы «неизвестный ключ» и
|
||||||
# искал опечатку там, где на самом деле переименование канона.
|
# искал опечатку там, где на самом деле переименование канона.
|
||||||
@@ -495,9 +518,19 @@ def _validate_config(data: dict, path: Path) -> dict:
|
|||||||
f" av-dev-docs:canon (upgrade), а не правь ключ в одиночку:"
|
f" av-dev-docs:canon (upgrade), а не правь ключ в одиночку:"
|
||||||
f" файл и ссылки на него переезжают вместе с ним")
|
f" файл и ссылки на него переезжают вместе с ним")
|
||||||
if unknown:
|
if unknown:
|
||||||
|
known = sorted({*DEFAULTS, VERSION_KEY})
|
||||||
raise Env(f"{path}: неизвестные ключи: {', '.join(sorted(unknown))}"
|
raise Env(f"{path}: неизвестные ключи: {', '.join(sorted(unknown))}"
|
||||||
f" (известны: {', '.join(sorted(DEFAULTS))})")
|
f" (известны: {', '.join(known)})")
|
||||||
|
# Версия — единственный ключ-число: остальные это имена файлов и заголовков.
|
||||||
|
# Битое число тут останавливает работу целиком (код 3), а не идёт дрейфом,
|
||||||
|
# потому что «на какой версии формата каталог» решает, чему верить дальше.
|
||||||
|
got = data.get(VERSION_KEY)
|
||||||
|
if VERSION_KEY in data and (isinstance(got, bool) or not isinstance(got, int)):
|
||||||
|
raise Env(f"{path}: ключ «{VERSION_KEY}» — версия формата задач,"
|
||||||
|
f" ожидалось целое число, а не {got!r}")
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
|
if key == VERSION_KEY:
|
||||||
|
continue
|
||||||
if not isinstance(value, str) or not value.strip():
|
if not isinstance(value, str) or not value.strip():
|
||||||
raise Env(f"{path}: ключ «{key}» — ожидалась непустая строка")
|
raise Env(f"{path}: ключ «{key}» — ожидалась непустая строка")
|
||||||
if key in PATH_KEYS and (value.startswith("/") or ".." in Path(value).parts):
|
if key in PATH_KEYS and (value.startswith("/") or ".." in Path(value).parts):
|
||||||
@@ -536,10 +569,46 @@ def config_problems(lay: Layout) -> list[str]:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def version_problems(lay: Layout) -> list[str]:
|
||||||
|
"""Версия формата задач: объявлена ли и та ли, которую знает скрипт.
|
||||||
|
|
||||||
|
Отвечает на один вопрос — «по какой записи журнала повышать каталог», — и
|
||||||
|
ни на какой другой. Что запись оформлена по правилам своей версии, отсюда не
|
||||||
|
следует: число двигает тот, кто прошёл шаги, и соврать им так же легко, как
|
||||||
|
любой другой строкой. Цена вранья при этом низкая, а польза от вопроса есть
|
||||||
|
ровно там, где формат поменялся, а каталог остался прежним.
|
||||||
|
|
||||||
|
`check --fix` этого не чинит намеренно: приписать недостающее число значило
|
||||||
|
бы объявить каталог приведённым к формату, шагов которого никто не делал.
|
||||||
|
Заводит число `init`, двигает — операция `upgrade` скилла.
|
||||||
|
"""
|
||||||
|
path = lay.root / CONFIG_NAME
|
||||||
|
# Прежний дом (`docs/.pm.json`) версии не знает, поэтому спрашиваем строго
|
||||||
|
# свой файл: «конфиг нашёлся» и «версия объявлена» это разные события.
|
||||||
|
if not path.is_file():
|
||||||
|
return [f"нет {path} — версия формата задач не объявлена."
|
||||||
|
f" Заведи файл с «{VERSION_KEY}»: {FORMAT_VERSION} (журнал версий —"
|
||||||
|
f" references/changelog.md скилла av-dev-tasks:tasks)"]
|
||||||
|
# Что число целое, уже проверил `_validate_config` — иначе сюда не дошли бы
|
||||||
|
# вовсе (код 3). Здесь `isinstance` значит ровно «ключ есть».
|
||||||
|
got = lay.cfg.get(VERSION_KEY)
|
||||||
|
if not isinstance(got, int):
|
||||||
|
return [f"{path}: нет ключа «{VERSION_KEY}» — версия формата не объявлена,"
|
||||||
|
f" текущая {FORMAT_VERSION}"]
|
||||||
|
if got < FORMAT_VERSION:
|
||||||
|
return [f"каталог приведён к формату версии {got}, текущая —"
|
||||||
|
f" {FORMAT_VERSION}: нужно повышение по журналу"
|
||||||
|
f" (скилл av-dev-tasks:tasks, операция upgrade)"]
|
||||||
|
if got > FORMAT_VERSION:
|
||||||
|
return [f"каталог приведён к формату версии {got}, а скрипт знает"
|
||||||
|
f" {FORMAT_VERSION}: устарел плагин, обнови маркетплейс"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def looks_like_tasks(p: Path) -> bool:
|
def looks_like_tasks(p: Path) -> bool:
|
||||||
if (p / CONFIG_NAME).is_file():
|
if (p / CONFIG_NAME).is_file():
|
||||||
return True
|
return True
|
||||||
try: # индекс мог быть переименован через docs/.pm.json
|
try: # индекс мог быть переименован через конфиг
|
||||||
name = load_config(p).get("backlog", DEFAULTS["backlog"])
|
name = load_config(p).get("backlog", DEFAULTS["backlog"])
|
||||||
except Env:
|
except Env:
|
||||||
name = DEFAULTS["backlog"]
|
name = DEFAULTS["backlog"]
|
||||||
@@ -1027,7 +1096,10 @@ def check(lay: Layout, fix: bool = False) -> int:
|
|||||||
entries = {k: v[0] for k, v in idx.items()}
|
entries = {k: v[0] for k, v in idx.items()}
|
||||||
sections = {k: v[1] for k, v in idx.items()}
|
sections = {k: v[1] for k, v in idx.items()}
|
||||||
tasks = tasks_of(lay)
|
tasks = tasks_of(lay)
|
||||||
errors: list[str] = []
|
# Версия формата идёт первой строкой расхождений: остальные находки читаются
|
||||||
|
# иначе, когда каталог отстал от формата, — часть из них тогда не дрейф, а
|
||||||
|
# непройденный шаг журнала.
|
||||||
|
errors: list[str] = version_problems(lay)
|
||||||
notes: list[str] = []
|
notes: list[str] = []
|
||||||
label = {k: lay.name(k) for k in lay.indexes}
|
label = {k: lay.name(k) for k in lay.indexes}
|
||||||
|
|
||||||
@@ -2524,13 +2596,15 @@ def apply_fixes(lay: Layout) -> tuple[list[str], list[str]]:
|
|||||||
def init_files(lay: Layout, sections: list[str], roadmap_sections: list[str],
|
def init_files(lay: Layout, sections: list[str], roadmap_sections: list[str],
|
||||||
cfg: dict) -> dict[Path, str]:
|
cfg: dict) -> dict[Path, str]:
|
||||||
out: dict[Path, str] = {}
|
out: dict[Path, str] = {}
|
||||||
if cfg:
|
# Файл заводится всегда, даже когда все имена умолчательные: в нём живёт
|
||||||
# Пишем всегда в свой `.tasks.json`, даже когда рядом живёт
|
# версия формата, а версия — не настройка, от которой можно отказаться.
|
||||||
# `docs/.pm.json`: дом настроек принадлежит этому плагину, а `docs/` —
|
#
|
||||||
# другому, и его в проекте может не быть. load_config читает свой файл
|
# Пишем всегда в свой `.tasks.json`, даже когда рядом живёт `docs/.pm.json`:
|
||||||
# первым, так что записанное сюда и прочитается отсюда.
|
# дом настроек принадлежит этому плагину, а `docs/` — другому, и его в
|
||||||
out[lay.root / CONFIG_NAME] = json.dumps(cfg, ensure_ascii=False,
|
# проекте может не быть. load_config читает свой файл первым, так что
|
||||||
indent=2) + "\n"
|
# записанное сюда и прочитается отсюда.
|
||||||
|
out[lay.root / CONFIG_NAME] = json.dumps(cfg, ensure_ascii=False,
|
||||||
|
indent=2) + "\n"
|
||||||
out[lay.index("backlog")] = (
|
out[lay.index("backlog")] = (
|
||||||
"# Беклог\n\n"
|
"# Беклог\n\n"
|
||||||
f"Что **можно взять**. Одна задача = один файл `{lay.cfg['items']}/<slug>.md`\n"
|
f"Что **можно взять**. Одна задача = один файл `{lay.cfg['items']}/<slug>.md`\n"
|
||||||
@@ -2589,9 +2663,15 @@ def uniq_sections(raw: str) -> list[str]:
|
|||||||
def cmd_init(root: Path, a: argparse.Namespace) -> int:
|
def cmd_init(root: Path, a: argparse.Namespace) -> int:
|
||||||
if not dir_within_cwd(root):
|
if not dir_within_cwd(root):
|
||||||
raise Usage(f"--dir вне рабочего каталога: {root}")
|
raise Usage(f"--dir вне рабочего каталога: {root}")
|
||||||
cfg = {k: v for k, v in (("items", a.items), ("backlog", a.backlog), ("roadmap", a.roadmap),
|
names = {k: v for k, v in (("items", a.items), ("backlog", a.backlog),
|
||||||
("rejected", a.rejected)) if v}
|
("roadmap", a.roadmap), ("rejected", a.rejected)) if v}
|
||||||
lay = Layout(root, cfg)
|
# Версия формата — первым ключом и всегда: каталог, заведённый сегодня,
|
||||||
|
# приведён к сегодняшнему формату, и объявить это должен тот, кто его завёл.
|
||||||
|
# Имена частей — следом и только те, что названы явно: умолчание, записанное
|
||||||
|
# в файл, стало бы вторым домом для того же имени. В раскладку версия не
|
||||||
|
# идёт — `Layout` про имена, и число среди имён там ничего не значит.
|
||||||
|
cfg = {VERSION_KEY: FORMAT_VERSION, **names}
|
||||||
|
lay = Layout(root, names)
|
||||||
if lay.index("backlog").exists():
|
if lay.index("backlog").exists():
|
||||||
raise Usage(f"{lay.index('backlog')} уже есть — каталог задач заведён")
|
raise Usage(f"{lay.index('backlog')} уже есть — каталог задач заведён")
|
||||||
|
|
||||||
@@ -2615,8 +2695,9 @@ def cmd_init(root: Path, a: argparse.Namespace) -> int:
|
|||||||
print(f"каталог задач заведён: {root}")
|
print(f"каталог задач заведён: {root}")
|
||||||
print(f" секции беклога: {', '.join(sections)};"
|
print(f" секции беклога: {', '.join(sections)};"
|
||||||
f" секции роадмапа канонические: {', '.join(roadmap_sections)}")
|
f" секции роадмапа канонические: {', '.join(roadmap_sections)}")
|
||||||
if cfg:
|
what = ("версия формата и имена частей записаны" if names
|
||||||
print(f" имена частей записаны в {root / CONFIG_NAME}")
|
else "версия формата записана")
|
||||||
|
print(f" {what} в {root / CONFIG_NAME}: формат {FORMAT_VERSION}")
|
||||||
return EXIT_OK
|
return EXIT_OK
|
||||||
|
|
||||||
|
|
||||||
@@ -2974,7 +3055,11 @@ def cmd_adopt_apply(a: argparse.Namespace) -> int:
|
|||||||
|
|
||||||
# --- план записи ---
|
# --- план записи ---
|
||||||
wr = Plan()
|
wr = Plan()
|
||||||
for path, text in init_files(lay, sections, roadmap_sections, {}).items():
|
# Версия та же, что у `init`: каталог выводится из чужой раскладки сегодня и
|
||||||
|
# сегодняшним форматом, сколько бы лет ни было тому, из чего он выведен.
|
||||||
|
# Имён частей здесь нет — адаптация раскладывает всё по умолчаниям.
|
||||||
|
for path, text in init_files(lay, sections, roadmap_sections,
|
||||||
|
{VERSION_KEY: FORMAT_VERSION}).items():
|
||||||
wr.file(path, text)
|
wr.file(path, text)
|
||||||
backlog_lines = init_files(lay, sections, roadmap_sections, {})[lay.index("backlog")].splitlines()
|
backlog_lines = init_files(lay, sections, roadmap_sections, {})[lay.index("backlog")].splitlines()
|
||||||
roadmap_lines = init_files(lay, sections, roadmap_sections, {})[lay.index("roadmap")].splitlines()
|
roadmap_lines = init_files(lay, sections, roadmap_sections, {})[lay.index("roadmap")].splitlines()
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ OWNERS = {
|
|||||||
# Адрес, верный на момент записи, здесь останется навсегда, и это не дрейф.
|
# Адрес, верный на момент записи, здесь останется навсегда, и это не дрейф.
|
||||||
JOURNALS = {
|
JOURNALS = {
|
||||||
"av-dev-docs/skills/canon/references/changelog.md": "журнал версий канона",
|
"av-dev-docs/skills/canon/references/changelog.md": "журнал версий канона",
|
||||||
|
"av-dev-tasks/skills/tasks/references/changelog.md": "журнал версий формата задач",
|
||||||
"DECISIONS.md": "журнал решений",
|
"DECISIONS.md": "журнал решений",
|
||||||
"HISTORY.md": "журнал работ",
|
"HISTORY.md": "журнал работ",
|
||||||
"NOTES.md": "рабочие заметки",
|
"NOTES.md": "рабочие заметки",
|
||||||
@@ -102,7 +103,7 @@ def stem(name: str) -> str:
|
|||||||
"""Имя документа без формы: файл, каталог и `.*` — один и тот же адрес.
|
"""Имя документа без формы: файл, каталог и `.*` — один и тот же адрес.
|
||||||
|
|
||||||
Форму дома канон оставляет проекту: `docs/security.md` и `docs/security/`
|
Форму дома канон оставляет проекту: `docs/security.md` и `docs/security/`
|
||||||
называют одно. Скрытые имена (`.pm.json`) остаются как есть — точка в них
|
называют одно. Скрытые имена (`.docs.json`) остаются как есть — точка в них
|
||||||
не расширение.
|
не расширение.
|
||||||
"""
|
"""
|
||||||
name = name.rstrip(".")
|
name = name.rstrip(".")
|
||||||
@@ -119,7 +120,7 @@ def vocabularies(root: Path) -> tuple[dict[str, set[str]], dict[str, str]]:
|
|||||||
docs_names = {stem(n) for n in docs.DOCS}
|
docs_names = {stem(n) for n in docs.DOCS}
|
||||||
docs_names |= {stem(n) for n in docs.CONDITIONAL_DOCS}
|
docs_names |= {stem(n) for n in docs.CONDITIONAL_DOCS}
|
||||||
docs_names |= {stem(n) for n in docs.NOT_DOCS}
|
docs_names |= {stem(n) for n in docs.NOT_DOCS}
|
||||||
# `docs/.pm.json` объявлен обязательным файлом вне раскладки.
|
# `docs/.docs.json` объявлен обязательным файлом вне раскладки.
|
||||||
docs_names |= {stem(Path(p).name) for p in docs.REQUIRED if p.startswith("docs/")}
|
docs_names |= {stem(Path(p).name) for p in docs.REQUIRED if p.startswith("docs/")}
|
||||||
|
|
||||||
tasks_names = {stem(tasks.DEFAULTS[k]) for k in tasks.PATH_KEYS}
|
tasks_names = {stem(tasks.DEFAULTS[k]) for k in tasks.PATH_KEYS}
|
||||||
@@ -201,7 +202,7 @@ def main() -> int:
|
|||||||
if own_themes:
|
if own_themes:
|
||||||
print(f"Имён вне перечня {len(own_themes)}, и они **не судятся** —"
|
print(f"Имён вне перечня {len(own_themes)}, и они **не судятся** —"
|
||||||
f" список тем открытый: {', '.join(sorted(own_themes))}.")
|
f" список тем открытый: {', '.join(sorted(own_themes))}.")
|
||||||
print(f"Не проверялось: журналы ({len(JOURNALS)} файла — они описывают"
|
print(f"Не проверялось: журналы ({len(JOURNALS)} шт. — они описывают"
|
||||||
f" прошлые состояния), адреса `openspec/*` (раскладка чужого"
|
f" прошлые состояния), адреса `openspec/*` (раскладка чужого"
|
||||||
f" инструмента, у нас владельца нет), упоминания в комментариях"
|
f" инструмента, у нас владельца нет), упоминания в комментариях"
|
||||||
f" скриптов — сверяется только markdown.")
|
f" скриптов — сверяется только markdown.")
|
||||||
|
|||||||
@@ -51,7 +51,9 @@
|
|||||||
|
|
||||||
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
**Присутствие узнаётся вызовом или следом в проекте, но не объявлением.** Перечня
|
||||||
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
установленных плагинов проект не ведёт — он разошёлся бы с действительностью
|
||||||
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.pm.json` —
|
молча. Что сосед здесь работал, видно по заведённому им файлу: `docs/.docs.json` —
|
||||||
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
канон, `<каталог задач>/.tasks.json` — задачи, `openspec/config.yaml` — конвейер.
|
||||||
|
Имя файла — имя плагина, который его завёл, и держит он в том числе версию своего
|
||||||
|
формата: у канона документов и у каталога задач они свои и двигаются порознь.
|
||||||
|
|
||||||
<!-- /дом: граница-плагинов -->
|
<!-- /дом: граница-плагинов -->
|
||||||
|
|||||||
Reference in New Issue
Block a user