Skip to content

Latest commit

 

History

46 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Dia-GramV — plan the architecture before you write the code

A typed, linted model of the system you are building — one file your coding agent reads and writes through MCP, and you edit on a canvas.

MCP over stdio Node 20.19+ Svelte 5 early MIT

Works with any MCP client. The skill and hooks are for Claude Code.
Diagrams are JSON files in your repo. Runs on your machine — no accounts, no cloud, no telemetry.

What's new · 30 seconds ↓ · Why · Four cases · Viewer · Reference

The viewer: a shop platform — clients, edge, services, data and providers as frames; shaped nodes; wires routed around cards carrying their protocol
examples/shop-platform.dgv.json — 17 components, 21 connections, 7 kB · the same file as an SVG export

Thirty seconds

git clone https://github.com/ShAInyXYZ/Dia-GramV.git && cd Dia-GramV
npm install && npm run build
node packages/mcp/bin/dgv.mjs doctor                        # checks Node + the build, prints the lines below with your path
claude mcp add dgv -s user -- node "$PWD/packages/mcp/bin/dgv.mjs" mcp
ln -s "$PWD/skill" ~/.claude/skills/dgv                     # optional: teaches the agent the workflow

Then, in any project, tell the agent:

Map this system in DGV before we start.

It reads the catalog, writes dgv/<name>.dgv.json, gets a lint report back on every write, repairs what it broke, lays the diagram out and opens it at http://127.0.0.1:7710. From then on the file is the map: every later session reads it before it reads code.

Needs Node 20.19+ or 22.12+. npm install fetches everything (~100 MB, nothing global); npm run build compiles the viewer once. Skip the build if you only want the MCP tools — everything works without it except dgv_open.

What's new · v0.2 — flags and history

The agent can now flag an architecture problem on the exact node, wire or frame it is about — a ⚑ bubble on the canvas with what is wrong, why, and the fix — and every change to the diagram, by the agent or by you, lands in a history you open from the bottom of the canvas. Three new MCP tools: dgv_flag, dgv_resolve, dgv_history.

The details — what it looks like, how to use it, where it lives, every new command

Lint says whether the graph is valid. It cannot say whether the design is right. Reviewing a real project through its diagram, the agent found six things no rule would ever fire on — a process that was a dead end for settings (every change was a restart), plugins loaded three times across the map, a spawned child with no death-pact — and had nowhere to put them but a chat message that scrolls away. Now it has two places: a flag on the element, and a record of what changed.

The installer card carries a ⚑; its open note says what is wrong, why, and the fix, with a resolve button. The history drop-up at the bottom lists nine changes grouped by element
Flags. The agent pinned a judgement on install.sh: no dependency contract, and the fix. The ⚑ keeps pulsing until it is opened; resolve when it is done. History. The pill at the bottom centre opens into what changed, per element or as a timeline.
The inspector on a flagged storage node: the flag with its note and fix, and the form to raise a new one — title, kind, fix. Behind it, the history with one element expanded
Yours too. Every flag is in the inspector with its note; + flag raises one by hand when you spot what the agent missed — it shows up in the agent's next dgv_read as . Expand an element in the history to see each change, who made it and when.

How you use it. After the map exists, ask for a review rather than a fix:

Read the diagram, then audit the architecture for incoherences lint can't see. Verify each against the code. Pin every real one on its element with what's wrong, why, and the fix. Don't fix anything yet.

Open the viewer: every flag is a ⚑ on its card, filled and ringing until read. Click one to read the note; resolve the ones you disagree with (the history keeps the fact that it was raised), leave the rest. Then: "fix the open flags on chainx, resolve each as you go" — and the history pill turns 3 new when the agent's writes land, with a mark on every card it touched. Layout moves are not recorded; architecture is.

Where it lives. Flags sit on the element in the file ("flags": [ { "id", "kind", "title", "note", "fix", "by", "at" } ]) and lint reports each one — flag/issue counts as a warning, flag/idea and flag/question as info — so the problems panel, the rail counter and the report dgv_apply returns all carry them. ack never silences a flag; only resolving does. History is computed by the server on every save, by diffing the file on disk against the one being written, and kept in the file as history (the last 300 changes). Nobody has to remember to log anything, and a save from the browser records the same way as one from the agent. Existing diagrams need nothing — the fields are optional and appear on the first save.

Every new command.

