Linting C4 architecture diagrams

Enforce the “declare your level” convention from Simon Brown’s C4 model across every diagram doc in a ctxgrd-enabled repository.

What ctxgrd checks – and what it does not

ctxgrd checks the markdown envelope: that a diagram doc has a non-empty title and a c4.level drawn from the configured allowlist. It does not parse the embedded diagram graph, validate Mermaid/Graphviz syntax, or evaluate whether the diagram’s content matches its declared level. Diagram quality – the right zoom, labelled relationships, a title that names the system – is the author’s job, not a structural rule.

Prerequisites

  • ctxgrd 0.48.0 or later.
  • A ctxgrd.toml in the repository root. If none exists, run ctxgrd pack add project-docs to create a baseline.

1. Add the pack

ctxgrd pack add c4

This writes a [C4] block into ctxgrd.toml, path-claiming docs/diagrams/**. To preview what would be written without changing anything:

ctxgrd pack add c4 --dry-run

2. Author a diagram doc

Create a markdown file under docs/diagrams/. The filename is the diagram’s slug – there is no id: field. Every diagram doc requires two frontmatter fields: a title and a c4 object containing a level.

Here is a minimal System Context diagram, the natural first view to draw in any C4 model:

---
title: System Context -- Payment Service
c4:
  level: context
---

# System Context -- Payment Service

A customer submits a payment through the web app. The Payment Service processes
it via the external payment gateway and records the result.

` ``mermaid
C4Context
  Person(customer, "Customer", "Places an order")
  System(payment, "Payment Service", "Processes card payments")
  System_Ext(gateway, "Payment Gateway", "Stripe")

  Rel(customer, payment, "Submits payment", "HTTPS")
  Rel(payment, gateway, "Charges card", "HTTPS / REST")
` ``

Run the linter to confirm the doc is clean:

ctxgrd

A missing title or absent c4.level produces a c4.frontmatter diagnostic pointing to the offending file. A level outside the allowlist produces the same code with a message naming the invalid value and the accepted set.

3. Understand the levels

The pack ships seven levels. The four core model levels map to zoom steps; the three supplementary views address specific scenarios.

LevelC4 zoomWhen to use
contextThe system as one boxAudience: non-technical stakeholders. Shows the system, its users, and direct upstreams / downstreams.
containerDeployable / runnable unitsAudience: developers and architects. Shows applications, services, databases, and their communication.
componentOne container’s internalsAudience: developers of that container. Shows components, their responsibilities, and interactions.
codeClasses / functionsAudience: developers of that component. Usually auto-generated; most teams skip it.
deploymentInfrastructure mappingShows how containers map to infrastructure nodes (cloud regions, VMs, containers).
dynamicRuntime sequenceShows a specific scenario’s message flow; acts like a sequence diagram at any zoom level.
landscapeMultiple systemsShows a system landscape where more than one system is the focus – broader than a single Context diagram.

4. Narrow the allowlist

Most teams skip the code level and may not need landscape. Edit ctxgrd.toml to restrict the accepted values:

[C4."c4.frontmatter"]
levels = ["context", "container", "component", "deployment", "dynamic"]

A c4.level value not in the list is caught by c4.frontmatter with a diagnostic naming the invalid level and the accepted set. Remove the levels key entirely to accept any non-empty string without vocabulary checking.

5. Lint and iterate

ctxgrd                                      # human-readable output
ctxgrd --format json | jq '.[] | select(.code | startswith("c4"))'

Exit codes follow the standard contract:

  • 0 – clean.
  • 1 – diagnostics reported.
  • 2 – kernel or config error.

Diagram source format

Diagram content lives as a fenced code block inside the same .md file – Mermaid (```mermaid), Graphviz (```dot), or any renderer your toolchain supports. ctxgrd does not restrict which fenced language you use; it reads only the markdown envelope.

Raw .mmd files (standalone Mermaid source) are not supported by the core walker – ctxgrd is strictly limited to markdown. If you need to lint standalone .mmd files, use an external source script (see docs/sources.md) that emits document envelopes.

Next steps