Keep a LikeC4 architecture model that describes the code as it is, the way as-built drawings describe a building as constructed rather than as designed.
asbuilt survey reads a code base and writes the .c4 model: crates as
containers, modules as components, module-to-module references as
relations labeled with the item names they reference, and the metadata
that ties every element to a path in the tree. Nothing in the model is
hand-written and nothing in the code is annotated. asbuilt check surveys
again and fails when the committed model no longer matches, so it runs as
a pre-commit hook and as a CI step. Validation, export, and rendering are
LikeC4's; asbuilt shells out to a pinned npx likec4 for those and
reimplements none of it.
The core is language-agnostic, with one front-end per language. The Rust
front-end (asbuilt-rust) ships first. It is implemented in Rust so it
installs as one static binary that a pre-commit hook in any repo can
call, and so later front-ends can use Rust-native parsers without a Node
or Python runtime present.
The landing site is at https://padamson.github.io/asbuilt/, versioned
per release, and every snapshot carries this repo's own architecture
documentation under architecture/: asbuilt docs over the model that
asbuilt survey wrote for this very code base, with the views LikeC4
rendered. The site is built, driven with playwright-rs, and deployed by
.github/workflows/pages.yml (docs/versioned-site.md).
0.3.1. The model, the Rust front-end, survey, check, the LikeC4
wrappers, and docs are in use on this repo and on
playwright-rust. 0.3.1
fixes the action's Graphviz install: on Ubuntu 24.04 (amd64) it now
installs Graphviz's own 15.1.1 package rather than apt's 2.43, which
fails to lay out some views.
0.3.0 added a release pin in asbuilt.toml, a GitHub Action that
installs the pinned release and checks, a check that names what
changed (and speaks JSON), and relation kinds you can tell apart, with
a legend in every diagram. Before 1.0 a minor version may change the
model's shape or the CLI, and CHANGELOG.md names every such change;
0.3.0 changed every model's specification block, so re-run asbuilt survey after upgrading from 0.2.
cargo install asbuilt
cargo binstall asbuilt # the release's prebuilt binary, no compileEach GitHub release also carries the archives directly: Linux
(static for x86_64 and aarch64, and a glibc x86_64 build), macOS, and
Windows, each with a
build-provenance attestation (gh attestation verify <archive> --repo padamson/asbuilt). To try what is on main before it is released, cargo install --git https://github.com/padamson/asbuilt asbuilt.
survey and check need only cargo. validate, export json,
render, and docs shell out to npx likec4 (Node) at the release
the badge above names, and
render and docs also need Graphviz dot; docs --no-render
reuses SVGs already rendered and needs neither.
asbuilt survey # writes docs/architecture/model.c4
asbuilt check # exit 1 with a diff, naming what changed, when the model is stale
asbuilt check --format json # the same as one JSON object
asbuilt validate # likec4 validate over the model directory (and curated views beside it)
asbuilt export json # docs/architecture/model.json, machine-independent
asbuilt render # one SVG per view under docs/architecture/views/
asbuilt docs # a static HTML tree under docs/architecture/site/, embedding those SVGsEvery subcommand takes an optional root (the current directory by
default) and --config <path>. Exit codes: 0 done or current, 1 drift or
an invalid model, 2 anything else, with the file or id in the message.
One container per crate; one component per module, nested as in the
code; a tests, an examples, and a bin element per crate that has
them (each a kind of its own, so [theme] colors them apart); and one
relation per pair of modules, with the referenced item names as the
label and the strongest evidence as the kind (implements,
constructs, calls, names, or uses), each drawn with its own line
and arrowhead.
Descriptions come from the first paragraph of
each module's //! doc. Paths resolve through pub use chains and glob
re-exports to the defining module; an edge from a module to its own
ancestor or descendant is never recorded. A macro's body counts when it
parses as Rust (assert_eq!, vec!, fuzz_target!); method calls on
values are not resolved.
Optional asbuilt.toml at the root:
asbuilt = "0.3.1" # the release the model is surveyed by
[output]
path = "docs/architecture/model.c4"
[docs]
title = "playwright-rust"
source_url = "https://github.com/padamson/playwright-rust/blob/main/"
home_url = "../" # a link back to the hosting site on every page
[theme]
container = "#f0a884" # the diagrams in your palette, per element kind
[rust]
extra_manifests = ["crates/site-e2e/Cargo.toml"] # crates in the repo but outside the workspace
include_tests = true
include_examples = true
[[externals]] # what the code cannot state
id = "node_driver"
kind = "process"
title = "Playwright driver"
technology = "Node.js process"
[[externals.relations]]
from = "playwright_rs.server.playwright_server"
title = "spawns"
technology = "stdio"asbuilt pins the release: every command run by another one stops
before reading anything else and names the cargo install line for the
pinned one, so a contributor with an older or newer binary is not shown
its output as drift. It is a top-level key, so it goes above the first
table. A from that names no generated element fails the survey, so a
typo is an error rather than a missing edge. [docs] title and source_url
name the documentation tree and turn its paths into links;
home_url, home_title, and stylesheet fit it into a host site.
docs replaces only the files an earlier run wrote, and refuses to
overwrite tree files it did not write unless --force. [theme]
colors each element kind in the rendered diagrams, with a light and a
dark color the docs pages switch between; a visitor can pick System,
Light, or Dark in the header, and [docs] color_scheme and
scheme_toggle set the default or leave the control out. Every
diagram sits in a frame at a readable scale with Fit, 1:1, Wide,
Legend, and Fullscreen controls, zooms with the wheel and Ctrl or ⌘ (or
a pinch) and pans by drag, and carries a legend of the element and
relation kinds it draws; [docs] viewer = false (or --no-viewer) leaves
the viewer and its script out. Curated views go in
a sibling .c4 file that references generated ids; asbuilt validate
catches a stale one. Rendered SVGs and the docs tree are build output:
commit model.c4 and the curated views, and ignore the rest.
# .pre-commit-config.yaml (pre-commit or prek)
repos:
- repo: https://github.com/padamson/asbuilt
rev: v0.3.1
hooks:
- id: asbuilt-checkThe hook runs the asbuilt on the PATH, so install the release rev
names; with the release pinned in asbuilt.toml, any other one stops
with the install line instead of reporting drift. It runs when a .rs
file, a Cargo.toml, asbuilt.toml, or anything under
docs/architecture/ changes; a model kept elsewhere ([output] path)
sets files: on the hook to include its directory.
In CI, one step:
- uses: padamson/asbuilt@v0.3.1The action reads the release from the pin in asbuilt.toml (or its
version input), installs that release's archive only once gh attestation verify says asbuilt's release workflow built it from that
tag (a release with no archive for the runner is built with cargo install instead, and the run says so), and runs asbuilt check.
command: runs any other subcommand instead (docs -o site writes the
documentation tree), installing Graphviz for render and docs when
the runner lacks it; command: '' installs only. A newer release is a notice in the run, never a failure. Run it
on every platform the code builds on: the survey is byte-identical
across them.
The asbuilt mark (a telescope sighting over a graduated arc) and the
AS/BUILT wordmark live in brand/, with the fonts they need
and a VERSION a copy records. A site that links to asbuilt can copy
the directory whole; its README says how each piece is used.
npx skills add padamson/asbuiltWorks with Claude Code,
Codex, Cursor, and any
other compatible agent. The install copies
the skill into your repo and records its source in skills-lock.json.
See CLAUDE.md for development commands.
- Rust toolchain (MSRV: 1.88)
- prek for pre-commit hooks:
cargo install prek && prek install - Node (for
npx likec4) and Graphvizdot, only for the tests and commands that validate or render a model
cargo build --workspace
cargo nextest run --workspaceLicensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.