where command does
MCP dgv_flag name, on (node / edge / frame id), title, note, fix, kind issue · idea · question. Raises one flag; returns the open count
MCP dgv_resolve name, on, optional flag id — needed only when the element carries several; the error names them
MCP dgv_history name, optional on, limit. Newest first: { at, by, node|edge|frame, change }
MCP dgv_apply unchanged, plus: accepts flags on any element (replaced wholesale — prefer the two above) and returns recorded, the history entries the write produced
MCP dgv_read unchanged, plus: under flagged elements, ## flags: N open, and ## recent changes at the end — the session-start hook prints the same
CLI dgv history <name> [--on id] [--limit n] the history in the terminal
viewer H open / close the history
viewer click a ⚑ read the note; resolve removes the flag
viewer inspector → + flag raise a flag by hand on the selected element

What it is

One file. dgv/<name>.dgv.json holds frames (boundaries), nodes (components) and edges (connections). Every node has a kind from a fixed catalog — ui, service, api, db, queue, bridge, external… — and can declare ports. Every edge names the port it lands on and the protocol it speaks. Plain JSON, in your repository, next to the code it describes.

Two ways in. The MCP server is the agent's: it creates, changes and reads the file, and on every write gets a lint report — a stable code, the element, and concrete fixes. The viewer is yours: a Svelte Flow canvas where kinds have shapes and wires carry their protocol, with an inspector for every field and the same lint live in a side panel. When the agent changes the file, the page reloads.

The agent reaches the file through MCP; you reach it through the viewer; nothing else holds state

Why it matters when an AI writes the code

The drawing is the least important part. What matters is that the model of the system is a file a program can read, check and change.

If you vibecode, the system grows faster than you can keep it in your head, and the shape you think it has drifts from the shape it has. DGV gives that shape a place to live, and a linter that objects when it stops making sense.

If you develop with an AI beside you, the diagram is where you state intent the code cannot express yet — the worker consumes the queue; the API never writes to the bucket directly — once, in a form every later session inherits.

If you are the agent, this is the difference between grepping and knowing. In an unfamiliar repository you rebuild the picture by opening files. dgv_read hands you the picture. Its complete output for the notes app below, verbatim:

# Notes app
frames 3 · nodes 6 · edges 5 · updated 2026-08-27

## frame browser: Browser
- web [ui] Notes UI — SvelteKit
## frame server: Server · one process
- api [api] HTTP API — /api/notes ports: rest:http/in
- jobs [worker] Job runner — thumbnails, exports
## frame data: Data
- pg [db] Postgres — notes, users ports: sql:sql/in
- redis [queue] Job queue — Redis lists ports: jobs:redis/in
- s3 [storage] Object store — uploads ports: put:s3/in

## edges
- web-api: web → api [sync http] fetch ports ·→rest
- api-pg: api → pg [data sql] ports ·→sql
- api-redis: api → redis [async redis] enqueue ports ·→jobs
- jobs-redis: jobs → redis [async redis] consume ports ·→jobs
- jobs-s3: jobs → s3 [data s3] ports ·→put

An agent that can read api → pg [data sql] ·→sql does not invent a REST endpoint on the database. Two hundred tokens replace a tour of the tree.

What it does — four cases

1 · Plan before you build, and be told when the plan cannot work

The agent describes a small notes app in one dgv_apply. Two ordinary mistakes are in it: the object store's port is called put on the node and upload on the edge, and a database is calling back into the API.

dgv_apply({ name: "notes-app",
  nodes: [ { id: "s3", kind: "storage", label: "Object store", frame: "data",
             ports: [ { id: "put", protocol: "s3", dir: "in" } ] },  ],
  edges: [ { id: "jobs-s3", source: "jobs", target: "s3", kind: "data", protocol: "s3", targetPort: "upload" },
           { id: "pg-api",  source: "pg",   target: "api", kind: "sync", protocol: "http", label: "notify on change" },  ] })

The write goes through, and the report comes back in the same turn:

{ "ok": false, "lint": { "error": 1, "warning": 2, "info": 0 },
  "diagnostics": [
    { "code": "port/undeclared", "severity": "error",
      "message": "edge \"jobs-s3\" uses target port \"upload\" but node \"s3\" does not declare it",
      "subject": { "type": "edge", "id": "jobs-s3", "field": "targetPort" },
      "fixes": [ "add port {id:\"upload\"} to node \"s3\"", "point the edge at one of: put" ] },
    { "code": "kind/store-initiates", "severity": "warning",
      "message": "\"pg\" is a db; stores do not initiate sync calls to \"api\"",
      "subject": { "type": "edge", "id": "pg-api" },
      "fixes": [ "reverse the edge and mark it kind:\"data\"",
                 "if it is a trigger/CDC stream, add a worker or queue between them" ] },  ] }

