Stemma

The diagram is the code.

An architecture model for solution architects that lives in your repository as C# source: edit the canvas, and Roslyn rewrites the file. Then walk stakeholders through it in a narrated book that travels with the model and cannot go stale either.

Where to find it Technical specifications

The Stemma canvas: three bounded contexts as lanes — Ordering, Payments, Fulfilment — with modules nested inside them, labelled data flows between them, and external systems along the top.
Storage
Your Git working treeNo runtime database, at any layer
Engine
Roslyn DocumentEditorTargeted rewrites, never string rebuilds
Guarantee
Round-trip fidelityA UI edit diffs as only that edit
Narrative
Story booksThe walkthrough that lands it, kept with the model
The source

Your model is a file you can read.

No project format, no XML, no notation to learn. A model is C# records in Architecture/Architecture.cs, and anyone on the team can open it in a text editor and follow it.

Architecture/Architecture.csexcerpt
// Bounded contextsvar ctxOrdering   = new BoundedContext("ctx_ordering", "Ordering");var ctxPayments   = new BoundedContext("ctx_payments", "Payments");var ctxFulfilment = new BoundedContext("ctx_fulfilment", "Fulfilment"); // Modules, each declaring the context it belongs tovar modCart   = new Module("mod_cart", "Cart", "ctx_ordering");var modOrder  = new Module("mod_order", "Order Lifecycle", "ctx_ordering");var modAuth   = new Module("mod_auth", "Authorisation", "ctx_payments");var modLedger = new Module("mod_ledger", "Ledger", "ctx_payments"); // A relationship carries what moves, not just that// something movesvar flowOrdered  = new DataFlow("flow_ordered",      "mod_order", "mod_auth", "OrderPlaced");var depSettleAuth = new Dependency("dep_settle_auth",      "mod_settlement", "mod_auth", "uses"); // Where a thing is in its life, and who answers for itvar tagLedger = Tag.For(modLedger,    lifecycle: new Lifecycle(Status: "to-be-created", Phase: "Q4"),    ownership: new Ownership(Squad: "Money", Domain: "Payments")); // The parts of a design that have no boxvar riskDualWrite = new Risk("risk_dualwrite",    "Order and Ledger are written in two transactions",    "mod_ledger");var qRefund = new Question("q_refund",    "Who owns a partial refund after delivery?", "mod_ledger");
Bounded contexts

Contexts are declared, not drawn.

A bounded context is a record with an id and a name. On the canvas it renders as a lane that other elements nest inside — but the nesting is a real reference in the file, not a position on a grid. Move a module between lanes and one string argument changes.

Elements

Ids are stable; names are not.

Every element carries an id that never changes and a display name that can. Flows, dependencies, tags and views all reference the id, so renaming a concept as your understanding improves costs nothing and breaks nothing.

Relationships

An arrow that says what it carries.

A DataFlow names its payload and its direction; a Dependency names its kind — uses, calls, reads, publishes, consumes. A link renders in a view only when both of its endpoints are present, so a filtered view never shows an arrow into nothing.

Lifecycle & ownership

Target state lives on the model, not in a deck.

Lifecycle marks a thing current, target, to-adapt, to-be-created or deprecated, with a phase and validity dates. Ownership attaches a squad, a domain and RAPID roles. Both go on elements and on relationships, which is where migration plans usually fall apart.

Concerns

Risks and questions are first-class.

A risk, an open question or a standing assumption is a model element anchored to what it concerns. It renders as a dotted edge to its subject, versions with it, and turns up on the Concerns board — instead of in a wiki page nobody opens.

The edit

One rename. Three lines. Nothing else moved.

Double-click the Ledger box on the canvas, type a longer name, press Enter. This is what git diff reported — all three hunks, with the file header removed and two over-long lines cut at the right margin.

git diffafter renaming one element in the UI1 file changed · +3 −3
@@ -39,7 +39,7 @@ public static class Architecture
         var modAuth = new Module("mod_auth", "Authorisation", "ctx_payments");
         var modSettlement = new Module("mod_settlement", "Settlement", "ctx_payments");
-        var modLedger = new Module("mod_ledger", "Ledger", "ctx_payments");
+        var modLedgerReconciliation = new Module("mod_ledger", "Ledger & Reconciliation", "ctx_payments");
 
         var modAllocation = new Module("mod_allocation", "Stock Allocation", "ctx_fulfilment");
         var modShipping = new Module("mod_shipping", "Shipping", "ctx_fulfilment");
