# C4-Builder * [Overview](#Overview) * [01 Getting started](#01-Getting-started) * [02 Project](#02-Project) * [03 Diagrams](#03-Diagrams) * [04 Outputs](#04-Outputs) * [05 CLI and configuration](#05-CLI-and-configuration) * [06 Plugins](#06-Plugins) * [07 Docker and CI](#07-Docker-and-CI) * [08 Agents](#08-Agents) * [09 Development](#09-Development) * [Design log](#Design-log) * [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) --- ## Overview # C4-Builder **Architecture as code that an agent writes, checks and reads.** C4-Builder is a CLI that builds a folder of markdown files and PlantUML, D2 or BPMN diagrams into documentation: a docsify site, a markdown file per folder, or a single file. Folders define the hierarchy of the [C4 model](https://c4model.com/) (system → container → component), and git keeps the history of architecture decisions next to the code: a change to the architecture is reviewed in a pull request like any other change. This site is built by c4builder itself from the `docs/` folder of the repository; its diagrams are rendered during the CI build. ```plantuml @startuml !include !include styles.iuml LAYOUT_TOP_DOWN() Person(dev, "Developer", "Writes markdown and diagrams, reviews architecture in pull requests") Person(agent, "AI agent", "Claude Code, Codex, Cursor: edits the model and verifies it with c4builder check") System(c4b, "C4-Builder", "CLI: builds a folder of markdown + PlantUML/D2 into documentation, checks individual diagrams") System_Ext(engines, "Diagram engines", "PlantUML (bundled jar + Java, Smetana), D2 (WASM)") System_Ext(openspec, "OpenSpec store", "Project specs and changes: proposal, design, tasks") System_Ext(openapi, "OpenAPI contracts", "Service specs: a folder or a repository archive") System_Ext(site, "Docsify site", "Static, no CDN: GitHub Pages, GitLab Pages, any web server") System_Ext(md, "Markdown", "A README per folder or a single file for the repository") Rel(dev, c4b, "Builds and previews", "c4builder --site -w") Rel(agent, c4b, "Checks edits", "c4builder check") Rel(c4b, engines, "Renders diagrams", "locally, offline") Rel(c4b, openspec, "Publishes a section", "openspec plugin") Rel(c4b, openapi, "Publishes swagger-ui", "openapi plugin") Rel(c4b, site, "Builds") Rel(c4b, md, "Builds") SHOW_LEGEND() @enduml ``` ## How this fork differs from upstream The original C4-Builder is no longer developed: its last commit dates from March 2024, and the last npm release of `c4builder` (0.2.16) is from 2022. At Alfa-Capital it builds an architecture repository of more than a hundred diagrams, so we continue it as a maintained fork and adapt it to working with AI agents: - **Deterministic offline rendering.** The PlantUML jar, the font and the engines ship with the package, layout is computed by Smetana (the engine built into PlantUML), Graphviz is not needed. The same sources produce the same SVG on a laptop, in CI and in Docker, and the build needs no network. - **`c4builder check` gives feedback in seconds.** It checks individual diagrams with the same engine as the build and returns an exit code and the line of the error. An agent edits a `.puml` and immediately knows whether it broke something — without a human and without a full build. - **Plugins.** `openspec` publishes specs and changes next to the C4 model, `openapi` publishes service contracts as offline swagger-ui pages. - **D2** as a second diagram engine, PNG via resvg, Java is optional: c4builder downloads a JRE if there is none. - **BPMN processes** as a third engine: the agent writes BPMN 2.0 XML without coordinates, the build checks the model and lays it out with horizontal pools and lanes. - **Engineering basics.** TypeScript, golden snapshots of the rendering, CI on every pull request, an npm package and a Docker image per release tag. The full list of changes is in the [CHANGELOG](https://github.com/AlfaCapital-Tech/C4-Builder/blob/master/CHANGELOG.md). ## The agent loop 1. An agent (or you) edits `.md` and `.puml` files in `src/`. 2. `c4builder check ` tells whether the diagram compiles and shows the line of the error. 3. `c4builder --site -w` rebuilds the site on every save and reloads the browser. Agent skills, `AGENTS.md` in a new project and `llms.txt` with diagram sources are on the [Agents](08%20Agents/08%20Agents.md) page. ## Sections - [Getting started](01%20Getting%20started/01%20Getting%20started.md) — installation and the first project. - [Project](02%20Project/02%20Project.md) — how folders and files become pages. - [Diagrams](03%20Diagrams/03%20Diagrams.md) — PlantUML, D2, BPMN, fonts, PNG, checking. - [Outputs](04%20Outputs/04%20Outputs.md) — site, markdown per folder, single file. - [CLI & configuration](05%20CLI%20and%20configuration/05%20CLI%20and%20configuration.md) — commands and `.c4builder` keys. - [Plugins](06%20Plugins/06%20Plugins.md) — `openspec`, `openapi`, writing your own. - [Docker & CI](07%20Docker%20and%20CI/07%20Docker%20and%20CI.md) — the image and pipeline builds. - [Development](09%20Development/09%20Development.md) — how c4builder is built and released. - **Design log** — the OpenSpec store of this repository: why c4builder works the way it does. > **The Design log is in Russian.** Its pages are the project's own OpenSpec artifacts > (proposals, designs, specs, tasks), which the team writes in Russian; they are published as > they are, without translation. Code, identifiers and commands in them are in English. ## Credits C4-Builder was created by Adrian Victor Lupu and is distributed under the MIT license. The fork keeps the license, the authorship and the history: upstream changes with links to their original pull requests are listed in the CHANGELOG. ## 01 Getting started [Overview](#C4-Builder) # Getting started ## Installation ```bash npm i -g @alfacapital-tech/c4builder ``` Node.js 22.17 or newer is required. `npm install` without a tag installs the latest stable release. Rendering PlantUML needs Java 17+. If `java` is available in `JAVA_HOME` or on `PATH`, c4builder uses it; otherwise the first build downloads a private JRE (Temurin) into its cache. Graphviz is not needed: layout is computed by Smetana, the engine built into PlantUML. The PlantUML jar and the diagram font are part of the package. ```bash c4builder jre info # which Java will be used (JSON: version, cache path) c4builder jre install # download the JRE in advance: before going offline or into a CI image ``` If you would rather not install Node and Java, use the [Docker image](07%20Docker%20and%20CI/07%20Docker%20and%20CI.md) — it has everything. ## First project ```bash c4builder --new --name demo -y # project from the template with a full config, no questions cd demo c4builder # build into docs/ c4builder site # site at http://localhost:3000 ``` Without `-y`, c4builder asks for the project name, and the first build walks you through the setup wizard. Since version 0.5.0 the shorter `c4builder new --name demo -y` works too; 0.4.0 has no positional form, only the `--new` flag. The new project contains the "Internet Banking System" demo from the C4 model: context, containers, deployment, a dynamic diagram, sequence, class and ditaa diagrams, a page in Cyrillic a D2 example and the "Account opening" BPMN process. All C4 diagrams include the library through the stdlib (`!include `): C4-PlantUML is built into the jar, so the build needs no internet. Next to `src/` there is `AGENTS.md` — instructions for AI agents on building and checking the diagrams (since 0.5.0; it does not end up in the documentation). ## Live preview ```bash c4builder --site -w ``` Builds the project, starts a local server and rebuilds the site on every save in `src/`. The browser reloads by itself and keeps the scroll position. `-o` opens the browser, `-p 8080` changes the port. ## Changing the settings `c4builder --config` (since 0.5.0 also `c4builder config`) runs the wizard again with the current values as defaults; `c4builder --list` prints the config. `.c4builder` is plain JSON and can be edited by hand: every key is described on the [CLI & configuration](05%20CLI%20and%20configuration/05%20CLI%20and%20configuration.md) page. ## Release candidates Pre-releases `X.Y.Z-rc.N` are published under the npm tag `rc` and never touch `latest`: ```bash npm i -g @alfacapital-tech/c4builder@rc ``` To go back to the stable version, install the package with the `latest` tag. ## 02 Project [Overview](#C4-Builder) # Project A c4builder project is a folder with a `.c4builder` file. It has two main folders: the **sources** (`src`, key `rootFolder`) and the **output** (`docs`, key `distFolder`). Run every command from the project folder: paths in the config are relative to it. ## Folder = page Every folder inside the sources is one documentation page. Following the C4 model, it is usually a system, a container or a component, but any topic works: ADRs, a glossary, a runbook. ```text demo/ ├── .c4builder project config ├── docs/ output: recreated on every build └── src/ sources ├── context.md root = home page ├── context.puml ├── styles.iuml shared diagram theme (pulled in with !include) └── 1 Internet Banking System/ ├── system.md ├── system.puml └── API Application/ ├── container.md └── container.puml ``` The build walks the tree and merges all `.md` files and diagrams of a folder into one page: - **Page title** — the folder name. If the first `.md` starts with its own `# Heading`, that heading is used instead. - **Page order** — file system order. To set it explicitly, use numeric prefixes: `1 Context`, `2 Containers`. The prefix is visible in the sidebar title. - **Diagrams** are inserted as images. By default they go before the page text; with `diagramsOnTop: false`, after it. - **A diagram can be placed anywhere in the text** by linking to its source: `![containers](container.puml)`. Such a diagram is not repeated in the common block. - **Files and folders prefixed with `_`** do not become pages and are not rendered on their own, but they are available to `!include` and D2 imports. This is the place for shared libraries: `_c4lib.d2`, `_common.iuml`. - **Other files** (images, attachments) are copied to the output next to the page unless `excludeOtherFiles` is on. Put an image next to the `.md` that references it. ## Service files | File | Purpose | In git | |---|---|---| | `.c4builder` | project config | yes | | `.c4builder.cache` | diagram checksums: unchanged diagrams are not rendered again | no | | `.c4builder.lock` | build lock: two builds in one folder never write the output at the same time | no | | `docs_bk/` | copy of the previous output during a build; kept as a fallback if the build fails | no | c4builder deletes and recreates the output folder on every build, so do not keep your own files there. Whether to commit the output depends on the format: markdown per folder is convenient to keep in the repository, a site is usually built in CI. ## 03 Diagrams [Overview](#C4-Builder) # Diagrams The engine is chosen by file extension: `.puml` is rendered by PlantUML, `.d2` by D2, `.bpmn` by the BPMN engine. The formats can live in one project and even in one folder, as long as the file names differ. ## PlantUML c4builder runs the bundled PlantUML jar directly with `java`; layout is computed by the built-in Smetana engine. There is no Graphviz to install. The jar version is pinned in the package, so updating PlantUML on a developer machine does not change the build. **C4 without internet.** The C4-PlantUML library is built into the jar; include it through the stdlib: ```puml @startuml !include !include ../styles.iuml Person(user, "Customer") System(bank, "Internet banking") Rel(user, bank, "Uses", "HTTPS") @enduml ``` **Shared styles** go into an `.iuml`: `skinparam`, `UpdateElementStyle(...)`, your own macros. Include it after the C4 include with a relative path. Paths in `!include` are relative to the diagram's folder, not to the project root. **Cyrillic** works in labels, descriptions and folder names: the diagram font contains it. **ditaa** (`@startditaa`) is supported too and is always rendered as PNG. ## Diagrams inside a page A ```` ```plantuml ```` block in an `.md` page is rendered at build time just like a `.puml` file: the site, the markdown collection and the complete document get an image in its place, not the block text. `@startuml`/`@enduml` may be omitted, `!include` paths are relative to the page's folder. A broken block does not fail the build: the image is replaced by a placeholder and the log shows a warning with the `.md` file path. To show diagram source as code, mark the block `puml` or `text`: such blocks stay code, and so do ```` ```d2 ```` blocks. ## Determinism and the font Diagrams are drawn with the bundled Nimbus Sans font (Helvetica metrics, Cyrillic, Greek) rather than a system font. The same diagram therefore has identical geometry on macOS, Linux, in CI and in Docker, and the SVG diff in a pull request shows only real changes. `useSystemFonts: true` in `.c4builder` (or `--system-fonts` for one run) switches to system fonts. The look becomes more familiar, but the result depends on the machine. ## Render cache Unchanged diagrams are not rendered again: c4builder keeps checksums in `.c4builder.cache`. A checksum covers the source, all local `!include`s and D2 imports, the output path and the render settings, plus the engine package versions for D2 and BPMN. Editing a shared `styles.iuml` re-renders every diagram that includes it, and only those. ## D2 [D2](https://d2lang.com) is rendered by the WASM build of the engine right inside Node — no external binaries. A markdown reference looks the same as for PlantUML: `![overview](landscape.d2)`. - The engine is an optional dependency, `@terrastruct/d2` (~60 MB). npm installs it together with c4builder, and it is loaded only when the project contains a `.d2`. If the package is missing (for example, installed with `--omit=optional`), the build stops with the hint `npm install @terrastruct/d2`. - [Imports](https://d2lang.com/tour/imports) `@file` and `...@file` are resolved by c4builder itself, no network needed. Prefix a shared class library with `_` (`_c4lib.d2`) so it is not rendered as a diagram of its own: `...@../_c4lib`. - Layout is set by the `d2Layout` key: `dagre` (default) or `elk`. ## BPMN `.bpmn` is a business process in BPMN 2.0 notation: events, tasks, gateways, horizontal pools and lanes, message flows between participants. A markdown reference is the usual one: `![process](process.bpmn)`. **Only the semantics are written.** The file is standard BPMN 2.0 XML: a `collaboration` with participants (pools) and message flows, a `process` with a `laneSet`, nodes and sequence flows. Coordinates (the `bpmndi:BPMNDiagram` section) are not needed, and if present they are ignored: layout is always done by the build. `incoming`/`outgoing` elements are optional, connections are taken from `sourceRef`/`targetRef`. ```xml ``` **Example** — the account opening process from the `c4builder new` template: two pools, lanes, messages to the credit bureau, a timer escalation. The file holds semantics only; this build did the layout and rendering: ```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 ``` **Layout** is done by [bpmn-auto-layout](https://github.com/bpmn-io/bpmn-auto-layout): pools and lanes as horizontal bands labelled on the left, flow from left to right, every node inside the band of its lane. Sub-processes are drawn collapsed — put their details into a separate `.bpmn`. The layouter is still alpha: if it loses a model element, the build stops with an error listing the element ids instead of publishing a truncated diagram. A bad layout cannot be fixed with manual coordinates — simplify the model or split it into an overview and details. **Rendering** uses [bpmn-js](https://bpmn.io) in the standard notation, without a browser or network. Text is measured with the bundled Nimbus Sans, so the SVG is the same on any machine (`useSystemFonts` does not affect BPMN); PNG comes from the regular rasterization. **Model checks** run before layout — both in the build and in `c4builder check`. The rule set is fixed: the recommended [bpmnlint](https://github.com/bpmn-io/bpmnlint) rules (except `no-bpmndi`) plus the c4builder rules: | Rule | Level | Checks | |---|---|---| | `no-disconnected`, `start-event-required`, `end-event-required`, `label-required`, … | error | bpmnlint recommended set | | `c4builder/lane-membership` | error | in a process with lanes, every node is in exactly one lane | | `c4builder/sequence-flow-in-pool` | error | a sequence flow does not cross a pool boundary | | `c4builder/message-flow-between-pools` | error | a message flow connects different pools | | `c4builder/gateway-flow-labels` | warning | branches of an XOR/inclusive gateway are labelled | An error stops rendering of the diagram, a warning is only printed. Each violation is a line of its own with the element id and the rule name: ```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 ``` **Conventions:** a pool is an organization (an external one is a pool without a process, a "black box"), a lane is a role or a unit inside it; only message flows go between pools; gateway branches are labelled; instead of a 40+ node diagram — an overview process plus separate `.bpmn` files for the details. **Dependencies.** The engine packages (`bpmn-js`, `bpmn-moddle`, `bpmnlint`, `bpmn-auto-layout`, `jsdom`) are optional: npm installs them together with c4builder, and they are loaded only when the project contains a `.bpmn`. Installing with `--omit=optional` keeps only the core — building a project with `.bpmn` then stops with a hint to reinstall c4builder without that flag. ## PNG With `diagramFormat: png`, c4builder renders SVG and rasterizes it with [resvg](https://github.com/thx/resvg-js) using the same bundled font. The PNG is consistent for PlantUML, D2 and BPMN and needs no browser. ditaa stays a native PlantUML PNG. ## Online PlantUML server With `generateLocalImages: false`, diagrams are not rendered locally: the output links to the server from `plantumlServerUrl` (default `https://www.plantuml.com/plantuml`). This means diagram sources go to an external server, and D2 and BPMN do not work in this mode — the build stops with an error. The mode is kept for compatibility; local rendering is the main one. ## Checking individual diagrams ```bash c4builder check src/context.puml "src/4 D2 Example/landscape.d2" ``` `check` validates `.puml`, `.iuml`, `.d2` and `.bpmn` files with the same engine as the build (for `.bpmn`: parsing, rules, layout and rendering), but writes nothing and needs no project or `.c4builder`. Exit code 0 means every file compiles, 1 means there is an error (warnings do not affect the code): ```text ✓ src/context.puml ✗ src/system.puml: line 4: Fatal parsing error ``` `!include` is resolved from the folder of the checked file. An `.iuml` library is checked by including it into an empty diagram, and an `.iuml` with C4 macros (`UpdateElementStyle` and the like) — in the context of the C4 stdlib, as in the diagrams that include it. No line number is printed for an `.iuml`. A pre-commit hook that checks staged diagrams (`.git/hooks/pre-commit`; spaces in paths are fine): ```bash #!/bin/sh git diff --cached --name-only -z --diff-filter=ACM -- '*.puml' '*.iuml' '*.d2' '*.bpmn' | xargs -0 -r c4builder check ``` ## 04 Outputs [Overview](#C4-Builder) # Outputs Output formats are switched on independently, in any combination: the wizard asks about them on the first build, and in `.c4builder` they are `generateMD`, `generateCompleteMD` and `generateWEB`. The output mirrors the structure of the sources. ## Docsify site `generateWEB: true` (default). The output gets an `index.html`, a sidebar with the page hierarchy and one `.md` per page. The site is static and does not go to the internet: docsify, the theme and the plugins sit in `vendor/` next to `index.html`, diagrams are ready-made SVG or PNG files. Theme fonts are system fonts (no Google Fonts), emoji such as `:smile:` are shown as characters rather than images from GitHub, and ```` ```plantuml ```` blocks are rendered at build time, not in the browser via plantuml.com. External addresses appear only by explicit choice: `generateLocalImages: false` (diagram images link to `plantumlServerUrl`), a custom `webTheme` or `docsifyTemplate` that uses them, external images in page text. - To view it locally, run `c4builder site` (or `--site -w` to rebuild on changes), by default at `http://localhost:3000`. Double-clicking `index.html` will not work: docsify loads pages with requests and needs an HTTP server. - To publish it, copy the output folder to any static hosting: GitHub Pages, GitLab Pages, nginx. The build writes `.nojekyll` so GitHub Pages does not hide files starting with `_`. - Site search is `supportSearch`, the repository link in the corner is `repoUrl`, a custom theme is `webTheme`, a custom `index.html` template is `docsifyTemplate`. This site is an example of such an output: see [Docker & CI](07%20Docker%20and%20CI/07%20Docker%20and%20CI.md) for how it is built. ## llms.txt for agents `generateLLMS: true` (since 0.5.0, needs the site) puts `llms.txt` — an index of links to the pages' `.md` files — and `llms-full.txt` — the full text with diagrams inserted as source — into the site root. Details are on the [Agents](08%20Agents/08%20Agents.md) page. ## Markdown per folder `generateMD: true` (default). Each output folder gets a `README.md` with all the text and diagrams of the page. This output is convenient to commit: GitHub and GitLab render it as regular repository documentation. Navigation at the top of each file: - `includeTableOfContents` (default) — the tree of all pages with the current one highlighted; - `includeNavigation` — a link to the parent and a list of child pages. ## Single file `generateCompleteMD: true` puts everything into `.md` at the output root: a table of contents followed by a section per page, each with a link back to the top. It works as a single architecture README or as input for tools that read one file. ## How diagrams appear | Key | What it changes | |---|---| | `diagramsOnTop` | diagrams not placed in the text explicitly go before the text (default) or after it | | `includeLinkToDiagram` | a link to the diagram file instead of the image | | `embedDiagram` | the image is embedded into markdown as a base64 string, with a download link next to it | | `includeBreadcrumbs` | the page's path in the tree under its title | | `excludeOtherFiles` | do not copy images and other files from the sources to the output | | `diagramFormat` | `svg` (default) or `png` | ## 05 CLI and configuration [Overview](#C4-Builder) # CLI & configuration ## Commands | Command | What it does | |---|---| | `c4builder` | builds the project in the current folder; without a config it runs the wizard first | | `c4builder --new` | creates a project from the template in a new folder; since 0.5.0 also `c4builder new` | | `c4builder --new --name demo -y` | the same without questions: a full config with default values | | `c4builder --config` | setup wizard with the current values; since 0.5.0 also `c4builder config` | | `c4builder site` | builds and serves the site locally (same as `--site`) | | `c4builder --site -w` | the same, rebuilding on every save and reloading the browser | | `c4builder check ` | checks `.puml`, `.iuml`, `.d2`, `.bpmn` diagrams without a build, exit code 0 or 1 | | `c4builder jre info` | which Java will be used (JSON) | | `c4builder jre install [--force]` | download a JRE into the cache in advance; `--force` — even if system Java exists | | `c4builder --list` | print the current config; since 0.5.0 outside a project — an error with exit code 1, no files created | | `c4builder --reset` | clear the project config; since 0.5.0 outside a project — an error with exit code 1 | | `c4builder --docs` | short reference of config keys and the address of this documentation | Positional commands are `check`, `jre`, `site`, and since 0.5.0 also `new` and `config`. Since 0.5.0 any other positional argument is an `unknown command` error with exit code 1: a typo (`c4builder nwe`) no longer starts a build. Flags: | Flag | Meaning | |---|---| | `-c, --config-file ` | a different config file instead of `.c4builder` | | `-w, --watch` | rebuild on changes in the sources and plugin folders | | `-o, --open` | open the site in the browser (with `--site`) | | `-p, --port ` | port for `--site` (defaults to `webPort` from the config, 3000) | | `--system-fonts` | render with system fonts for one run | | `-V, --version` | version | ## How the config is read `.c4builder` is JSON in the project folder. The first build asks for missing keys through the wizard and sets `hasRun: true`; after that, `c4builder` builds without questions. `new -y` writes a full config right away, so there is no wizard at all. Keys are validated against a schema. A value of the wrong type stops the build with an error naming the key, and `c4builder --config` lets you fix it. Unknown keys are silently dropped — this way an older c4builder does not fail on a config written by a newer one. Keys of features removed in older versions produce a warning listing them, and the build continues. ## `.c4builder` keys | Key | Default | What it sets | |---|---|---| | `projectName` | name given at creation | project name: title of the site and of the single file | | `homepageName` | `Overview` | title of the root page | | `rootFolder` | `src` | sources folder | | `distFolder` | `docs` | output folder, recreated on every build | | `generateWEB` | `true` | docsify site | | `generateMD` | `true` | markdown per folder | | `generateCompleteMD` | `false` | single `.md` file | | `generateLLMS` | `true` in new projects, off when absent | `llms.txt` and `llms-full.txt` for agents, needs `generateWEB` (since 0.5.0) | | `includeTableOfContents` | `true` | page tree at the top of each markdown file | | `includeNavigation` | `false` | links to the parent and child pages in markdown | | `includeBreadcrumbs` | `true` | page path under the title | | `diagramsOnTop` | `true` | diagrams before the page text | | `includeLinkToDiagram` | `false` | a link instead of the diagram image | | `embedDiagram` | `false` | image embedded in markdown as base64 | | `excludeOtherFiles` | `false` | do not copy other files from the sources | | `generateLocalImages` | `true` | local rendering; `false` — links to an online server | | `plantumlServerUrl` | `https://www.plantuml.com/plantuml` | server for `generateLocalImages: false` | | `diagramFormat` | `svg` | `svg` or `png` | | `d2Layout` | `dagre` | D2 layout: `dagre` or `elk` | | `useSystemFonts` | `false` | system fonts instead of the bundled one | | `charset` | `UTF-8` | encoding of PlantUML sources | | `webTheme` | `vendor/vue.css` | docsify CSS theme | | `supportSearch` | `true` | search in the sidebar | | `repoUrl` | empty | repository link in the corner of the site | | `executeScript` | `false` | run `` (та же схема, что в шаблоне `--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 [Overview](#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//`, пост-инъекция ``/`