Перейти к содержимому

21. CLI

CLI archlang покрывает всю поверхность инструментария: проверку и форматирование файлов, запуск проверок governance и CI-гейтов, запись полей модели, экспорт разрешённой модели, headless-рендер диаграмм, обслуживание HTTP API, запуск языкового сервера, проверку привязок-свидетельств по репозиторию кода и проверку соответствия (conformance) развёрнутой системы модели. Двенадцать подкоманд, один бинарник. Эта глава разбирает, что делает каждая, какие флаги она принимает и как встроить их в CI и pre-commit хуки.

Окно терминала
npm install -g @archlang/cli

После этого archlang появится в вашем PATH. CLI — это тонкая обёртка над @archlang/engine (парсинг, разрешение, валидация) и @archlang/lsp (общий загрузчик пакетов) — всё, что нужно этим пакетам, уже включено в бинарник. PNG-вывод render идёт через @archlang/render, который включает нативный бинарь resvg и шрифт Inter — единственная подкоманда с нативной зависимостью; всё остальное — чистый JS.

Окно терминала
archlang --help
archlang --version

--version печатает версию инструментария. --help перечисляет все подкоманды и их флаги.

Окно терминала
archlang validate <path>

Парсит каждый .arch файл внутри <path> (рекурсивно, с учётом вложенных границ package.archspace), запускает резолвер, запускает валидатор. Печатает диагностики в stdout. Возвращает ненулевой код выхода, если что-то не прошло.

Окно терминала
archlang validate . # current directory
archlang validate examples/demo # a specific package
archlang validate orders.arch # a single file (its package is auto-detected)

Однофайловые черновики — полноправны. Отдельный .arch без окружающего пакета — вполне хороший способ начать: он может зависеть от локально определённых типов и стандартной библиотеки, чего достаточно, чтобы быстро смоделировать реальную идею. CLI валидирует такой файл сам по себе; повысить его до управляемого пакета можно позже. (См. правила отдельных файлов в главе 11 и главе 12.)

Диагностики используют тот же формат, что выдаёт LSP, — то есть ошибка, которую вы видите в редакторе, совпадает с тем, что печатает CLI. Примеры:

orders.arch:14:5 ERROR Required field 'team' is not fulfilled and not dropped
checkout.arch:8:9 ERROR Process step callee 'Payments' resolves to a module, not an interface

Строка-сводка считает три различные степени важности — они не взаимозаменяемы:

2 errors, 1 warning, 4 todos, 0 info
  • Ошибки — это противоречия: модель утверждает то, что не может быть истиной. Исправьте их.
  • Предупреждения — нежелательное, но допустимое: запах, а не ложь.
  • TODO — это долг черновика, а не баги: упомянутый, но не определённый модуль, интерфейс или подпроцесс, который компилятор синтезировал как заглушку. TODO означает недостающую деталь, а не неправильно. Черновое описание «процесс-сначала» порождает их по замыслу; в середине черновика это нормально.

Зрелая модель не должна выходить в свет с открытыми TODO. Флаг --complete (алиас --no-todos) включает гейт слияния с нулевым TODO: при этом прогон завершается кодом 3, если остался хоть один TODO-диагностика, и вместо простого счётчика печатает локализованный чек-лист по категориям того, что осталось.

Окно терминала
archlang validate --strict <path> # treat warnings as errors too (exit 2)
archlang validate --complete <path> # fail (exit 3) if any TODO remains; prints the punch list
archlang validate --verbose <path> # print every TODO in full, not the grouped summary
Окно терминала
archlang validate --watch <path>

Перезапускает валидацию при каждом сохранении .arch. Удобно держать терминал открытым рядом с редактором, когда не хочется включать встроенный предпросмотр. Работает до Ctrl-C.

Ненулевой код выхода — это контракт. В CI:

Окно терминала
archlang validate .

Pull-запросы не проходят, пока валидация не пройдёт. Сочетайте с format --check и check ниже.

Окно терминала
archlang format <path>

Переписывает файл (или каждый .arch файл внутри каталога) в каноническую форму: нормализованные пробелы, согласованные отступы. По умолчанию он также чеканит стабильный идентификатор для любой декларации без него — именно в форматтере фактически работает правило §3.1 спецификации «пусть инструментарий чеканит его». В остальном форматтер не меняет семантическое содержимое — он только нормализует раскладку.