@@ -86,7 +86,7 @@ public static class Architecture
         var tagSettlement = Tag.For(modSettlement, lifecycle: new Lifecycle(Status: "target", …
-        var tagLedger = Tag.For(modLedger, lifecycle: new Lifecycle(Status: "to-be-created", …
+        var tagLedger = Tag.For(modLedgerReconciliation, lifecycle: new Lifecycle(Status: "to-be-created", …
         var tagAllocation = Tag.For(modAllocation, lifecycle: new Lifecycle(Status: "current"), …
@@ -98,7 +98,7 @@ public static class Architecture
             ctxOrdering, ctxPayments, ctxFulfilment,
             modCart, modPricing, modOrder,
-            modAuth, modSettlement, modLedger,
+            modAuth, modSettlement, modLedgerReconciliation,
             modAllocation, modShipping,
  • The declarationThe display name changed. The id did not, so every reference elsewhere still resolves.
  • The local variable, twiceRoslyn renamed the symbol, so the tag and the aggregate list followed it. A find-and-replace would have missed one and still compiled.
  • And nothing elseNo reordering, no reformatting, no blank line moved, no comment lost. The reviewer sees a rename, because that is what happened.

This is the contract the rest of the tool rests on. A suite of minimal, realistic and deliberately pathological fixtures runs every operation and asserts the diff byte for byte, and the codebase has one standing instruction about it: when the fidelity suite fails, the implementation is wrong, not the test.

The vocabulary

Everything you are allowed to say.

Deliberately richer than a class diagram, deliberately poorer than an IDE. This is the whole language — there is no hidden second half.

Elements

RecordCarries
PersonAn actor, with a role — external, internal, user
SoftwareSystemA system, usually a neighbouring one you don't own
ContainerA deployable unit inside a system, with a kind — service, worker, cronjob, db
BoundedContextA DDD context; renders as a lane other elements nest inside
ModuleA unit of cohesion, optionally inside a context
CapabilityA business capability, optionally inside a context
UseCaseA user-visible use case
RiskA design risk, anchored to what it concerns
QuestionAn open design question, anchored the same way
AssumptionA standing assumption everything downstream rests on

Relationships, metadata, views

RecordCarries
DataFlowFrom, to, a payload name shown as the edge label, and a direction
DependencyFrom, to, and a kind — uses, calls, reads, publishes, consumes
LifecycleStatus (current, target, to-adapt, to-be-created, deprecated, or your own word), phase, valid-from, valid-until
OwnershipSquad, domain, and RAPID role lists — recommend, agree, perform, input, decide
Tag.ForAttaches a lifecycle and/or an ownership to any element or any link
ViewA named subset with a base lens — module map, dependency graph, or all — stored as its own .cs file
The views

One model. Whatever lens the room needs.

A view is a projection, not a copy — a named subset of the same model with its own layout, styling and notes. Switching one changes the lens, never the data.

The Concerns board in Stemma, listing the model's open question, assumption and two risks, each labelled with the element it is about.
The Concerns board Every question, assumption and risk in the model, each still pointing at the element it belongs to. Click one and it opens on the map, selected.
Story books

An architecture nobody followed is an architecture that didn't happen.

Modelling the system is half the job. The other half is forty minutes in front of people who control the budget — and that walkthrough is normally a slide deck, rebuilt by hand every quarter, drifting from the model the moment either one moves.

Concepts/view-book.stemma.yamlexcerpt
books:  - id: book_steering    name: Steering review — extracting Payments    audience: leadership    pages:      - viewId: moduleMap        title: Where the money moves today        narrative: Three contexts. Ordering and Fulfilment are          already out of the monolith. Payments is the one still          half in — Settlement is target state for Q3, Ledger does          not exist yet…       - viewId: dependencyGraph        title: The dependency that sets the order of work        narrative: Order Lifecycle still reads the legacy          monolith. That arrow is why Payments cannot go first…
A page

A view, plus the thing you would actually say.

Each page points at a view of the model and carries a title and a narrative. Not a caption — the sentence you say out loud when that picture is on the screen, written down so it survives you being on holiday.

An audience

One model, one deck per room.

A book is tagged for who it is for — leadership, engineering, security, operations. The committee that funds the work and the team that builds it get different sequences, different depth and different language, off the same model, with no second copy to keep honest.

Present mode

The canvas follows the story.

Open a book and the tool becomes the deck: the canvas switches to each page's view, the narrative sits in a strip beneath it, and prev/next walks the room through it. Drag to reorder pages, edit the narrative in place, export the whole thing as a multi-page PDF for the people who want a document.

Why it matters

The deck cannot drift either.

A book stores the reference to a view, never a picture of it. Change the architecture and next quarter's walkthrough redraws itself from the current model — so the story you told the board and the code you shipped stay the same artifact. That is the part no diagramming tool can offer, because it has no model to point at.

Stemma in present mode: a four-page steering-review book tagged for leadership, with the page strip along the bottom and the narrative for page one beneath the canvas.
Present mode A four-page steering review tagged leadership. The strip along the bottom is the story; the canvas above it is the model. Nothing was exported to build this.
The shape of it

Four layers, one direction, no second store.

The engine is pure Roslyn. It knows nothing about the web layer and nothing about any model provider, which is what keeps the fidelity guarantee testable in isolation.

ClientReact 19 · Vite · React Flow · zustand · Tailwind. Renders the active view, emits operations, applies deltas. Holds no authoritative state — a refresh re-fetches the snapshot.
WebASP.NET Core 10. REST for load, snapshot, layout and views; a SignalR hub for the operation stream. The only place model-provider calls are allowed to live.
EnginePure Roslyn. Workspace, in-memory model, the operation catalogue, DocumentEditor rewrites, the DSL reader and writer, undo, the layout sidecar. No web, no network.
ModelThe record vocabulary your workspace references. Nothing but type definitions.
StorageYour Git working tree. Architecture/*.cs, Views/*.cs, stemma.layout.json. No SQLite, no Postgres, no embedded store.
The lineage of a Stemma workspace Two committed files at the top — Architecture.cs and stemma.layout.json — with descent lines to four derived views: Module Map, Dependency Graph, Concerns board and the HTML report. Model · committed Architecture.cs α Presentation · committed stemma.layout.json β View Module Map A View Dependencies B Board Concerns C Publication Report .html D
A stemma codicum is the family tree philologists draw for a manuscript: every surviving copy traced back to one archetype. Persistence here has the same shape — two committed files, every view descended from them, and not one descendant that has to be kept honest by hand.
How it differs

Not a better canvas.

Three honest categories of tool already exist, and each is good at what it set out to do. This is what changes when the model has nowhere to drift to.

  Stemma Canvas drawing tools Model-as-language tools Code analysers
Source of truthYour source filesA separate documentA separate language beside the codeYour source files, read-only
Redesign in the pictureYes — the code followsYes, and nothing followsYes, and the code still doesn't followNo
DriftStructurally impossibleImmediate and permanentSlower, but there are still two artifactsNone, at the price of not designing
Reviewed in a pull requestAs a diff of the architectureAs an image somebody re-exportedAs a diff of the descriptionNot applicable
Risk, ownership, questionsFirst-class, anchored, versionedA sticky note, if you're luckyLimitedAbsent by construction
Telling the storyNarrated books that travel with the modelA slide deck, rebuilt by hand each timeNot addressedNot addressed
Where a coding agent meets itIn the repository it is already editingNowhere near itA file beside the code, easy to ignoreAfter the fact, as a report

For a team the practical differences are narrow and concrete: an architecture review stops opening with twenty minutes of establishing whether the picture is current; a module reaching across a context boundary shows up in the diff a reviewer already reads; and a new architect has one file and one canvas to read instead of a wiki space to excavate. There is nothing to procure — it runs on a laptop, against a repository you already own, and nothing leaves it.

Specifications

Technical specifications.

Engine
.NET 10 · Roslyn. Loads a repository through MSBuildWorkspace, or a model-only folder through an AdhocWorkspace. All edits are DocumentEditor rewrites.
Host
ASP.NET Core 10 — REST for workspace, layout, views and violations; SignalR for the operation stream and delta broadcast.
Client
React 19 · Vite · @xyflow/react · zustand · Tailwind.
Requirements
A .NET 10 runtime. A model-only workspace opens with no SDK installed, no restore and no NuGet — the metadata references come from the assemblies the process is already running on.
Model storage
Architecture/*.cs and Views/*.cs in your Git working tree.
Presentation
stemma.layout.json, committed beside the model: positions, node and edge styling, routing, notes, custom properties, free-form shapes, per-view layout choice.
Runtime database
None. The model lives in memory while a workspace is open; permanent storage is the files.
Operations
Add, rename and remove elements; re-parent; set attributes; add and remove links; set link attributes; set lifecycle; set ownership; restore — each with its own round-trip fixtures.
Views
Module map, dependency graph and concerns board built in; any number of saved custom views, each stored as code.
Layout
Architectural (by type), hierarchical, force-directed and manual — tunable per view, remembered per view. A manual move flips that view to custom.
Canvas
Pan, zoom (0.05×–2.5×), minimap, snap to 20px grid, multi-select, align and distribute, undo and redo, six connection dots per node, draw.io-style edge docking, four routing modes, both-end markers.
Export
PNG · SVG · draw.io · Mermaid · multi-page PDF view-books · a single-file interactive HTML report that opens offline with no network requests.
Report
Audience modes (builder, stakeholder, reviewer), per-view layer toggles, element search, and element-anchored comments that export as a pack and import back into the workspace.
Story books
Ordered, audience-tagged sequences of pages; each page references a view and carries a title and a narrative. Present mode drives the canvas from the page, with prev/next, drag-to-reorder, in-place narrative editing and multi-page PDF export. Stored as Concepts/view-book.stemma.yaml beside the model.
Validation
Dangling references and architecture-rule violations surfaced inline on the canvas and in the status bar.
Collaboration
Git. Branch, tag, blame, merge, revert, pull request. Live sync over SignalR for clients open on the same workspace; external file edits reconcile without clobbering in-flight work.
Fidelity
Every operation carries minimal, realistic and pathological round-trip fixtures. A failing fixture blocks the change, not the other way round.
Licence
Source-available. The exact terms are still an open decision and will be settled in writing before the code lands, not after.
Roadmap

Horizons, not dates.

A side project built on evenings and weekends since 4 May 2026. Promising quarters would be a lie with a nice font, so each item carries an honest position instead. The words are Stemma's own lifecycle vocabulary — the same three it puts on your systems.

Now current

  • The Roslyn engine and the fidelity suite that gates it
  • The canonical model — elements, links, lifecycle, ownership, views as code
  • The canvas — module map, dependency graph, saved views, layout, styling, shapes, notes
  • The committed sidecar that keeps presentation out of the model
  • Story books — narrated, audience-tagged page sequences, with present mode and PDF export
  • The single-file HTML report, with audience modes and a comment loop
  • Model-only workspaces and the from-scratch onboarding path

Next target

  • Fidelity fixtures across the whole operation catalogue — every operation, no exception
  • A desktop window, so it stops reading as a dev server
  • One-command install as a global tool
  • Clearer view management — faster switching, better handling
  • An accessibility pass — keyboard, focus, contrast, reduced motion
  • A curated sample gallery as living documentation

Later to‑be‑created

  • More projections — Mermaid and PlantUML output, a live code-preview pane
  • Git-backed sessions — a session is a branch, a save is a commit
  • Deeper validation — background compilation, richer rules inline
  • Canvas search and filter, and deep-linkable views
  • A repository integration that regenerates the report on merge and comments the architecture change on the pull request
  • Hosted report links — the one part that could ever be a service
Questions

What people ask first.

Six objections, answered where you can find them rather than three emails in.

Does the diagram stay in sync with the code?

It cannot drift, because it is the same artefact. The model is C# in Architecture/*.cs inside your repository, and a canvas edit is a Roslyn DocumentEditor rewrite of that file. There is no export step to forget and no second copy to reconcile.

Does my whole system have to be written in C#?

No. C# is the notation the model is written in, not a constraint on what you model. The workspace can be a model-only folder with no solution in it, and the systems it describes can be written in anything. A .NET runtime is the only requirement, and a model-only workspace opens with no SDK, no restore and no NuGet.

Where is the data stored?

In your Git working tree, and nowhere else. There is no runtime database at any layer. The model is C# source; presentation — positions, styling, routing, notes — is a stemma.layout.json sidecar committed beside it. While a workspace is open the model lives in memory; permanent storage is the files.

How is this different from draw.io, Lucidchart or an EA tool?

Drawing tools produce a picture that is true on the day it is saved. Reverse-engineering tools read code but cannot edit it. Stemma closes the loop in both directions and guarantees round-trip fidelity, so a UI edit arrives as a diff containing only that edit — which is what makes the loop trustworthy enough to use.

What does a change look like in review?

A normal Git diff. Renaming one element on the canvas rewrites the declaration and every reference Roslyn can see, and touches nothing else: no reordering, no reformatting, no lost comment. Reviewers read it as the change it was.

How do stakeholders see the model without installing anything?

Two ways. A story book is an ordered, audience-tagged sequence of pages that drives the canvas as you present, stored beside the model so it cannot go stale either. Or export a single self-contained HTML report — one file, opens offline with no server, no login and no seat, with audience modes and element-anchored comments that import back.

The argument behind all of it is an essay: The Diagram That Cannot Lie — why a diagram that can drift always does. On what diagrams are actually for, there is Diagrams Are Conversations, Not Documentation.

Where to find it

The repository.

github.com/batas2/Stemma

The repository is open. The engine lands in it once the operation catalogue is fully covered by fidelity fixtures — the guarantee has to hold everywhere before it is offered to anyone, and that is the one thing worth waiting for. Watch it there to know when.

If you want to argue with any of the above — which is more useful to me than agreement — write to me. Architects who have lived with the drift problem long enough to be cynical about it are exactly the readers I want.

Reading this with a machine? The whole page is also plain markdown, and the site's context file is llms.txt.

Stemma is a pet project. It is built by one person on evenings and weekends, it is open source, and it is not a company — there is no roadmap I owe anybody, no support line and no sales call at the end of this page. It exists because I wanted the tool and nobody had built it.