The same report in the viewer — the failing wire is red, and every entry jumps to its element:

The problems panel: 1 error, 2 warnings, each with its code, message and fix; the failing wire drawn in red

The first mistake is a typo that would have become a bug. The second is an architecture an agent would have implemented without a second thought. Both come back as an id, a code and a fix, so the plan is repaired before any code exists:

dgv_apply({ name: "notes-app",
            edges: [ { id: "jobs-s3", targetPort: "put" } ],       // partial: id + the field that changes
            remove: { edges: [ "pg-api" ] } })
→ { "ok": true, "lint": { "error": 0, "warning": 0, "info": 0 } }
The repaired notes app, exported as SVG
the repaired file, exported with dgv_export format:"svg"source · the version with the two mistakes, to see the lint yourself

2 · Map a system you already have

Point the agent at a repository — map Cerveau's architecture in DGV, from the code — and it reads entrypoints, listeners, clients and config, then writes what it found. The local AI harness below is 13 components in four boundaries: a panel and a phone driving a Go core, a llama.cpp server, Typesense for memory, a Python embedding sidecar.

At full size — Cerveau itself, 35 components across 7 boundaries, every call bound to a declared port:

Cerveau's architecture: 35 components across 7 boundaries

Press S and every frame folds into one node, with the wires that crossed it merged into a single labelled link. Same file; there is no second overview diagram to keep in step with the first:

The shop platform folded: 5 groups, the links between them merged and counted

3 · Track the build on the same diagram

A node can carry a statustodo wip done blocked failed update. Press 2 and the canvas colours by status instead of kind; the file is now the build board. An agent picks up where the last session stopped by reading what is still todo, and a note on a blocked node says why:

The shop platform coloured by status: done in green, wip in amber, todo grey, the payments bridge blocked in violet

4 · Know when it stops being true

Lint says the plan is coherent. It cannot say the plan is true — that the code on disk is still the code the diagram describes. Give a node a path (a file, a directory, a glob, a list) and dgv_drift walks the project — git ls-files, so .gitignore is respected — and reports a path that matches nothing (drift/missing), a directory of code that belongs to no node (drift/unclaimed), and two nodes claiming the same file (drift/shared).

This repository keeps its own architecture that way, every node with a path:

DGV's own architecture, drift-checked: Claude Code and the hooks; the MCP server, CLI, HTTP/SSE server and core in one Node process; the viewer; the diagram files

The first time drift ran on it, it found something:

$ node packages/mcp/bin/dgv.mjs drift dia-gramv
warning drift/unclaimed  packages/mcp/ — 1 of 5 files belong to no node
        fix: add a node with this path | widen an existing node's path to cover it | add it to meta.driftIgnore if it is not part of the system

packages/mcp/package.json, claimed by nobody, because the MCP node's path was one file. Widened, and clean.

Two optional Claude Code hooks close the loop (hooks/; doctor prints the settings block with your path):

  • SessionStart prints the outline of every diagram in ./dgv into context, with its drift summary — the first thing the agent knows is the shape of the system and whether the map is stale.
  • Stop runs drift after each turn and, only when there is something to say, leaves one line: DGV · app: 1 node path no longer exists (old). It never blocks.

The viewer

node packages/mcp/bin/dgv.mjs servehttp://127.0.0.1:7710 — or dgv_open from the agent.

Quick-add popover: type to filter the kinds, each with its shape
Add where you point. Double-click the canvas or press A: pick a kind, it lands under the cursor, inside whatever frame is there.
Inline editor on a selected wire: kind, protocol, label, from/to port
The wire is the contract. Click one: kind, protocol, what happens, which port. Drag a new wire onto a port chip and it binds to that port.
The key: kinds grouped by role with their shapes, link kinds, build status
A key that filters. Hover an entry to spotlight that kind on the canvas; click to pin it.