Окно терминала
archlang format --check <path> # exit non-zero if anything would change
archlang format --diff <path> # print the diff that would be applied, but don't write
archlang format --no-mint <path> # skip ID minting; keep bare drafts bare
archlang format --indent=2 <path> # spaces per indent level (default 4)
archlang format --tabs <path> # use tabs instead of spaces

--check — флаг для CI. --diff — локальный флаг «а что бы это сделало?». Ни один из них не пишет на диск.

Если в файле есть ошибки парсинга, форматирование пробелов всё равно выполняется над разбираемыми участками, но чеканка идентификаторов для этого файла пропускается (чеканка записывает постоянный идентификатор — никогда не в исходник, который парсер не может разобрать).

Идентификатор бессмысленен и постоянен — по-настоящему случайный токен, который не меняется, даже когда названная им вещь переименована, переведена в другую область видимости или перемещена. Поскольку случайно набирать руками тяжело, пусть инструментарий чеканит его за вас. Форматтер (и автодополнение редактора) генерирует по-настоящему случайные идентификаторы для любой декларации, которая проходит путь format без идентификатора. Всегда предпочитайте инструмент написанию вручную.

Не редактируйте стабильный идентификатор вручную после записи — никогда не меняйте его и не вшивайте в него имя домена или типа (всё осмысленное рано или поздно устаревает и провоцирует переименование, что сводит на нет весь смысл). Переименования опираются на идентификатор, так что движок диффа видит одну переименованную декларацию вместо удаления-плюс-добавления (Глава 13).

Окно терминала
archlang check <path>

Запускает четыре фазы по очереди и отчитывается по всем, даже если более ранняя провалилась, — так вы видите всю картину сразу, а не чините-перезапускаете-чините: validate, затем policy-check, затем format --check, затем evidence. Код выхода — максимум по фазам: 0, если всё чисто, 1, если validate нашёл ошибки, политика подняла активную ошибку, format изменил бы файлы или привязка-свидетельство сломана, 2 под --strict, если найдены только предупреждения.

check — это более тяжёлый собрат validate. Используйте validate во внутреннем редакторском цикле, где нужна быстрая обратная связь; используйте check в CI и на крупных контрольных точках.

Окно терминала
archlang check --strict <path>
Окно терминала
archlang check <path> --against=main

Добавляет ещё одну фазу: разрешает рабочее пространство и на заданном git-ref (база), и в рабочем дереве (head), затем вычисляет гейты изменений v0.10 when по этому диффу и сообщает, какие гейты сработали и какого ревью каждый потребовал бы. Она также заново вычисляет правило двухстороннего waiver в контексте диффа — waiver, введённый или изменённый в этом изменении, по определению непровверен, так что любая находка, которую он иначе подавил бы, реактивируется в отчёте, даже если представление head-состояния (render/LSP/policy-check) показывает её как waived. Это пробный прогон: CLI не хранит состояние одобрений, так что сработавший гейт просто проваливает проверку (exit 1) — запись и принудительное применение реальных одобрений — задача Studio, а не CLI.

Окно терминала
archlang policy-check <path>

Вычисляет каждую декларацию policy в рабочем пространстве по производному графу зависимостей и сообщает об активных (не подавленных waiver’ом) находках. Уровни важности политики:

  • error — проваливает гейт (exit 1).
  • warning — сообщается; проваливает только под --strict (exit 2).
  • advisory — только для отчёта, никогда не проваливает.
Окно терминала
archlang policy-check --strict <path>
archlang policy-check --json <path>

Политика, использующая ещё не поддерживаемый селектор или выражение, пропускается с пометкой в stderr вместо падения всего прогона — остальной набор политик по-прежнему применяется.

Окно терминала
archlang evidence <path> [--repo=<path>] [--json]

Проверяет каждую привязку-свидетельство sources: в рабочем пространстве по коммиту, закреплённому в package.archspace (Глава 37): указанный путь является blob на этой ревизии, диапазон строк помещается внутрь него, а origin этой рабочей копии совпадает с заявленным репозиторием. --repo указывает на рабочую копию кода; без него используется текущий каталог.

https://github.com/acme/shop @ a1b2c3d
✓ Checkout src/checkout/index.ts:12-88
✗ Checkout src/checkout/tax.ts:5-40
· Ledger src/ledger.ts
1 verified, 1 broken, 1 unverified

