Files
convy/README.md
T
av 92bd1f463d манифесты стали данными, заведён convy sync
- убраны комментарии из suite.toml и .conventions.toml: файл, который
  машина переписывает, комментарий через круг не проносит; объяснения
  ушли в README рядом, который suite init теперь заводит
- удалена текстовая правка манифеста целиком — 520 строк ручного
  лексера TOML вместе со всем классом ошибок порчи данных
- запись идёт из структур энкодером; ключ, которого инструмент не
  знает, запись останавливает, а не теряется молча
- convy sync сверяет манифест и подводит под него раскладку файлов:
  чего не хватает — собирает, что осиротело — удаляет, копию с
  локальной частью не трогает никогда
2026-07-28 09:45:10 +03:00

308 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# convy
CLI для управления конвенциями разработки: ведёт набор конвенций и собирает
копии в проектах.
Модель, которую инструмент реализует, описана не здесь, а в репозитории набора
(`dev-conventions`): `README.md` — устройство набора и копий, `LANGUAGE.md`
форма правила, `GUIDE.md` — правила ведения набора, `TOOL.md` — решения об этом
инструменте. При расхождении истина там.
## Термины
| Уровень | Что это |
|---|---|
| язык | как записывается правило: слова, версия, `READING.md` |
| набор | репозиторий с конвенциями, манифестом `suite.toml` и обвязкой |
| проект | репозиторий-потребитель с манифестом `.conventions.toml` |
| тема | набор правил об одном фокусе разработки; единица подписки |
| слой | один файл темы: базовый, языковой или стековый |
| компонент | адресат сборки в проекте: один язык, один стек, один вид приложения |
| префикс | четыре заглавные латинские буквы, адрес правила: `GTIM-3` |
Тему и ось слоя объявляет шапка файла, а не путь: `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`), сборка обычная:
```
go install git.vakhrushev.me/av/convy@latest
```
или из клона репозитория:
```
go build -o convy .
```
Способ раздачи готовых бинарей пока не выбран — вопрос открыт в `TOOL.md`.
## Команды
```
В наборе:
convy suite init завести набор: директория и манифест
convy suite add завести конвенцию: файл, тема и префикс
convy suite rule дописать правило: следующий номер, блоки по порядку
convy suite retire снять правило, конвенцию или тему — без переиспользования
convy suite list что в наборе и что возьмёт компонент
convy suite check целостность набора: префиксы, темы, оси, ссылки, форма
В проекте:
convy init подключить конвенции: источник и первый компонент
convy add <тема> подписаться и собрать
convy pull пересобрать подписанное, текст и всё
convy sync привести файлы в соответствие манифесту
convy list что подключено и что ещё есть в наборе
convy check проверить форму того, что здесь
```
Контекст определяется по манифесту рядом: `suite.toml` — набор,
`.conventions.toml` — проект. Наугад не делается ничего: команда не того уровня
отказывает и подсказывает нужную.
## Два режима
Команды, которые что-то меняют, работают в двух режимах, и режим читается по
командной строке.
**Без аргументов — диалог.** Каждое поле спрашивается с подсказкой о том, что
туда кладут и почему. Это для человека.
```
$ convy suite add
The focus the rules are about: time, config, db-schema. A topic is the unit of
subscription — a consumer takes it whole. The name never changes and is never reused.
Topic: logging
It goes into the manifest and builds the table of conventions in a consumer's README.
One line about the topic: логирование: уровни, структура записи
...
```
**С флагами — автоматика.** Всё передаётся сразу, вопросов не задаётся, все
недостающие поля называются разом. Это для агентов и скриптов.
```
$ convy suite add --topic logging --about "логирование: уровни" \
--prefix SLOG --title "Логирование"
```
Без терминала пустой вызов отказывает, а не виснет в ожидании ответа, которого
некому дать.
## Проверка набора
```
$ convy suite check
conventions/lang/go/logging.md
error:5 extends points at "arch/time.md" with topic "time", while the file
carries topic "logging": the layers of one topic declare one name [spread]
suite: 13 files, 8 topics, language version 1 (ru)
errors: 1, warnings: 0
```
Строка находки — `уровень:строка что не так [семейство]`. Семейств четыре:
- **manifest** — префиксы, темы, объявленные файлы, незарегистрированные файлы;
- **form** — форма правила: нумерация, блоки, модальные слова, словарь.
Проверяется в любом файле, который язык употребляет;
- **spread** — то, что относится к отъезду к потребителю: тема в шапке, ось,
`extends`, пути канона, самодостаточность нормы. Только в конвенциях;
- **links** — разрешение ссылок вида `PREFIX-N`.
Третья часть списка проверок языка — взаимоисключительность строк таблицы,
покрытие области действия, отвечает ли обоснование на «что сломается» — разбором
текста не даётся и остаётся работой читателя.
Коды возврата: `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` действуют вместе, это разные таблицы одного сервиса. Два языка не
действуют вместе никогда — ради этого компонент и заведён.
## Манифесты — данные, а не текст
Оба манифеста инструмент и читает, и переписывает целиком. Поэтому комментариев
в них нет: файл, который машина переписывает, комментарий через круг не
проносит, а вид, что проносит, стоит этого комментария в день, когда никто не
смотрит. Объяснения живут в соседних файлах, которых ни одна команда не
касается: `convy suite init` заводит рядом `README.md` и пишет их туда.
Ключ, которого инструмент не знает, при записи потерялся бы. Поэтому он не
пишет вовсе:
```
$ convy suite add --topic time --about "время" --prefix TIME --title "Время"
suite.toml holds 1 key the tool does not know (language.descriptoin); a write
goes out of what the tool understands, so the key would be dropped — fix the
spelling first
```
## Манифест — источник истины
`.conventions.toml` правится руками так же законно, как командой. Дальше
раскладку под него подводит `sync`:
```
$ convy sync --dry-run
backend → docs/conventions
+ docs/conventions/errors.md subscribed, and no file
- docs/conventions/logging.md nothing subscribes to "logging"
2 files would change; run without --dry-run to do it
```
Деление с `pull` проходит по тому, о чём команда. `pull`о содержимом:
берёт текст всех подписок заново, и оставленный им дифф и есть смысл запуска.
`sync` — о наборе файлов: чего манифест требует и нет — собирается, что есть и
никому не нужно — удаляется.
Копия с локальной частью не удаляется никогда: ниже маркера лежит
единственное, чего нет больше нигде. Такая копия называется в отчёте, и `sync`
завершается с ошибкой, пока её не убрали руками или не подписались снова.
Перед тем как что-то трогать, `sync` сверяет манифест: подписка на снятую или
несуществующую тему, тема дважды, два языка в одном компоненте, тема без
подходящего слоя, общая директория у двух компонентов. Находки называются
разом, и ничего не пишется.
Копия плоская, файл на тему. Первый слой — сам документ; каждый следующий
становится его разделом, и заголовки внутри опускаются на уровень: слой
реализует и сужает базу, а не стоит рядом с ней. Строка о версии языка
остаётся одна.
```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
```
Язык копии определяется по строке о версии, которую она несёт: манифест рядом
с ней не лежит, и больше сказать некому.
## Ступени и словарь
Слова, которыми записаны модальность и метки, — свойство версии языка и
естественного языка набора, а не самого набора. Инструмент знает их сам, и
`suite.toml` их не дублирует: достаточно `[language] version` и `lang`.
Поэтому ступень называется категорией, а не словом:
```
--modality requirement | prohibition | recommendation | not-recommended | permission
```
`prohibition` в русском наборе превращается в `**НЕ ДОЛЖЕН.**`, в английском —
в `**MUST NOT.**`. Вызывающему не нужно знать, на каком языке записан набор.
По той же причине `convy` умеет написать строку о версии языка, и созданный им
файл проходит `suite check` без единой правки.
## Чего инструмент не делает
- не сливает трёхсторонне и не разрешает конфликты: правка выше маркера
локальной части теряется, и это заявленное поведение;
- не ведёт лок-файл: копии закоммичены, ответ на «что было в прошлый раз» даёт
git;
- не хранит список подписчиков: подписка — свойство проекта;
- не переносит правки из проекта в набор: операция ручная и редкая;
- не перенумеровывает правила: номер — идентификатор, а не позиция;
- не кэширует источник: клон делается заново и удаляется;
- не знает нескольких наборов сразу: `source` в проекте один;
- не хранит комментарии в манифестах: они данные, а объяснения — в соседних
файлах.