каталог задач уехал из docs/ в корень репозитория

Версия 8 отпустила задачи из канона — перестала требовать каталог и перестала в
него смотреть, — но место он занимал всё то же, docs/tasks/. Полдела: каталог,
принадлежащий одному плагину, лежал внутри дерева, которым владеет другой.
Проекту, поставившему учёт работ без канона документов, приходилось заводить
docs/ ради одной вложенной папки.

Дом задач теперь tasks/ в корне. tasks.py ищет его там первым, docs/tasks и
doc/tasks остались в списке поиска для непереехавших проектов, init заводит
только в корне, --target у adopt тоже. Настройки лежат рядом, tasks/.tasks.json —
после версии 8 они уже были в своём файле, теперь и файл вне чужого дерева.

docs.py продолжает терпеть docs/tasks/ в списке нечитаемого: непереехавший проект
не должен получать выдуманную ошибку «файл вне канона» вдобавок к записи журнала,
которая и так велит ему переехать. Адреса упразднённых слотов (plan.md, backlog/)
теперь ведут в tasks/ и называют плагин.

Тридцать три живых упоминания пути разведены по смыслу, а не заменой строки: в
раскладке канона tasks/ вышел из-под docs/ и стоит на своём уровне; у
doc-consistency каталог перестал быть исключением внутри docs/** и стал чужой
территорией, названной в обеих формах; у review-scope и review-pipeline
процессный список поехал вместе с путём. В журнале версий тринадцать упоминаний
оставлены как есть — они описывают прошлое состояние.

Канон повышен до версии 11. В записи названа тихая часть переезда: файл
tasks/items/x.md стал на уровень ближе к корню, и ../../passport.md в теле записи
теперь ../docs/passport.md. Битую относительную ссылку внутри записи tasks.py
check не ловит вовсе — её видит только docs.py и только у документов канона, так
что этот шаг делается руками и тем же коммитом, что git mv.

Фикстура переехала, поиск вверх по дереву находит новый путь, гейт зелёный.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
av
2026-08-09 14:33:33 +03:00
co-authored by Claude Opus 5
parent fdadfb65ac
commit f40e0cd7bb
13 changed files with 91 additions and 53 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ color: green
## Что тебе дают
Список файлов записей (`docs/tasks/items/<slug>.md`) или каталог задач целиком.
Список файлов записей (`tasks/items/<slug>.md`) или каталог задач целиком.
Каталог тебе нужен и сам по себе: задача несёт тег `goal:<слаг>`, и **файл цели
ты открываешь**, иначе седьмое правило не проверить.
+1 -1
View File
@@ -200,7 +200,7 @@ python3 $tk sprint close --dir D # конец спринта; --d
python3 $tk reopen <слаг> --dir D --reason … # приёмка не сошлась после закрытия
```
`D` — каталог задач проекта, по канону всегда `docs/tasks`; `--dir` передаётся
`D` — каталог задач проекта, всегда `tasks/` в корне; `--dir` передаётся
явно каждой командой. Вызов из чужого контекста описан в скилле `tasks`
(«Переносимость»). **Коды выхода** — там же: 1 это дрейф в беклоге, 3 это
«каталога нет», и ветвиться на них надо по-разному.
+9 -8
View File
@@ -60,13 +60,14 @@ description: Ведение задач и целей как каталога mar
## Раскладка
Каталог задач — **`docs/tasks`, жёстко**: это часть канона документов, и
подгоняется под него проект, а не наоборот. Канон ведёт другой плагин
(`av-dev-docs`), и **путь известен скиллу сам** — ссылки в чужое дерево здесь
нет намеренно: скилл работает и там, где того плагина не поставили.
Каталог задач — **`tasks/` в корне репозитория, жёстко.** Он принадлежит этому
плагину, а не канону документов: `docs/` ведёт другой плагин (`av-dev-docs`), и
проект, поставивший учёт работ без него, каталога `docs/` не имеет вовсе. Внутри
`docs/` задачи лежали до версии канона 11; непереехавший проект скрипт
по-прежнему находит, но новый заводит только в корне.
```
docs/tasks/
tasks/
items/ задачи и цели файлами, <slug>.md, слаги английские
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет
BACKLOG.md что можно взять — только задачи, целей здесь нет
@@ -133,7 +134,7 @@ docs/tasks/
**У сделанной задачи записи не остаётся** — файл и строка удаляются (`close
--implemented`). Ей хватает коммита и документации проекта; вторая запись была
бы вторым домом для того же факта. Вопрос «что было в спринте N» отвечается
даром: `SPRINT.md` лежит под git, `git log -p docs/tasks/SPRINT.md` отдаёт историю
даром: `SPRINT.md` лежит под git, `git log -p tasks/SPRINT.md` отдаёт историю
всех наборов без отдельного журнала.
**У достигнутой цели запись остаётся, и это единственное исключение.** Файл
@@ -359,7 +360,7 @@ stateDiagram-v2
## Инструмент (`tasks.py`)
Пусть `tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"`, а `D`
`docs/tasks` от корня проекта. `--dir` стоит в примерах намеренно: вызов из
`tasks` от корня проекта. `--dir` стоит в примерах намеренно: вызов из
подкаталога — обычное дело.
```
@@ -618,7 +619,7 @@ python3 $tk adopt scan --from … | apply --plan … # разовая адап
просто каталог markdown. Текст задач — русский (язык документации проекта);
зашита только латиница слага. OpenSpec ему тоже не нужен.
- **Каталог задач — `docs/tasks`, жёстко**, и `--dir` передаётся явно всегда:
- **Каталог задач — `tasks/` в корне, жёстко**, и `--dir` передаётся явно всегда:
раскладка канона одинакова во всех проектах, и искать больше нечего. Каталога
нет — код 3 и вопрос человеку; `init` заводит его **только** когда проект
действительно новый, а перевод чужой раскладки делает `av-dev-docs:canon`.
@@ -38,7 +38,7 @@
tk="$CLAUDE_PLUGIN_ROOT/skills/tasks/scripts/tasks.py"
python3 $tk adopt scan --from docs/backlog docs/plan.md TODO.md \
--target docs/tasks --out tasks-adopt-plan.json # только чтение
--target tasks --out tasks-adopt-plan.json # только чтение
python3 $tk adopt apply --plan tasks-adopt-plan.json \
--refs docs openspec CLAUDE.md README.md # запись
```
@@ -63,7 +63,7 @@ python3 $tk adopt apply --plan tasks-adopt-plan.json \
## Порядок
1. **Осмотрись.** Где лежат задачи, роадмап, заметки. Каталог задач по канону —
всегда `docs/tasks`. Секции беклога (`--sections`) — по умолчанию
всегда `tasks`. Секции беклога (`--sections`) — по умолчанию
`Ядро,Инфра`; если у проекта деление другое по существу, оно называется
здесь, а не подгоняется под умолчание, и становится **заголовками `##`
индекса** — их единственным домом. В `docs/.pm.json` секции не пишутся.
@@ -106,7 +106,7 @@ take` такую задачу не возьмёт). Закрывается эт
- **Не удаляет источники.** Старый каталог остаётся на месте: сверить и убрать —
дело человека, удалять чужое молча нельзя. В доклад идёт готовая команда.
- **Не переписывает подписи ссылок.** `[docs/backlog](docs/tasks/BACKLOG.md)`
- **Не переписывает подписи ссылок.** `[docs/backlog](tasks/BACKLOG.md)`
цель поправлена, текст остался; это правится глазами, и таких мест немного.
- **Не сочиняет критерии приёмки и не придумывает цели**, которых в материале
нет. Придуманная цель хуже отсутствующей: под неё соберут спринт.
+18 -17
View File
@@ -6,11 +6,12 @@
спринт — замороженный набор задач, обычно под одну цель. Индексов теперь
четыре, и задача живёт ровно в одном из них за раз.
Раскладка. Путь каталога — `docs/tasks`, жёстко: это часть канона документов
av-dev, и подгоняется под него проект. Имена внутри настраиваются через
`docs/.pm.json`, ключ `tasks`.
Раскладка. Путь каталога — `tasks/` в корне репозитория, жёстко. Каталог
принадлежит этому плагину, а не канону документов: `docs/` ведёт другой плагин, и
проект, поставивший учёт работ без него, каталога `docs/` не имеет вовсе. Имена
внутри настраиваются через `tasks/.tasks.json`.
docs/tasks/
tasks/
items/ задачи и цели файлами, <slug>.md
ROADMAP.md состояние проекта: что уже умеет и чего ещё не умеет.
Секции канонические и в этом порядке: Запланировано |
@@ -75,9 +76,9 @@ goal | feature | fix | chore | research, по-английски, как и пр
tasks.py adopt scan --from PATH [PATH …] [--target DIR] [--out PLAN.json]
tasks.py adopt apply --plan PLAN.json [--refs PATH …] [--dry-run]
Каталог задач: `--dir` (обязан быть внутри рабочего каталога) → `docs/tasks`
вверх от текущего каталога. Прежние раскладки (`tasks`, `doc/tasks`) читаются,
пока живы непереехавшие проекты; переводит их скилл av-dev-docs:canon.
Каталог задач: `--dir` (обязан быть внутри рабочего каталога) → `tasks/` вверх
от текущего каталога. Прежние раскладки (`docs/tasks`, `doc/tasks`) читаются,
пока живы непереехавшие проекты; переезд — запись 11 журнала версий канона.
Коды выхода (единый словарь, на нём ветвятся скиллы):
@@ -572,7 +573,7 @@ def resolve_layout(explicit: str | None) -> Layout:
return Layout(root, load_config(root))
here = Path.cwd().resolve()
for base in (here, *here.parents):
for candidate in (base, base / "docs/tasks", base / "tasks", base / "doc/tasks"):
for candidate in (base, base / "tasks", base / "docs/tasks", base / "doc/tasks"):
if looks_like_tasks(candidate):
try:
rel = candidate.relative_to(here)
@@ -581,10 +582,10 @@ def resolve_layout(explicit: str | None) -> Layout:
return Layout(rel if str(rel) != "." else candidate, load_config(candidate))
if (base / ".git").exists():
break # выше корня репозитория не ищем
raise Env("каталог задач не найден: ни --dir, ни docs/tasks вверх от"
f" {here}. По канону путь всегда docs/tasks; чужую раскладку"
" переводит скилл av-dev-docs:canon, новый проект —"
" tasks.py init --dir docs/tasks")
raise Env("каталог задач не найден: ни --dir, ни tasks/ вверх от"
f" {here}. Путь всегда tasks/ в корне репозитория; прежний"
" docs/tasks переезжает по записи 11 журнала версий канона,"
" новый проект — tasks.py init --dir tasks")
# --- Чтение индексов ---
@@ -3090,9 +3091,9 @@ def cmd_adopt_scan(a: argparse.Namespace) -> int:
def requalify_links(text: str, old_dir: Path, new_dir: Path) -> str:
"""Относительные ссылки тела после переезда файла глубже.
`docs/backlog/x.md` знал соседей как `../passport.md`; из
`docs/tasks/items/x.md` тот же файл — уже `../../passport.md`. Молча
съехавшая на уровень ссылка — самый дешёвый способ развалить документацию.
`docs/backlog/x.md` знал соседей как `../passport.md`; из `tasks/items/x.md`
тот же файл — уже `../docs/passport.md`. Молча съехавшая на уровень ссылка —
самый дешёвый способ развалить документацию.
"""
delta = len(new_dir.parts) - len(old_dir.parts)
if delta <= 0:
@@ -3420,7 +3421,7 @@ def main() -> int:
asub = p.add_subparsers(dest="adopt_command", required=True)
s = asub.add_parser("scan", help="только карта: что найдено и как разложилось")
s.add_argument("--from", dest="sources", nargs="+", required=True)
s.add_argument("--target", default="docs/tasks")
s.add_argument("--target", default="tasks")
s.add_argument("--out", default="tasks-adopt-plan.json")
s.add_argument("--sections", default=DEFAULT_SECTIONS)
@@ -3431,7 +3432,7 @@ def main() -> int:
a = ap.parse_args()
if a.command == "init":
return cmd_init(Path(a.dir or "docs/tasks"), a)
return cmd_init(Path(a.dir or "tasks"), a)
if a.command == "adopt":
return cmd_adopt_scan(a) if a.adopt_command == "scan" else cmd_adopt_apply(a)
lay = resolve_layout(a.dir)