Код выхода равен 1 только если какая-то привязка сломана. Привязка, которую вообще не удалось проверить — нет репозитория, нет закрепления, не тот репозиторий — сообщается как unverified и не проваливает прогон. Асимметрия намеренна: сломанная привязка это дефект, который автор может починить, а непроверяемая обычно означает, что у машины, где идёт проверка, нет репозитория. При этом unverified по-прежнему никогда не считается успехом, и ничто не показывает его как успех.

Все семь диагностик свидетельств имеют серьёзность warning, поэтому они всплывают в validate и check, не проваливая их; проваливает только код выхода по сломанной привязке.

Окно терминала
archlang set <path> <target> <field> <value> [--aspect]
archlang set <path> <target> <text> --description

Headless-запись в модель. <target> адресует модуль по #id, квалифицированному пути (Platform.Auth) или уникальному имени. Запись идёт через тот же слой мутаций, который используют редакторы board/inspector, так что ручное форматирование и комментарии сохраняются, — а запись отказывает (exit 1, с печатью причины), вместо того чтобы когда-либо повредить файл.

Окно терминала
archlang set . Payments version 2.1 # field: version: 2.1
archlang set . Payments team Core --aspect # aspect: team: Core
archlang set . Payments "Owns payments" --description

Создано для использования в CI/CD — поднять поле version при релизе, проставить окружение деплоя — там, где скрипту нужно тронуть одно поле без ручного редактирования исходника .arch.

Окно терминала
archlang export json <path>

Выгружает разрешённую модель рабочего пространства как JSON — та же форма, что у GET /api/model на archlang serve, так что CI-задаче, которой нужна модель, не приходится поднимать сервер.

Окно терминала
archlang export json . -o model.json # write to a file instead of stdout
archlang export json . --stdlib=<path> # override the stdlib lookup

Полезная нагрузка — { model: { modules, processes, subprocesses, views, types, documents?, backlinks? }, diagnostics: [...] }.

Окно терминала
archlang export html . -o architecture.html

Пишет ОДИН самодостаточный HTML-файл: оболочка Viewer, ваше рабочее пространство и шрифты — всё внутри. Он открывается двойным щелчком, на машине, где ничего нашего никогда не устанавливали, с выдернутой сетью — без сервера, без развёрнутого Viewer, без настройки.

Это артефакт для мест, куда встраивание из главы 23 не дотягивается: приложить к ревью, отправить архитектору письмом, положить в релиз, передать кому-то за пределами вашей сети. Внутри файла — полноценный Viewer: навигация, пространства, представления процессов и таблиц, ваши документы .md (только для чтения — снимок заморожен), вложенные в них картинки, экспорт PNG/SVG; без редактора и сравнения, которые замороженному снимку ни к чему.

Окно терминала
archlang export html . -o out.html --force # emit despite error diagnostics

-o обязателен (файл — мегабайты разметки). Команда отказывается работать с пространством, где есть диагностики уровня error, пока не передан --force, и отказывается перезаписать один из ваших исходников. --force также разрешает -o писать за пределы текущего каталога — иначе это запрещено.

Текст рисуется вшитым Inter — тем же шрифтом, что и в headless-экспорте PNG. Поэтому артефакт ближе к выводу archlang render, чем к тому, что показывает ваш браузер со своим системным шрифтом.

Это экспорт, а не замена встраиванию: файл — снимок, он не обновляется вслед за моделью. Для живой диаграммы на странице, которой вы управляете, встраивайте Viewer.

Окно терминала
archlang render [path] --out=<file.svg|file.png> [--view=board|bpmn|flow|sequence] ...

Headless-экспорт SVG/PNG — без браузера, без сервера. Это тот же конвейер walk→layout→SVG, который используют эндпоинты /api сервера предпросмотра и инструмент MCP arch_render.

Окно терминала
archlang render . --view=board --out=board.svg # module board (default view)
archlang render . --view=bpmn --process=Orders.Checkout --out=flow.png --scale=3
archlang render . --view=flow --process=Orders.Checkout --out=flow.svg
archlang render . --view=bpmn --process=Orders.Checkout --out=diff.svg --diff-base=../old-workspace
archlang render . --view=board --out=compare.svg --diff-base=../old-workspace # Before/Delta/After + чек diff'а
archlang render . --view-name=CheckoutOps --out=ops.svg # a declared flow view

