# C4-Builder * [Обзор](#%D0%9E%D0%B1%D0%B7%D0%BE%D1%80) * [01 Быстрый старт](#01-%D0%91%D1%8B%D1%81%D1%82%D1%80%D1%8B%D0%B9-%D1%81%D1%82%D0%B0%D1%80%D1%82) * [02 Проект](#02-%D0%9F%D1%80%D0%BE%D0%B5%D0%BA%D1%82) * [03 Диаграммы](#03-%D0%94%D0%B8%D0%B0%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D1%8B) * [04 Выводы](#04-%D0%92%D1%8B%D0%B2%D0%BE%D0%B4%D1%8B) * [05 CLI и конфигурация](#05-CLI-%D0%B8-%D0%BA%D0%BE%D0%BD%D1%84%D0%B8%D0%B3%D1%83%D1%80%D0%B0%D1%86%D0%B8%D1%8F) * [06 Плагины](#06-%D0%9F%D0%BB%D0%B0%D0%B3%D0%B8%D0%BD%D1%8B) * [07 Docker и CI](#07-Docker-%D0%B8-CI) * [08 Агенты](#08-%D0%90%D0%B3%D0%B5%D0%BD%D1%82%D1%8B) * [09 Разработка](#09-%D0%A0%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0) * [Журнал решений](#%D0%96%D1%83%D1%80%D0%BD%D0%B0%D0%BB-%D1%80%D0%B5%D1%88%D0%B5%D0%BD%D0%B8%D0%B9) * [Changes](#Changes) * [Specs](#Specs) * [agent-skills](#agent-skills) * [bpmn-diagrams](#bpmn-diagrams) * [build-pipeline](#build-pipeline) * [ci-validation](#ci-validation) * [cli-commands](#cli-commands) * [config-validation](#config-validation) * [dev-toolchain](#dev-toolchain) * [diagram-rendering](#diagram-rendering) * [docs-site](#docs-site) * [jre-resolution](#jre-resolution) * [offline-site](#offline-site) * [openapi-plugin](#openapi-plugin) * [openspec-plugin](#openspec-plugin) * [output-formats](#output-formats) * [plugin-system](#plugin-system) * [project-scaffold](#project-scaffold) * [project-template](#project-template) * [regression-testing](#regression-testing) * [release-publishing](#release-publishing) * [Archive](#Archive) * [2026-10-06-docs-site](#2026-10-06-docs-site) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [docs-site](#docs-site) * [2026-10-06-bpmn-diagrams](#2026-10-06-bpmn-diagrams) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [bpmn-diagrams](#bpmn-diagrams) * [diagram-rendering](#diagram-rendering) * [project-template](#project-template) * [regression-testing](#regression-testing) * [2026-10-03-pr8-review-fixes](#2026-10-03-pr8-review-fixes) * [tasks](#tasks) * [README](#README) * [2026-10-03-plugin-system](#2026-10-03-plugin-system) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [openapi-plugin](#openapi-plugin) * [openspec-plugin](#openspec-plugin) * [plugin-system](#plugin-system) * [2026-10-03-plantuml-1-2026-8](#2026-10-03-plantuml-1-2026-8) * [tasks](#tasks) * [2026-10-03-offline-site-assets](#2026-10-03-offline-site-assets) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [offline-site](#offline-site) * [openapi-plugin](#openapi-plugin) * [2026-10-03-node-22](#2026-10-03-node-22) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [dev-toolchain](#dev-toolchain) * [2026-10-03-llms-txt](#2026-10-03-llms-txt) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [output-formats](#output-formats) * [project-scaffold](#project-scaffold) * [2026-10-03-cli-fixes](#2026-10-03-cli-fixes) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [cli-commands](#cli-commands) * [2026-10-03-cli-english-messages](#2026-10-03-cli-english-messages) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [cli-commands](#cli-commands) * [2026-10-03-agent-skills](#2026-10-03-agent-skills) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [agent-skills](#agent-skills) * [cli-commands](#cli-commands) * [project-template](#project-template) * [2026-07-24-nimbus-font-system-flag](#2026-07-24-nimbus-font-system-flag) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [diagram-rendering](#diagram-rendering) * [2026-07-09-tag-driven-rc-publish](#2026-07-09-tag-driven-rc-publish) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [release-publishing](#release-publishing) * [2026-07-08-zod-and-deps-upgrade](#2026-07-08-zod-and-deps-upgrade) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [config-validation](#config-validation) * [dev-toolchain](#dev-toolchain) * [2026-07-07-ts-port-full](#2026-07-07-ts-port-full) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [build-pipeline](#build-pipeline) * [2026-07-07-build-split](#2026-07-07-build-split) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [build-pipeline](#build-pipeline) * [2026-07-05-ts-scaffold](#2026-07-05-ts-scaffold) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [build-pipeline](#build-pipeline) * [ci-validation](#ci-validation) * [dev-toolchain](#dev-toolchain) * [2026-07-05-resvg-png](#2026-07-05-resvg-png) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [diagram-rendering](#diagram-rendering) * [2026-07-05-remove-vscode-snippets](#2026-07-05-remove-vscode-snippets) * [design](#design) * [tasks](#tasks) * [README](#README) * [specs](#specs) * [project-scaffold](#project-scaffold) * [2026-07-05-remove-plantuml-version](#2026-07-05-remove-plantuml-version) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [diagram-rendering](#diagram-rendering) * [2026-07-05-remove-pdf](#2026-07-05-remove-pdf) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [output-formats](#output-formats) * [2026-07-05-new-noninteractive](#2026-07-05-new-noninteractive) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [project-scaffold](#project-scaffold) * [2026-07-05-jre-resolver](#2026-07-05-jre-resolver) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [diagram-rendering](#diagram-rendering) * [jre-resolution](#jre-resolution) * [2026-07-05-golden-matrix](#2026-07-05-golden-matrix) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [regression-testing](#regression-testing) * [2026-07-05-dev-toolchain](#2026-07-05-dev-toolchain) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [ci-validation](#ci-validation) * [dev-toolchain](#dev-toolchain) * [2026-07-05-d2-backend](#2026-07-05-d2-backend) * [design](#design) * [tasks](#tasks) * [README](#README) * [specs](#specs) * [diagram-rendering](#diagram-rendering) * [regression-testing](#regression-testing) * [2026-07-04-template-offline](#2026-07-04-template-offline) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [project-template](#project-template) * [2026-07-04-plantuml-java-direct](#2026-07-04-plantuml-java-direct) * [design](#design) * [tasks](#tasks) * [README](#README) * [specs](#specs) * [ci-validation](#ci-validation) * [diagram-rendering](#diagram-rendering) * [regression-testing](#regression-testing) * [2026-07-04-golden-test-ci](#2026-07-04-golden-test-ci) * [design](#design) * [tasks](#tasks) * [specs](#specs) * [ci-validation](#ci-validation) * [regression-testing](#regression-testing) --- ## Обзор # C4-Builder **Архитектура как код, которую агент пишет, проверяет и читает.** C4-Builder — CLI, который собирает папку с markdown и диаграммами PlantUML, D2 или BPMN в документацию: сайт на docsify, markdown по папкам или один файл. Папки задают иерархию [C4-модели](https://c4model.com/) (система → контейнер → компонент), а git хранит историю архитектурных решений вместе с кодом: правка архитектуры проходит ревью в pull request, как любое другое изменение. Этот сайт собран самим c4builder из папки `docs/` репозитория, диаграммы на нём отрендерены при сборке в CI. ```plantuml @startuml !include !include styles.iuml LAYOUT_TOP_DOWN() Person(dev, "Разработчик", "Пишет markdown и диаграммы, ревьюит архитектуру в pull request") Person(agent, "AI-агент", "Claude Code, Codex, Cursor: правит модель и проверяет её через c4builder check") System(c4b, "C4-Builder", "CLI: собирает папку markdown + PlantUML/D2 в документацию, проверяет отдельные диаграммы") System_Ext(engines, "Движки диаграмм", "PlantUML (вендорный jar + Java, Smetana), D2 (WASM)") System_Ext(openspec, "OpenSpec-store", "Спеки и change'и проекта: proposal, design, tasks") System_Ext(openapi, "OpenAPI-контракты", "Спеки сервисов: папка или архив репозитория") System_Ext(site, "Docsify-сайт", "Статика без CDN: GitHub Pages, GitLab Pages, любой веб-сервер") System_Ext(md, "Markdown", "README по папкам или один файл для репозитория") Rel(dev, c4b, "Собирает и смотрит", "c4builder --site -w") Rel(agent, c4b, "Проверяет правки", "c4builder check") Rel(c4b, engines, "Рендерит диаграммы", "локально, без сети") Rel(c4b, openspec, "Публикует раздел", "плагин openspec") Rel(c4b, openapi, "Публикует swagger-ui", "плагин openapi") Rel(c4b, site, "Собирает") Rel(c4b, md, "Собирает") SHOW_LEGEND() @enduml ``` ## Чем форк отличается от upstream Оригинальный C4-Builder не развивается: последний коммит — март 2024 года, последний npm-релиз `c4builder` 0.2.16 вышел в 2022-м. В Альфа-Капитале на нём собирается архитектурный репозиторий на сотню с лишним диаграмм, поэтому мы продолжаем проект как поддерживаемый форк и развиваем его под работу с AI-агентами: - **Детерминированный офлайн-рендер.** PlantUML-jar, шрифт и движки поставляются с пакетом, раскладку считает встроенный в PlantUML движок Smetana, Graphviz не нужен. Одни и те же исходники дают одинаковые SVG на ноутбуке, в CI и в Docker, а сеть при сборке не нужна. - **`c4builder check` — обратная связь за секунды.** Проверяет отдельные диаграммы тем же движком, что и сборка, и возвращает код выхода и номер строки с ошибкой. Агент правит `.puml` и сразу узнаёт, сломал ли он что-нибудь, без человека и без полной сборки. - **Плагины.** `openspec` публикует спеки и change'и рядом с C4-моделью, `openapi` — контракты сервисов офлайн-страницами swagger-ui. - **D2** вторым движком диаграмм, PNG через resvg, Java по желанию: если её нет, c4builder скачает JRE сам. - **BPMN-процессы** третьим движком: агент пишет BPMN 2.0 XML без координат, сборка проверяет модель и раскладывает её с горизонтальными пулами и дорожками. - **Инженерная база.** TypeScript, golden-снапшоты рендера, CI на каждый pull request, npm-пакет и Docker-образ по тегу релиза. Полный список изменений — в [CHANGELOG](https://github.com/AlfaCapital-Tech/C4-Builder/blob/master/CHANGELOG.md). ## Цикл с агентом 1. Агент (или вы) правит `.md` и `.puml` в `src/`. 2. `c4builder check <файлы>` отвечает, собирается ли диаграмма, и показывает строку ошибки. 3. `c4builder --site -w` пересобирает сайт на каждое сохранение и обновляет браузер. Скиллы для агентов, `AGENTS.md` в новом проекте и `llms.txt` с исходниками диаграмм — на странице [Агенты](08%20Агенты/08%20Агенты.md). ## Разделы - [Быстрый старт](01%20Быстрый%20старт/01%20Быстрый%20старт.md) — установка и первый проект. - [Проект](02%20Проект/02%20Проект.md) — как папки и файлы превращаются в страницы. - [Диаграммы](03%20Диаграммы/03%20Диаграммы.md) — PlantUML, D2, BPMN, шрифты, PNG, проверка. - [Выводы](04%20Выводы/04%20Выводы.md) — сайт, markdown по папкам, один файл. - [CLI и конфигурация](05%20CLI%20и%20конфигурация/05%20CLI%20и%20конфигурация.md) — команды и ключи `.c4builder`. - [Плагины](06%20Плагины/06%20Плагины.md) — `openspec`, `openapi`, свой плагин. - [Docker и CI](07%20Docker%20и%20CI/07%20Docker%20и%20CI.md) — образ и сборка в пайплайне. - [Разработка](09%20Разработка/09%20Разработка.md) — как устроен и выпускается c4builder. - **Журнал решений** — OpenSpec-store этого репозитория: почему c4builder устроен именно так. ## Благодарности C4-Builder создал Adrian Victor Lupu; проект распространяется по лицензии MIT. Форк сохраняет лицензию, авторство и историю: изменения upstream со ссылками на исходные pull request'ы перечислены в CHANGELOG. ## 01 Быстрый старт [Обзор](#C4-Builder) # Быстрый старт ## Установка ```bash npm i -g @alfacapital-tech/c4builder ``` Нужен Node.js 22.17 или новее. `npm install` без тега ставит последний стабильный релиз. Для рендера PlantUML нужна Java 17+. Если `java` есть в `JAVA_HOME` или в `PATH`, c4builder возьмёт её; если нет — при первой сборке скачает приватную JRE (Temurin) в свой кэш. Graphviz не нужен: раскладку считает встроенный в PlantUML движок Smetana. PlantUML-jar и шрифт диаграмм входят в пакет. ```bash c4builder jre info # какая Java будет использована (JSON: версия, путь кэша) c4builder jre install # скачать JRE заранее: перед работой офлайн или в образ CI ``` Если ставить Node и Java не хочется, есть [Docker-образ](07%20Docker%20и%20CI/07%20Docker%20и%20CI.md), в нём уже всё есть. ## Первый проект ```bash c4builder --new --name demo -y # проект из шаблона с готовым конфигом, без вопросов cd demo c4builder # сборка в docs/ c4builder site # сайт на http://localhost:3000 ``` Без `-y` c4builder спросит имя проекта, а первая сборка проведёт через мастер настройки. С версии 0.5.0 то же самое можно писать короче — `c4builder new --name demo -y`; в 0.4.0 позиционной формы нет, там только флаг `--new`. В новом проекте лежит демо «Internet Banking System» из C4-модели: контекст, контейнеры, deployment, динамическая диаграмма, sequence, class и ditaa, страница с кириллицей, пример на D2 и BPMN-процесс «Открытие счёта». Все C4-диаграммы подключают библиотеку через stdlib (`!include `): C4-PlantUML встроен в jar, поэтому сборке не нужен интернет. Рядом с `src/` лежит `AGENTS.md` — инструкция для AI-агентов, как собирать и проверять диаграммы (с версии 0.5.0, в документацию не попадает). ## Живой предпросмотр ```bash c4builder --site -w ``` Собирает проект, поднимает локальный сервер и пересобирает сайт на каждое сохранение в `src/`. Браузер перезагружается сам и сохраняет позицию прокрутки. `-o` открывает браузер, `-p 8080` меняет порт. ## Изменить настройки `c4builder --config` (с 0.5.0 — и `c4builder config`) снова запускает мастер, подставляя текущие значения; `c4builder --list` показывает конфиг. `.c4builder` — обычный JSON, его можно править руками: все ключи описаны на странице [CLI и конфигурация](05%20CLI%20и%20конфигурация/05%20CLI%20и%20конфигурация.md). ## Релиз-кандидаты Пре-релизы `X.Y.Z-rc.N` публикуются под npm-тегом `rc` и не трогают `latest`: ```bash npm i -g @alfacapital-tech/c4builder@rc ``` Чтобы вернуться на стабильную версию, поставьте пакет с тегом `latest`. ## 02 Проект [Обзор](#C4-Builder) # Проект Проект c4builder — это папка с файлом `.c4builder`. В ней две главные папки: **исходники** (`src`, ключ `rootFolder`) и **выход** (`docs`, ключ `distFolder`). Все команды запускаются из папки проекта: пути в конфиге считаются от неё. ## Папка = страница Каждая папка внутри исходников — одна страница документации. По C4-модели это обычно система, контейнер или компонент, но подойдёт любая тема: ADR, глоссарий, runbook. ```text demo/ ├── .c4builder конфиг проекта ├── docs/ выход: пересоздаётся при каждой сборке └── src/ исходники ├── context.md корень = главная страница ├── context.puml ├── styles.iuml общая тема диаграмм (подключается через !include) └── 1 Internet Banking System/ ├── system.md ├── system.puml └── API Application/ ├── container.md └── container.puml ``` Сборка обходит дерево и склеивает все `.md` и диаграммы папки в одну страницу: - **Заголовок страницы** — имя папки. Если первый `.md` начинается со своего `# Заголовка`, берётся он. - **Порядок страниц** — порядок файловой системы. Чтобы задать его явно, используйте числовые префиксы: `1 Контекст`, `2 Контейнеры`. Префикс виден в заголовке в sidebar. - **Диаграммы** вставляются картинками. По умолчанию — перед текстом страницы, после текста — с `diagramsOnTop: false`. - **Диаграмму можно поставить в любое место текста** ссылкой на её исходник: `![контейнеры](container.puml)`. Такая диаграмма в общий блок уже не попадает. - **Файлы и папки с префиксом `_`** не становятся страницами и не рендерятся отдельно, но доступны для `!include` и D2-импортов. Это место для общих библиотек: `_c4lib.d2`, `_common.iuml`. - **Прочие файлы** (картинки, вложения) копируются в выход рядом со страницей, если не включён `excludeOtherFiles`. Картинку кладите рядом с `.md`, который на неё ссылается. ## Служебные файлы | Файл | Зачем | В git | |---|---|---| | `.c4builder` | конфиг проекта | да | | `.c4builder.cache` | чексуммы диаграмм: неизменённые не рендерятся заново | нет | | `.c4builder.lock` | лок сборки: две сборки в одной папке не пишут выход одновременно | нет | | `docs_bk/` | копия прошлого выхода на время сборки; при ошибке остаётся как резерв | нет | Папку выхода c4builder удаляет и создаёт заново на каждой сборке, поэтому своё туда не кладите. Коммитить ли сам выход, зависит от вывода: markdown по папкам удобно держать в репозитории, сайт обычно собирается в CI. ## 03 Диаграммы [Обзор](#C4-Builder) # Диаграммы Движок выбирается по расширению файла: `.puml` рендерит PlantUML, `.d2` — D2, `.bpmn` — BPMN-движок. Форматы могут жить в одном проекте и даже в одной папке, если у файлов разные имена. ## PlantUML c4builder запускает вендорный PlantUML-jar напрямую через `java`, раскладку считает встроенный движок Smetana. Graphviz ставить не нужно. Версия jar зафиксирована в пакете, поэтому обновление PlantUML на машине разработчика сборку не меняет. **C4 без интернета.** Библиотека C4-PlantUML встроена в jar, подключайте её через stdlib: ```puml @startuml !include !include ../styles.iuml Person(user, "Клиент") System(bank, "Интернет-банк") Rel(user, bank, "Пользуется", "HTTPS") @enduml ``` **Общие стили** — в `.iuml`: `skinparam`, `UpdateElementStyle(...)`, свои макросы. Подключаются после C4-инклюда относительным путём. Пути в `!include` считаются от папки диаграммы, а не от корня проекта. **Кириллица** работает в подписях, описаниях и именах папок: шрифт диаграмм её содержит. **ditaa** (`@startditaa`) тоже поддерживается и всегда рендерится в PNG. ## Диаграмма прямо в странице Блок ```` ```plantuml ```` в `.md`-странице рендерится при сборке так же, как файл `.puml`: в сайте, markdown-коллекции и общем документе на его месте будет картинка, а не текст блока. `@startuml`/`@enduml` можно опустить, `!include` считаются от папки страницы. Блок с ошибкой сборку не роняет: на месте картинки — заглушка, в логе — предупреждение с путём `.md`-файла. Чтобы показать исходник диаграммы как код, пометьте блок `puml` или `text`: такие блоки, как и ```` ```d2 ````, остаются кодом. ## Детерминизм и шрифт Диаграммы рисуются вендорным шрифтом Nimbus Sans (метрики Helvetica, кириллица, греческий), а не системным. Поэтому одна и та же диаграмма даёт одинаковую геометрию на macOS, Linux, в CI и в Docker, а дифф SVG в pull request показывает только реальные изменения. `useSystemFonts: true` в `.c4builder` (или `--system-fonts` на один запуск) включает системные шрифты. Вид станет привычнее, но результат будет зависеть от машины. ## Кэш рендера Неизменённые диаграммы при повторной сборке не рендерятся: c4builder хранит чексуммы в `.c4builder.cache`. В чексумму входят исходник, все локальные `!include` и D2-импорты, путь выхода и параметры рендера, для D2 и BPMN — ещё версии пакетов движка. Правка общего `styles.iuml` перерисует все диаграммы, которые его подключают, и только их. ## D2 [D2](https://d2lang.com) рендерится через WASM-сборку движка прямо в Node — без внешних бинарников. Ссылка из markdown такая же, как для PlantUML: `![схема](landscape.d2)`. - Движок — опциональная зависимость `@terrastruct/d2` (~60 МБ). npm ставит её вместе с c4builder, а загружается она только если в проекте есть `.d2`. Если пакета нет (например, установка шла с `--omit=optional`), сборка остановится с подсказкой `npm install @terrastruct/d2`. - [Импорты](https://d2lang.com/tour/imports) `@file` и `...@file` разрешает сам c4builder, сеть не нужна. Общую библиотеку классов назовите с `_` (`_c4lib.d2`), чтобы она не рендерилась отдельной диаграммой: `...@../_c4lib`. - Раскладка — ключ `d2Layout`: `dagre` (по умолчанию) или `elk`. ## BPMN `.bpmn` — бизнес-процесс в нотации BPMN 2.0: события, задачи, шлюзы, горизонтальные пулы и дорожки, message flow между участниками. Ссылка из markdown обычная: `![процесс](process.bpmn)`. **Пишется только семантика.** Файл — стандартный BPMN 2.0 XML: `collaboration` с участниками (пулами) и message flow, `process` с `laneSet`, узлами и sequence flow. Координаты (секция `bpmndi:BPMNDiagram`) не нужны, а если они есть — игнорируются: раскладку всегда делает сборка. Элементы `incoming`/`outgoing` писать не обязательно, связи берутся из `sourceRef`/`targetRef`. ```xml ``` **Пример** — процесс открытия счёта из шаблона `c4builder new`: два пула, дорожки, обмен сообщениями с бюро кредитных историй, таймер-эскалация. В файле только семантика, раскладку и рендер сделала эта сборка: ```xml Start_AccountNeeded Task_SubmitApplication End_AccountOpened End_Rejected Task_CheckDocuments Task_RequestHistory Task_ReceiveReport Task_Assess Timer_AssessOverdue Task_Escalate End_Escalated Gateway_Decision Task_OpenAccount Task_PrepareRejection ``` **Раскладка** — [bpmn-auto-layout](https://github.com/bpmn-io/bpmn-auto-layout): пулы и дорожки горизонтальными полосами с подписью слева, поток слева направо, каждый узел в полосе своей дорожки. Подпроцессы рисуются свёрнутыми — детализацию выносите в отдельный `.bpmn`. Раскладчик пока в alpha: если он потеряет элемент модели, сборка остановится с ошибкой и id элементов, а не выпустит урезанную схему. Ручными координатами неудачную раскладку не поправить — упростите модель или разбейте её на обзор и детализацию. **Рендер** — [bpmn-js](https://bpmn.io) в стандартной нотации, без браузера и сети. Текст меряется по вендорному Nimbus Sans, поэтому SVG одинаков на любой машине (режим `useSystemFonts` на BPMN не влияет), PNG — штатной растеризацией. **Проверка модели** идёт перед раскладкой — и в сборке, и в `c4builder check`. Набор правил фиксирован: рекомендованные правила [bpmnlint](https://github.com/bpmn-io/bpmnlint) (кроме `no-bpmndi`) и правила c4builder: | Правило | Уровень | Что проверяет | |---|---|---| | `no-disconnected`, `start-event-required`, `end-event-required`, `label-required`, … | error | рекомендованный набор bpmnlint | | `c4builder/lane-membership` | error | у процесса с дорожками каждый узел ровно в одной дорожке | | `c4builder/sequence-flow-in-pool` | error | sequence flow не пересекает границу пула | | `c4builder/message-flow-between-pools` | error | message flow соединяет разные пулы | | `c4builder/gateway-flow-labels` | warning | ветки XOR/inclusive-шлюза подписаны | Ошибка останавливает рендер диаграммы, предупреждение только печатается. Каждое нарушение — отдельная строка с id элемента и именем правила: ```text ✗ src/process.bpmn: Task_Check [no-disconnected] Element is not connected ⚠ src/process.bpmn: Flow_No [c4builder/gateway-flow-labels] Outgoing flow of a diverging gateway has no label ``` **Конвенции:** пул — организация (внешняя — пулом без процесса, «чёрным ящиком»), дорожка — роль или подразделение внутри неё; между пулами только message flow; ветки шлюза подписаны; вместо диаграммы на 40+ узлов — обзорный процесс и отдельные `.bpmn` для подробностей. **Зависимости.** Пакеты движка (`bpmn-js`, `bpmn-moddle`, `bpmnlint`, `bpmn-auto-layout`, `jsdom`) — опциональные: npm ставит их вместе с c4builder, а загружаются они, только если в проекте есть `.bpmn`. Установка с `--omit=optional` оставляет только ядро — сборка проекта с `.bpmn` остановится с подсказкой переустановить c4builder без этого флага. ## PNG С `diagramFormat: png` c4builder рендерит SVG и растеризует его через [resvg](https://github.com/thx/resvg-js) тем же вендорным шрифтом. PNG получается одинаковым для PlantUML, D2 и BPMN и не требует браузера. ditaa остаётся нативным PNG PlantUML. ## Онлайн-сервер PlantUML С `generateLocalImages: false` диаграммы не рендерятся локально: в выход попадают ссылки на сервер из `plantumlServerUrl` (по умолчанию `https://www.plantuml.com/plantuml`). Это значит, что исходники диаграмм уходят на внешний сервер, а D2 и BPMN в этом режиме не работают — сборка остановится с ошибкой. Режим оставлен для совместимости; основной — локальный рендер. ## Проверка отдельных диаграмм ```bash c4builder check src/context.puml "src/4 D2 Example/landscape.d2" ``` `check` проверяет `.puml`, `.iuml`, `.d2` и `.bpmn` тем же движком, что и сборка (для `.bpmn` — разбор, правила, раскладка и рендер), но ничего не пишет и не требует проекта или `.c4builder`. Код выхода 0 — все файлы собираются, 1 — есть ошибка (предупреждения на код не влияют): ```text ✓ src/context.puml ✗ src/system.puml: line 4: Fatal parsing error ``` `!include` разрешается от папки проверяемого файла. Библиотека `.iuml` проверяется включением в пустую диаграмму, а `.iuml` с макросами C4 (`UpdateElementStyle` и т. п.) — в контексте C4-stdlib, как в диаграммах, которые его подключают. Номер строки для `.iuml` не печатается. Pre-commit-хук, который проверяет диаграммы в индексе (`.git/hooks/pre-commit`, пробелы в путях не мешают): ```bash #!/bin/sh git diff --cached --name-only -z --diff-filter=ACM -- '*.puml' '*.iuml' '*.d2' '*.bpmn' | xargs -0 -r c4builder check ``` ## 04 Выводы [Обзор](#C4-Builder) # Выводы Форматы выхода включаются независимо, в любом сочетании: мастер спрашивает их при первой сборке, в `.c4builder` за них отвечают `generateMD`, `generateCompleteMD` и `generateWEB`. Структура выхода повторяет структуру исходников. ## Сайт на docsify `generateWEB: true` (по умолчанию). В выходе появляются `index.html`, sidebar с иерархией страниц и по `.md` на страницу. Сайт статический и не ходит в интернет: docsify, тема и плагины лежат в `vendor/` рядом с `index.html`, диаграммы — готовыми SVG или PNG. Шрифты темы — системные (без Google Fonts), emoji вроде `:smile:` — символами, а не картинками с GitHub, блоки ```` ```plantuml ```` рендерятся при сборке, а не в браузере через plantuml.com. Внешние адреса появляются только по явному выбору: `generateLocalImages: false` (картинки диаграмм — ссылками на `plantumlServerUrl`), своя `webTheme` или `docsifyTemplate` с ними, внешние картинки в тексте страниц. - Посмотреть локально — `c4builder site` (или `--site -w` с пересборкой), по умолчанию на `http://localhost:3000`. Открыть `index.html` двойным щелчком не получится: docsify загружает страницы запросами, нужен HTTP-сервер. - Опубликовать — скопировать папку выхода на любой статический хостинг: GitHub Pages, GitLab Pages, nginx. Сборка кладёт `.nojekyll`, чтобы GitHub Pages не прятал файлы с `_`. - Поиск по сайту — `supportSearch`, ссылка на репозиторий в углу — `repoUrl`, своя тема — `webTheme`, свой шаблон `index.html` — `docsifyTemplate`. Этот сайт — пример такого вывода: см. [Docker и CI](07%20Docker%20и%20CI/07%20Docker%20и%20CI.md), как он собирается. ## llms.txt для агентов `generateLLMS: true` (с 0.5.0, нужен сайт) кладёт в корень сайта `llms.txt` — оглавление со ссылками на `.md` страниц — и `llms-full.txt` — весь текст, где диаграммы вставлены исходниками. Подробно — на странице [Агенты](08%20Агенты/08%20Агенты.md). ## Markdown по папкам `generateMD: true` (по умолчанию). В каждой папке выхода — `README.md` со всем текстом и диаграммами страницы. Такой выход удобно коммитить: GitHub и GitLab показывают его как обычную документацию репозитория. Навигация вверху каждого файла: - `includeTableOfContents` (по умолчанию) — дерево всех страниц, текущая выделена; - `includeNavigation` — ссылка на родителя и список дочерних страниц. ## Один файл `generateCompleteMD: true` собирает всё в `.md` в корне выхода: оглавление, затем разделы по страницам, у каждого ссылка на начало. Подходит как единый README архитектуры или как вход для инструментов, которые читают один файл. ## Как выглядят диаграммы | Ключ | Что меняет | |---|---| | `diagramsOnTop` | диаграммы, которые не вставлены в текст явно, идут до текста (по умолчанию) или после | | `includeLinkToDiagram` | вместо картинки — ссылка на файл диаграммы | | `embedDiagram` | картинка встраивается в markdown base64-строкой, рядом ссылка на скачивание | | `includeBreadcrumbs` | под заголовком страницы — её путь в дереве | | `excludeOtherFiles` | не копировать в выход картинки и прочие файлы из исходников | | `diagramFormat` | `svg` (по умолчанию) или `png` | ## 05 CLI и конфигурация [Обзор](#C4-Builder) # CLI и конфигурация ## Команды | Команда | Что делает | |---|---| | `c4builder` | собирает проект в текущей папке; без конфига сначала запускает мастер | | `c4builder --new` | создаёт проект из шаблона в новой папке; с 0.5.0 — и `c4builder new` | | `c4builder --new --name demo -y` | то же без вопросов: полный конфиг со значениями по умолчанию | | `c4builder --config` | мастер настройки с текущими значениями; с 0.5.0 — и `c4builder config` | | `c4builder site` | собирает и раздаёт сайт локально (то же, что `--site`) | | `c4builder --site -w` | то же с пересборкой на каждое сохранение и перезагрузкой браузера | | `c4builder check <файлы...>` | проверяет диаграммы `.puml`, `.iuml`, `.d2`, `.bpmn` без сборки, код выхода 0 или 1 | | `c4builder jre info` | какая Java будет использована (JSON) | | `c4builder jre install [--force]` | скачать JRE в кэш заранее; `--force` — даже если есть системная Java | | `c4builder --list` | показать текущий конфиг; с 0.5.0 вне проекта — ошибка с кодом 1, файлов не создаёт | | `c4builder --reset` | очистить конфиг проекта; с 0.5.0 вне проекта — ошибка с кодом 1 | | `c4builder --docs` | краткая справка по ключам конфига и адрес этой документации | Позиционные команды — `check`, `jre`, `site`, а с 0.5.0 ещё `new` и `config`. С 0.5.0 любой другой позиционный аргумент — ошибка `unknown command` с кодом 1: опечатка (`c4builder nwe`) больше не запускает сборку. Флаги: | Флаг | Значение | |---|---| | `-c, --config-file <путь>` | другой файл конфига вместо `.c4builder` | | `-w, --watch` | пересобирать при изменениях в исходниках и папках плагинов | | `-o, --open` | открыть сайт в браузере (с `--site`) | | `-p, --port ` | порт для `--site` (по умолчанию `webPort` из конфига, 3000) | | `--system-fonts` | рендер системными шрифтами на один запуск | | `-V, --version` | версия | ## Как читается конфиг `.c4builder` — JSON в папке проекта. Первая сборка задаёт недостающие ключи через мастер и ставит `hasRun: true`; после этого `c4builder` собирает без вопросов. `new -y` сразу пишет полный конфиг, поэтому мастера не будет вовсе. Ключи проверяются схемой. Значение не того типа останавливает сборку с ошибкой, где назван ключ, а `c4builder --config` позволяет его исправить. Неизвестные ключи молча отбрасываются — так старый c4builder не падает на конфиге от нового. Ключи функций, удалённых из старых версий, дают предупреждение с их списком, сборка продолжается. ## Ключи `.c4builder` | Ключ | По умолчанию | Что задаёт | |---|---|---| | `projectName` | имя при создании | имя проекта: заголовок сайта и единого файла | | `homepageName` | `Overview` | заголовок корневой страницы | | `rootFolder` | `src` | папка исходников | | `distFolder` | `docs` | папка выхода, пересоздаётся при каждой сборке | | `generateWEB` | `true` | сайт на docsify | | `generateMD` | `true` | markdown по папкам | | `generateCompleteMD` | `false` | один файл `.md` | | `generateLLMS` | `true` в новых проектах, выкл. без ключа | `llms.txt` и `llms-full.txt` для агентов, нужен `generateWEB` (с 0.5.0) | | `includeTableOfContents` | `true` | дерево страниц вверху каждого markdown-файла | | `includeNavigation` | `false` | ссылки на родителя и дочерние страницы в markdown | | `includeBreadcrumbs` | `true` | путь страницы под заголовком | | `diagramsOnTop` | `true` | диаграммы до текста страницы | | `includeLinkToDiagram` | `false` | ссылка вместо картинки диаграммы | | `embedDiagram` | `false` | картинка в markdown base64-строкой | | `excludeOtherFiles` | `false` | не копировать прочие файлы из исходников | | `generateLocalImages` | `true` | локальный рендер; `false` — ссылки на онлайн-сервер | | `plantumlServerUrl` | `https://www.plantuml.com/plantuml` | сервер для `generateLocalImages: false` | | `diagramFormat` | `svg` | `svg` или `png` | | `d2Layout` | `dagre` | раскладка D2: `dagre` или `elk` | | `useSystemFonts` | `false` | системные шрифты вместо вендорного | | `charset` | `UTF-8` | кодировка исходников PlantUML | | `webTheme` | `vendor/vue.css` | CSS-тема docsify | | `supportSearch` | `true` | поиск в sidebar | | `repoUrl` | пусто | ссылка на репозиторий в углу сайта | | `executeScript` | `false` | выполнение `` (та же схема, что в шаблоне `--new`), без CDN. Имя страницы — родительская папка (в корне источника — имя файла); коллизии имён — ошибка. ## Risks / Trade-offs - [Виртуальные `dir` вне диска] → `generateImages` использует `item.dir` только для локальных инклюдов и путей вывода; при апгрейде ядра любой новый `fs`-доступ по `item.dir` ловится юнит-тестом «виртуальный элемент собирается без каталога на диске». - [Пост-инъекция в чужой HTML] → детерминированные точки вставки и тест на шаблон без ``; хуже сломать невозможно — при промахе теги в конце файла всё равно исполняются. - [Кэш архива в `os.tmpdir()`] → ключ по sha1(url), очистка не нужна (tmp), в CI раннер одноразовый; параллельные сборки одного URL пишут в один каталог — распаковка в `.tmp` + rename. - [Секреты в `${ENV}`] → значения заголовков не логируются; в вывод сборки не попадают. - [swagger-ui.css вендорится] → +~150 КБ в пакете; whitelist `files` уже включает `vendor/`. - [Рост архива OpenSpec → длинный sidebar] → sidebar-collapse на стороне arch уже стоит; группировка по годам — когда понадобится (в спеке не фиксируем). - [`.strict()` у встроенных плагинов ломает конфиг при переименовании опции] → переименования через deprecated-алиас с предупреждением, как с легаси-ключами конфига. ## Migration Plan 1. Релиз `rc` c4builder → образ `:rc` (арх-репы на нём). 2. В арх-репо: `plugins` в `.c4builder` (openspec — `dir: openspec`; openapi — `archive` на репозиторий контрактов, `glob: */openapi.yaml`). Заодно убрать легаси-ключи `plantumlVersion`/`generatePDF*`. 3. Проверка через MR-preview Pages; после — обычный релиз `v*`. 4. Откат: вернуть образ на `:latest` — ключ `plugins` игнорируется, сайт собирается без разделов. ## Open Questions - Нужен ли на сводке OpenSpec блок ссылок ADR ↔ change (обратный индекс по «Основание: ADR-NNN»)? Не влияет на API и разбиение задач — можно добавить отдельным change'ом. ## tasks [Обзор](#C4-Builder) ## 1. Конфиг и контракт плагина - [x] 1.1 `config/schema.ts`: ключ `plugins` — массив `string | [string, Record]`, дефолт `[]`; ошибка формы с путём ключа; `BuildOptions`/`getOptions` пробрасывают сырой список - [x] 1.2 `core/plugins/types.ts`: `definePlugin`, типы `Plugin`, `ScanCtx`, `BuildCtx`, `PageSpec` (сегменты пути, markdown, диаграммы), `SourceSpec` (`dir` | `archive`+`subdir`+`headers`) - [x] 1.3 `core/plugins/load.ts`: резолв идентификатора (встроенный → путь от cwd → npm через `createRequire`), `import()`, подстановка `${ENV}` в опциях, `safeParse` схемы с сообщением `plugins[i] : — `, применение `requires.executeScript` с логом - [x] 1.4 Тесты `test/plugins-load.test.mjs`: строка = пара, ошибка формы, неизвестный ключ у строгой схемы, плагин без схемы, подстановка окружения, несуществующий модуль ## 2. Виртуальные страницы и хуки в сборке - [x] 2.1 `core/plugins/tree.ts`: `addPage()` — вычисление `dir`/`level`/`parent`, обновление `descendants`, вставка в конец поддерева родителя (DFS), автосоздание промежуточных узлов-оглавлений, ошибка при коллизии с реальной папкой - [x] 2.2 `core/build.ts`: третий аргумент `plugins`; вызов `afterScan` после `generateTree` (по порядку, ошибка с именем плагина), `afterBuild` после выходов; `dispatch.ts` грузит плагины до `build()` и передаёт их - [x] 2.3 Проверить `render/diagrams.ts` на виртуальном `item.dir` (инклюды, пути вывода, checksum-восстановление из бэкапа) — без `fs`-доступа к несуществующему каталогу - [x] 2.4 Тесты `test/plugins-tree.test.mjs`: виртуальный раздел в `_sidebar.md` и страницах сайта, в `generateMD`/complete, диаграмма виртуальной страницы рендерится (PlantUML на stdlib), порядок DFS, ошибка хука прерывает сборку ## 3. Ассеты, watch, источники - [x] 3.1 `core/plugins/assets.ts`: копирование `assets` в `dist/vendor/plugins//`, пост-инъекция ``/`