Flags. Lint catches what a rule can catch. The agent (or you) catches the rest — a process that is a dead end for settings, plugins loaded three times across the map, a child process with no death-pact — and pins it on the element with dgv_flag. On the canvas it is a ⚑ bubble on the card, the frame label or the wire, filled and ringing until you open it; the note says what is wrong, why, and the concrete fix, with a resolve button. Open flags sit in the problems list too (flag/issue counts as a warning), and in the outline every session starts with.

History. The pill at the bottom centre — history · 3 new — opens upward into what changed: per element (chainx · 4 changes · 2m ago, click to see them and jump to the card) or as a timeline. Every save that changes the architecture lands there, whether it came from dgv_apply (agent) or from this page (you): a status flipped, a port added, a node renamed, a flag raised or resolved. Dragging cards around does not count. It is computed on the server by diffing the file on every write, kept in the file (capped), and readable from the agent with dgv_history. Cards changed since you last looked carry a small green mark.

Drag a node into a frame and it joins it; frames grow to fit. Ctrl+Z undoes. Ctrl+S saves — and if the agent changed the file while you had unsaved edits, the page says so and lets you choose. L cycles the wire style: floating bezier, routed around cards, straight. Shift+S saves what is on screen as a self-contained SVG, which is how every diagram in this README was made.

Every shortcut
A / double-click add a node, choosing its kind
drag from a node's right handle connect; drop on a port chip to bind the edge to that port
G wrap the selection in a new frame
1 / 2 colour by kind / by status
L wire style: floating, routed, straight
S fold every frame into one node; again to unfold. Hover a single frame to fold just that one
Shift+S save what is on screen as SVG
H the change history
F fit · I inspector · P problems · Esc close · Del delete · Ctrl+S save · Ctrl+Z undo

The folded view keeps its own arrangement per diagram in your browser, never in the file.

Reference

MCP tools
tool does
dgv_catalog the node kinds (shape and meaning), edge kinds, protocols and statuses — read once per session
dgv_list the diagrams in the directory, with counts
dgv_read one diagram: mode: "summary" (the outline above, default) or mode: "json"
dgv_create a new, empty diagram
dgv_apply upsert frames, nodes and edges by id; remove by id; places new nodes; returns the lint report. Partial: to change one field on an existing element, send its id and that field
dgv_lint the diagnostics: code, severity, subject, fixes
dgv_drift does the diagram still describe the code? every path must exist, every directory of code must belong to a node
dgv_flag pin an architecture judgement on a node, edge or frame — title, note (why), fix (the change); kind issue / idea / question. Shown as a bubble in the viewer, listed by lint until resolved
dgv_resolve remove a flag once it is fixed; the history keeps both ends
dgv_history who changed what: every architecture change per element, agent or viewer, newest first
dgv_layout dagre layout, TB or LR; overwrites positions
dgv_open starts the viewer if it is not running and opens the diagram
dgv_export markdown (tables), mermaid, summary (the outline), or svg

Diagrams go to ./dgv under the directory the agent was started in; DGV_DIR puts them elsewhere.

What the linter checks

Shape first (schema/invalid), then references (ref/missing-node, ref/missing-frame, ref/duplicate-id), then the rules below. Errors block ok; warnings and info are advice.

Errors — fix before moving on.

code fires when
port/undeclared an edge names a port the node does not declare
port/protocol-mismatch the edge's protocol is not the port's protocol
port/direction an edge enters an out port, or leaves an in port
graph/import-cycle modules import each other in a loop
frame/nested a frame has a parent — frames do not nest; one level keeps folding, layout and the file simple

Warnings — the plan probably has a hole.

code fires when
port/unbound the target declares ports and a call edge names none
contract/unspecified an edge between different kinds has neither a protocol nor a label
kind/store-initiates a database, cache or bucket is the source of a call
kind/import-across-programs an import crosses a frame boundary — two processes cannot share one
kind/api-unused an API that nothing calls
kind/bridge-one-sided a bridge touching fewer than two other nodes
graph/orphan a node with no edges
layout/overlap, layout/outside-frame cards overlap, or sit outside their frame — dgv_layout fixes both

Info — worth a look, silent in the counts: kind/store-access, kind/module-loose, kind/external-inside, graph/shared-store, layout/unplaced.

Flags — not rules but judgements, raised with dgv_flag: flag/issue is a warning, flag/idea and flag/question are info. They are never acknowledged away; they are resolved.

A warning that is intentional gets ack: "<reason>" on its element: it becomes info with the reason attached, and the reason travels with the file. Errors cannot be acknowledged.