Флаги:

  • --out=<file> — обязателен и должен заканчиваться на .svg или .png (расширение выбирает формат; --format=svg|png переопределяет записываемые байты, но не требование к расширению). Путь обязан разрешаться внутри текущего каталога и не может быть одним из исходников модели.
  • --view=<v>board (по умолчанию), bpmn, flow или sequence. bpmn/flow/sequence отрисовывают процесс и требуют --process.
  • --view-name=<name> — вместо этого отрисовать объявленную view по имени (её тело flow выбирает plain/sequence/bpmn, включая раскладку lane/pool bpmn-проекции; экземпляры view связывают свои knob’ы). Взаимоисключим с --process и --diff-base.
  • --process=<name> — квалифицированное точечное имя процесса (например, Orders.Checkout), обязателен для bpmn/flow/sequence.
  • --expand=all|<ids> — развернуть узлы do-подпроцессов: all либо список id do-узлов через запятую.
  • --diff-base=<path> — сравнить со старым рабочим пространством по этому пути. С --view=board отрисовывается артефакт Before / Delta / After (Delta — объединение обеих ревизий, единственная панель, где добавленное и удалённое видно вместе), а в чек добавляется блок compare: классификация каждого изменения, явная формулировка правил идентичности, сырые и семантические хеши обеих сторон и массив limitations — что сравнение сказать не может. С --view=bpmn отрисовывается объединённый каркас процесса с акцентами изменений. flow/sequence диффа не поддерживают.
  • --scale=<n> — только для PNG: множитель зума для разрешения (по умолчанию 2, ограничен 8).
  • --force — отрисовать, несмотря на диагностики уровня error, и разрешить --out вне текущего каталога. Перезаписать исходник модели он не разрешает никогда.
  • --stdlib=<path> — переопределить путь поиска stdlib.

Растеризация PNG идёт через встроенный resvg пакета @archlang/render — детерминированный вывод, встроенные шрифты Inter, без зависимости от системных шрифтов.

Сначала проверка, потом доставка. Модель с диагностиками уровня error отклоняется (exit 1), а не отрисовывается: в артефакте не хватало бы всего, что не разрешилось, а exit 0 сказал бы CI, что всё в порядке. Артефакт пишется через staging-файл, который затем переименовывается на место, поэтому после отказа или сбоя предыдущий артефакт — последний хороший — остаётся нетронутым. Каждый запуск кладёт рядом <out>.receipt.json с хешем входа, хешем артефакта, счётчиками и перечнем того, чего расписка не утверждает (визуальная проверка не заявляется никогда); при отказе расписка называет хеш сохранённого последнего хорошего артефакта и перечисляет каждую блокирующую диагностику как расписку о ремонте: её зарегистрированный код, msgKey/params, из которых собрано сообщение (включая кандидатов did-you-mean), и supportedFixes — что именно правит починка: вход модели или вход солвера. Диагностика, первопричина которой — другая диагностика в том же файле, отбрасывается, поэтому список называет причины, а не симптомы. Инструмент MCP arch_render (force: true, <outFile>.receipt.json) и маршруты отрисовки /api сервера (?force=1, HTTP 409 с распиской отказа, X-ArchLang-Receipt при успехе) следуют тем же трём правилам.

Окно терминала
archlang serve [path] [--port=<n>] [--web-root=<path>] [--quiet]

Запускает HTTP API сервер — разрешённую модель плюс REST-эндпоинты и опциональный веб-интерфейс, если --web-root указывает на собранный. Оборачивает CLI @archlang/server; каждый флаг пробрасывается как есть. Порт по умолчанию — 3100; --port=0 выбирает эфемерный порт. Работает бесконечно — для выхода SIGINT/SIGTERM.

Окно терминала
archlang lsp

Запускает языковой сервер на stdio для клиентов редактора/IDE, которые хотят запускать LSP как подпроцесс, а не полагаться на встроенную копию. VS Code и IntelliJ включают LSP напрямую (см. Главу 22); archlang lsp — для построения собственной интеграции. Работает бесконечно, пока клиент не пришлёт уведомление LSP exit или процесс не получит SIGINT/SIGTERM.

Окно терминала
archlang conform <path> --source=jaeger|tempo --endpoint=<url>

Проверяет развёрнутую систему по модели с помощью трасс OpenTelemetry — направление «модель-к-реальности», дополняющее направление validate «синтаксис-к-модели». Два измерения: соответствие графа сервисов (агрегированные рёбра, видимые в трассах, против рёбер, объявленных моделью) и соответствие процесса по трассе (совпадают ли шаги наблюдаемой трассы с шагами смоделированного процесса, по порядку).

