Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67cfa45162
|
||
|
|
324fb289ab
|
@@ -1,6 +1,6 @@
|
||||
# Канон документов проекта
|
||||
|
||||
**Версия 1.**
|
||||
**Версия 2.**
|
||||
|
||||
Это **единственный дом определения канона**. Скиллы `init`, `canon` и `docs`
|
||||
читают его, а не пересказывают: три описания одной раскладки разъедутся, и
|
||||
@@ -164,6 +164,12 @@ kebab-case.
|
||||
Записи неизменяемы: передумали — заводится новая, старая получает статус.
|
||||
Активная запись статуса не имеет.
|
||||
|
||||
**Статус живёт полем меты записи**, там же, где дата и источник:
|
||||
`- **Статус:** заменено на ADR-…` либо `- **Статус:** устарело`. Места ему в
|
||||
шаблоне не отводилось, и каждая запись изобретала своё — то абзацем, то
|
||||
заголовком; в таблице `adr/README.md` статус при этом обязан быть, а брать его
|
||||
оттуда, где он у каждого свой, нельзя.
|
||||
|
||||
### `review.md`
|
||||
|
||||
Два раздела с разными сроками жизни.
|
||||
@@ -286,7 +292,7 @@ kebab-case.
|
||||
|
||||
```json
|
||||
{
|
||||
"canon": 1,
|
||||
"canon": 2,
|
||||
"migrations": "internal/store/migrations",
|
||||
"tasks": {
|
||||
"backlog": "INDEX.md"
|
||||
|
||||
@@ -13,6 +13,32 @@ upgrade` идёт по записям снизу вверх от версии п
|
||||
|
||||
---
|
||||
|
||||
## Версия 2 — 2026-08-03
|
||||
|
||||
Шапка записи ADR — мета-блоком общей формы, и у статуса появился объявленный
|
||||
дом. Раскладка не менялась: правка касается одного шаблона.
|
||||
|
||||
**Что добавилось:** поле `- **Статус:**` в шапке `docs/adr/template.md` —
|
||||
`заменено на ADR-…` либо `устарело`, у активной записи поля нет. Правило
|
||||
«старая запись получает статус» было и раньше ([canon.md](canon.md), `adr/`),
|
||||
но места под него шаблон не отводил: каждая запись изобретала своё, а колонка
|
||||
«Статус» таблицы `adr/README.md` брала его оттуда, где он у каждого свой.
|
||||
|
||||
**Что переехало:** поля `Дата` и `Источник` в шаблоне стали жирными
|
||||
(`- **Дата:**`, `- **Источник:**`) — та же форма, что у меты задачи и у записи
|
||||
журнала дефектов: поле на строку, имя жирным.
|
||||
|
||||
**Что удалено:** ничего.
|
||||
|
||||
**Что сделать проекту:**
|
||||
|
||||
1. Привести `docs/adr/template.md` к скелету версии 2
|
||||
([skeletons.md](skeletons.md), раздел `docs/adr/template.md`).
|
||||
2. В существующих записях `docs/adr/ADR-*.md`: жирным поля шапки; если статус
|
||||
записан прозой или заголовком — перенести его полем `- **Статус:**` в шапку
|
||||
и сверить с колонкой «Статус» таблицы в `docs/adr/README.md`.
|
||||
3. `docs/.pm.json`: `"canon": 2`.
|
||||
|
||||
## Версия 1 — 2026-08-03
|
||||
|
||||
Первая версия. Проект любой прежней раскладки приводится к ней скиллом `canon`
|
||||
|
||||
@@ -220,7 +220,8 @@
|
||||
реально принято.
|
||||
- Записи неизменяемы: передумали — новая запись, старой ставится статус.
|
||||
- Активная запись статуса не имеет. Значений два: `заменено на ADR-…` и
|
||||
`устарело`.
|
||||
`устарело`; ставятся полем меты записи — `- **Статус:** …` рядом с датой и
|
||||
источником, а не абзацем в теле.
|
||||
|
||||
## Записи
|
||||
|
||||
@@ -235,8 +236,12 @@
|
||||
```markdown
|
||||
# Краткий заголовок решения
|
||||
|
||||
- Дата: ГГГГ-ММ-ДД
|
||||
- Источник: openspec/changes/archive/<id>/design.md
|
||||
- **Дата:** ГГГГ-ММ-ДД
|
||||
- **Источник:** openspec/changes/archive/<id>/design.md
|
||||
|
||||
Статус ставится тем же полем и только при пересмотре:
|
||||
`- **Статус:** заменено на ADR-…` либо `- **Статус:** устарело`.
|
||||
У активной записи поля нет.
|
||||
|
||||
## Решение
|
||||
|
||||
@@ -377,7 +382,7 @@ severity стоит здесь, а не выводится каждым прох
|
||||
|
||||
```json
|
||||
{
|
||||
"canon": 1
|
||||
"canon": 2
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
CANON_VERSION = 1
|
||||
CANON_VERSION = 2
|
||||
|
||||
OK, DRIFT, USAGE, ENV, INTERNAL = 0, 1, 2, 3, 4
|
||||
|
||||
|
||||
@@ -172,6 +172,12 @@
|
||||
| `SPRINT.md` | какая цель и какой набор под неё | одна: «Набор» |
|
||||
| `REJECTED.md` | что ушло без реализации и почему | — |
|
||||
|
||||
Шапку `SPRINT.md` пишет `sprint start` — **тем же мета-блоком, что у задачи**:
|
||||
поле на строку, `- **Цель:** [Заголовок](items/slug.md)`, `- **Начат:**` датой,
|
||||
`- **Спринт:**` слагом, которым метится урожай. Прежняя форма (три поля одной
|
||||
строкой через `·`) читается по-прежнему и уходит сама: файл переписывается на
|
||||
следующем `sprint start` и очищается на `sprint close`.
|
||||
|
||||
Секции — **единственные заголовки `##` в индексе**: любой другой `##` в
|
||||
преамбуле проверка сочтёт секцией. Внутри секции беклога порядок значения не
|
||||
имеет — порядка в беклоге нет вовсе.
|
||||
|
||||
@@ -161,7 +161,9 @@ SECTION = re.compile(r"^##\s+(.+?)\s*$")
|
||||
TYPE_PREFIX = re.compile(r"^\[(.+?)\]\s*(.*)$")
|
||||
SLUG_RE = re.compile(r"[a-z0-9]+(?:-[a-z0-9]+)*")
|
||||
SLUG = re.compile(SLUG_RE.pattern + r"\.md")
|
||||
GOAL_LINE = re.compile(r"^\*\*(?:Цель|Goal):\*\*\s*\[(.+?)\]\((.+?\.md)\)")
|
||||
GOAL_LINE = re.compile(r"^(?:-\s+)?\*\*(?:Цель|Goal):\*\*\s*\[(.+?)\]\((.+?\.md)\)")
|
||||
# `search`, а не `match`: строка меты идёт пунктом списка, а прежняя форма —
|
||||
# третьим полем строки через `·`. Обе читаются, пишется новая.
|
||||
SPRINT_SLUG_LINE = re.compile(r"\*\*(?:Спринт|Sprint):\*\*\s*`?([a-z0-9][a-z0-9.-]*)`?")
|
||||
DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}")
|
||||
BULLET = re.compile(r"^[-*]\s+(.*)$")
|
||||
@@ -651,10 +653,18 @@ def sprint_slug(lay: Layout) -> str:
|
||||
|
||||
def sprint_header(lay: Layout, goal_slug: str, goal_title: str,
|
||||
date: str, slug: str) -> list[str]:
|
||||
"""Шапка спринта — мета-блок той же формы, что у задачи: поле на строку.
|
||||
|
||||
Прежняя форма (все три поля одной строкой через `·`) читается по-прежнему
|
||||
— обе регулярки её берут, — но чинить её нечем и не нужно: `SPRINT.md`
|
||||
переписывается целиком на `sprint start` и очищается на `sprint close`,
|
||||
так что старая шапка живёт не дольше идущего спринта.
|
||||
"""
|
||||
link = f"{lay.cfg['items']}/{goal_slug}.md"
|
||||
return ["# Спринт", "",
|
||||
f"**Цель:** [{goal_title}]({link}) · **Начат:** {date}"
|
||||
f" · **Спринт:** `{slug}`", "",
|
||||
f"- **Цель:** [{goal_title}]({link})",
|
||||
f"- **Начат:** {date}",
|
||||
f"- **Спринт:** `{slug}`", "",
|
||||
f"Урожай спринта поднимается `tasks.py list --tag {SPRINT_TAG}{slug}`"
|
||||
" — это первая порция переоценки на сессии.", ""]
|
||||
|
||||
@@ -837,7 +847,7 @@ def check(lay: Layout, fix: bool = False) -> int:
|
||||
if not goal_of_sprint and entries["sprint"]:
|
||||
errors.append(f"{label['sprint']}: набор есть, а цель не названа")
|
||||
if goal_of_sprint and not sprint_slug(lay):
|
||||
errors.append(f"{label['sprint']}: у спринта нет слага (**Спринт:** `…`) —"
|
||||
errors.append(f"{label['sprint']}: у спринта нет слага (- **Спринт:** `…`) —"
|
||||
f" урожай не отобрать; перезапусти `sprint start`")
|
||||
|
||||
rejected = lay.index("rejected")
|
||||
|
||||
Reference in New Issue
Block a user