проектные команды и ссылки на источник

- заведён internal/source: уровни ссылаются друг на друга путём на диске
  или git-репозиторием, ревизия закрепляется хвостом #ref; клон делается
  заново и удаляется, кэша нет
- добавлены init, add, pull, list, check в проекте — манифест
  .conventions.toml, сборка копий по разу на компонент, маркер локальной
  части, READING.md рядом
- проверки формы развязаны с набором: принимают lang.Vocabulary, а язык
  копии узнаётся по строке о версии — манифеста рядом с ней нет
This commit is contained in:
av
2026-07-27 20:42:18 +03:00
parent 4615de6e86
commit 23d88c4048
27 changed files with 3009 additions and 57 deletions
+114 -3
View File
@@ -12,7 +12,9 @@ CLI для управления конвенциями разработки: в
| Уровень | Что это |
|---|---|
| язык | как записывается правило: слова, версия, `READING.md` |
| набор | репозиторий с конвенциями, манифестом `suite.toml` и обвязкой |
| проект | репозиторий-потребитель с манифестом `.conventions.toml` |
| тема | набор правил об одном фокусе разработки; единица подписки |
| слой | один файл темы: базовый, языковой или стековый |
| компонент | адресат сборки в проекте: один язык, один стек, один вид приложения |
@@ -21,6 +23,37 @@ CLI для управления конвенциями разработки: в
Тему и ось слоя объявляет шапка файла, а не путь: `topic:`, `lang:`, `stack:`.
Слой без ключей оси — базовый, он попадает в копию всегда.
## Три уровня и ссылки между ними
Уровни стоят стопкой, и каждый нижний называет верхний ссылкой:
```
язык → слова, версия, короткое описание для читателя
набор → ссылается на язык: [language] version, lang, source
проект → ссылается на набор: source в .conventions.toml
```
Каждый уровень — набор файлов, а где эти файлы лежат, решает не модель, а
ссылка. Реализовано два транспорта:
| Ссылка | Что это |
|---|---|
| `../dev-conventions`, `/srv/conventions` | директория на диске, как она лежит |
| `https://git.example.org/av/conventions.git` | git-репозиторий, клонируется |
| `file:///srv/conventions#v2` | тот же репозиторий, но в закоммиченном виде |
Относительный путь считается от манифеста, который ссылку несёт. Хвост
`#ветка`, `#тег` или `#коммит` закрепляет ревизию и осмыслен только у git.
`ssh://` и `git@host:path` работают тем же клонированием, но проверены хуже.
Клон делается заново на каждый вызов и удаляется. Кэш сэкономил бы второй
клон и вернул бы вопрос, что в нём протухло, — а на «что было в прошлый раз»
отвечает git в репозитории-потребителе.
Ключ `[language] source` в наборе пока обычно пуст: описание языка живёт в
самом наборе. Когда спецификация уедет в свой репозиторий, тот же ключ её
назовёт, и больше ничего не изменится.
## Установка
Внешняя зависимость одна (`BurntSushi/toml`), сборка обычная:
@@ -48,10 +81,11 @@ go build -o convy .
convy suite list что в наборе и что возьмёт компонент
convy suite check целостность набора: префиксы, темы, оси, ссылки, форма
В проекте (пока не реализовано):
В проекте:
convy init подключить конвенции: источник и первый компонент
convy add <тема> подписаться и собрать
convy pull пересобрать подписанное
convy list что подключено и что доступно
convy list что подключено и что ещё есть в наборе
convy check проверить форму того, что здесь
```
@@ -118,6 +152,81 @@ errors: 1, warnings: 0
Коды возврата: `0` — чисто, `1` — есть ошибки, `2` — команда набрана неверно или
не в том контексте.
## Подключение в проект
```
$ convy init --source ../dev-conventions --component backend \
--dir backend/docs/conventions --lang go --stack sqlite
$ convy add time
$ convy add logging --for backend
```
`init` проверяет источник до того, как писать манифест: набор, до которого
никто не доберётся, проходит любую проверку и никому не помогает. Получается
`.conventions.toml`:
```toml
source = "../dev-conventions"
[components.backend]
dir = "backend/docs/conventions"
lang = ["go"]
stack = ["sqlite"]
topics = ["logging", "time"]
```
Компонент пишется всегда, даже когда он один; при нескольких команда без
`--for` не угадывает, а перечисляет имена. `stack` — список: `sqlite` и
`postgres` действуют вместе, это разные таблицы одного сервиса. Два языка не
действуют вместе никогда — ради этого компонент и заведён.
Копия плоская, файл на тему. Первый слой — сам документ; каждый следующий
становится его разделом, и заголовки внутри опускаются на уровень: слой
реализует и сужает базу, а не стоит рядом с ней. Строка о версии языка
остаётся одна.
```markdown
---
origin: time
---
# Время
...
### TIME-1. Единый формат — RFC 3339, UTC
...
## Время: реализация на Go
...
#### GTIM-1. «Сейчас» берётся у слоя хранилища
...
<!-- conv:local -->
```
Всё ниже `<!-- conv:local -->` принадлежит репозиторию и переживает `pull`;
всё выше перезаписывается. Рядом с копиями кладётся `READING.md` — он
приезжает с уровня языка. `README.md` в той же директории принадлежит проекту
и не трогается, а файл, у которого убрали `origin:`, перестаёт быть копией:
`pull` его не перезапишет и скажет почему.
## Проверка проекта
`convy check` до набора не дотягивается и сети не требует: форма правила одна
и та же, локальные правила проекта на `X`-префиксах записаны по ней же.
Проверяются шапка `origin:`, маркер локальной части, нумерация по каждому
префиксу, блоки правила, словарь — и то, чего в наборе не бывает:
```
$ convy check
docs/conventions/time.md
error:22 rule XTIM-1 is a rule of the repository standing above the
marker: a reassembly would wipe it [spread]
project: 2 files in 1 component
errors: 1, warnings: 0
```
Язык копии определяется по строке о версии, которую она несёт: манифест рядом
с ней не лежит, и больше сказать некому.
## Ступени и словарь
Слова, которыми записаны модальность и метки, — свойство версии языка и
@@ -144,4 +253,6 @@ errors: 1, warnings: 0
git;
- не хранит список подписчиков: подписка — свойство проекта;
- не переносит правки из проекта в набор: операция ручная и редкая;
- не перенумеровывает правила: номер — идентификатор, а не позиция.
- не перенумеровывает правила: номер — идентификатор, а не позиция;
- не кэширует источник: клон делается заново и удаляется;
- не знает нескольких наборов сразу: `source` в проекте один.