Окно терминала
archlang conform . --source=jaeger --endpoint=https://jaeger.internal \
--metrics-endpoint=https://prom.internal --lookback=3600 --limit=1000 --json
  • --source=jaeger|tempo и --endpoint=<url> обязательны.
  • --metrics-endpoint=<url> — опциональный бэкенд метрик для более богатого сигнала соответствия.
  • --lookback=<sec> — окно запроса трасс (по умолчанию 3600, ограничено неделей).
  • --limit=<n> — максимум трасс для выборки (по умолчанию 1000, ограничено 100 000).
  • Авторизация: задайте ARCHLANG_OTEL_TOKEN в окружении; он отправляется как заголовок Authorization бэкенда и никогда не логируется и не отражается.

Exit 1 при любом нарушении соответствия (рёбра, видимые только в телеметрии и не объявленные моделью, неизвестные трассы, лишние/пропущенные/переставленные шаги) или если бэкенд вообще не вернул данных.

Окно терминала
archlang info <path>

Печатает сводку пакета: имя, версия, число файлов/модулей/типов/процессов/проекций/документов, объявленные пространства (spaces) и дерево транзитивных зависимостей.

Сценарии использования:

  • Введение в проект. Новый член команды запускает archlang info на пакете, в котором ему предстоит работать; он получает обзор размером в один экран, не открывая файлы.
  • Артефакты CI. Сохраняйте вывод как артефакт сборки, чтобы рецензенты PR могли с одного взгляда увидеть, как выглядит архитектура ветки.
  • Аудит миграций. Запускайте на старом пакете при его моделировании, затем перезапускайте после каждого прохода рефакторинга.

Типичный pre-commit хук:

#!/usr/bin/env bash
set -e
archlang format --check .
archlang validate .

Типичная CI-задача:

Окно терминала
archlang format --check .
archlang check . --against=origin/main
archlang info . > arch-summary.txt

format --check гарантирует, что каждый файл канонически отформатирован. check --against запускает validate + policy-check + format-check + evidence плюс пробный прогон гейта изменений относительно базовой ветки PR. info выдаёт артефакт для рецензентов.

Для целей CI считайте провалом любой ненулевой код выхода.

  • 0 — успех, ошибок нет.
  • 1 — диагностики уровня error, активная ошибка политики, несоответствие формата под --check, сломанная привязка-свидетельство, нарушение соответствия (conformance) или сбой загрузки.
  • 2 — под --strict и хотя бы одна диагностика/находка уровня warning (validate, check, policy-check).
  • 3 — под --complete и остался хотя бы один TODO (только validate).
  • 64 — ошибка использования: неизвестная подкоманда, отсутствует обязательный аргумент.

validate --watch и serve работают до прерывания; их отражаемый код выхода (0) отражает чистое завершение через SIGINT/SIGTERM, а не результат валидации.

Дифф диаграмм не отдельная подкоманда — это два флага на командах выше: render --diff-base=<path> отрисовывает визуальный дифф (доска Before/Delta/After или объединённый каркас BPMN с --view=bpmn), а check --against=<ref> запускает дифф гейта изменений относительно git-ref. Базовый дифф — это ещё и библиотечный API (BpmnEdge.diff и соседи из @archlang/engine), который режим диффа Viewer потребляет напрямую.

Системы плагинов нет — всё, что делает CLI, — это одна из двенадцати подкоманд выше; настройка происходит через прямое использование библиотечных пакетов (@archlang/engine, @archlang/viewer-core, @archlang/render).

  • Двенадцать подкоманд: validate, format, check, policy-check, evidence, set, export, render, serve, lsp, conform, info.
  • validate — для внутреннего цикла; check (опционально --against=<ref>) — для CI; policy-check — для governance; info/export — для сводок и инструментария.
  • set headless-пишет одно поле/аспект/описание; render производит headless SVG/PNG; serve запускает HTTP API; lsp запускает языковой сервер; conform проверяет телеметрию живой системы по модели.
  • Коды выхода стабильны; конвейеры ориентируются на них.
  • Нет системы плагинов, нет отдельной подкоманды diff — дифф живёт в render --diff-base / check --against и в библиотечном diff API движка.

Глава 22: Интеграция с редактором → — что языковой сервер даёт в VS Code и JetBrains.