The file format and the catalog
{ "dgv": 1,
  "meta":   { "title": "Notes app", "description": "", "colorBy": "kind", "edgeStyle": "routed" },
  "frames": [ { "id": "server", "label": "Server · one process", "tone": "amber",
                "position": { "x": 480, "y": 60 }, "size": { "width": 380, "height": 300 } } ],
  "nodes":  [ { "id": "api", "kind": "api", "label": "HTTP API", "sublabel": "/api/notes",
                "frame": "server", "status": "done", "path": "src/api", "position": { "x": 520, "y": 120 },
                "ports": [ { "id": "rest", "protocol": "http", "dir": "in" } ] } ],
  "edges":  [ { "id": "web-api", "source": "web", "target": "api",
                "kind": "sync", "protocol": "http", "targetPort": "rest", "label": "fetch",
                "flags": [ { "id": "f1", "kind": "issue", "title": "no death-pact on the child", "fix": "PR_SET_PDEATHSIG", "by": "agent", "at": "2026-08-28T12:15:00Z" } ] } ],
  "history": [ { "at": "2026-08-28T12:15:00Z", "by": "agent", "type": "edge", "id": "web-api", "op": "flag", "flag": { "id": "f1", "kind": "issue", "title": "no death-pact on the child" } } ] }

flags may sit on any frame, node or edge; history is written by the store on every save (never by hand) and holds the last 300 architecture changes.

Every node kind with its shape, and every wire kind with its dash — drawn from the catalog

kind is required on a node. On an edge it is inferred from the protocol when omitted — data for sql redis s3 fs smb, async for kafka nats amqp mqtt sse ws, otherwise sync. Positions are saved, so an arrangement you made stays made. The full catalog — every kind, protocol and lint code — is in skill/references/format.md.

CLI, for use without an agent
node packages/mcp/bin/dgv.mjs serve  [--dir d] [--port p] [--no-open]      # viewer, default http://127.0.0.1:7710
node packages/mcp/bin/dgv.mjs lint   <name|file> [--json]
node packages/mcp/bin/dgv.mjs layout <name|file> [--direction TB|LR]
node packages/mcp/bin/dgv.mjs export <name|file> [--format markdown|mermaid|summary|svg]
node packages/mcp/bin/dgv.mjs drift  <name|file> [--root dir] [--json]
node packages/mcp/bin/dgv.mjs history <name|file> [--on id] [--limit n]
node packages/mcp/bin/dgv.mjs list | catalog | doctor | open <name>
Packages
path what
packages/core plain ESM, no DOM: catalog, schema, lint, dagre layout, orthogonal wire router, fold, exports, drift, history, file store
packages/mcp the dgv CLI, the MCP server, and the local HTTP/SSE server behind the viewer
packages/viewer Svelte 5 + Svelte Flow: shaped nodes, frames, folding, inspector, live problems, flag bubbles, change history
skill/ a Claude Code skill (SKILL.md) that teaches the workflow
hooks/ SessionStart and Stop hooks for Claude Code
dgv/ this repository's own diagram, drift-checked
examples/ notes-app · notes-app-broken · shop-platform · local-ai-harness

npm test — core: schema, lint rules, patch semantics, layout containment, folding, exports, SVG, drift.

Limits

DGV does not parse your source. Lint can tell you the plan is coherent; drift can tell you every node still points at code that exists and every directory of code has a node. Neither can tell you that the calls the diagram draws are the calls the code makes — that is still read by a person, or by the agent, and the file living in the repo is what makes that reading reviewable.

Not here: collaboration or hosting, sequence and lifecycle diagrams, discovery of a repository's structure. The format is versioned (dgv: 1) so those can be added without breaking existing files.

Where it came from

Cerveau is a local-first agentic coding harness. Its docs folder held a private draft called arch-viewer: a Svelte Flow canvas reading a Diagram.json of its architecture — 99 nodes, 127 edges, nodes with a kind, edges with a label. Nothing but a browser could read it, so the agent doing the building never saw it. DGV keeps the canvas, the frames and the layout, and puts a contract underneath: a catalog, ports and protocols, declared membership, a linter, and an MCP so the agent reads and writes the same file. archify supplied the idea of a typed intermediate representation with repairable diagnostics.

MIT © Mounir Belahbib

About

Plan a system's architecture as a typed, linted, live diagram before you write code. Local-only MCP server + Svelte Flow viewer.

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages