@diagrammo/dgmo 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,14 +1,16 @@
1
1
  # DGMO AI Integration Guide
2
2
 
3
- Use AI coding tools to generate `.dgmo` diagrams. This guide covers Claude Code, Copilot, Cursor, and other AI tools.
3
+ Use AI coding tools to generate `.dgmo` diagrams. This guide covers Claude Code, Copilot, Cursor, Windsurf, and any tool with an MCP client.
4
4
 
5
- ## MCP Server
5
+ ---
6
6
 
7
- `@diagrammo/dgmo-mcp` provides an MCP server that exposes DGMO rendering, sharing, and documentation tools. Works with Claude Desktop, Claude Code, and any MCP-compatible client.
7
+ ## MCP Server (recommended for Claude)
8
8
 
9
- 5 tools: `render_diagram`, `share_diagram`, `open_in_app`, `list_chart_types`, `get_language_reference`.
9
+ `@diagrammo/dgmo-mcp` provides an MCP server that gives Claude the ability to render, share, and look up DGMO diagrams directly — no file management needed.
10
10
 
11
- Setup (Claude Code add to `.claude/settings.local.json`):
11
+ **5 tools:** `render_diagram`, `share_diagram`, `open_in_app`, `list_chart_types`, `get_language_reference`
12
+
13
+ Add to `~/.claude/settings.json` (global) or `.claude/settings.local.json` (project):
12
14
 
13
15
  ```json
14
16
  {
@@ -23,60 +25,46 @@ Setup (Claude Code — add to `.claude/settings.local.json`):
23
25
 
24
26
  See `dgmo-mcp/README.md` for full configuration options.
25
27
 
26
- ## Claude Code — Skills
27
-
28
- Copy the `.claude/skills/dgmo-*` directories from this repo into your project's `.claude/skills/` directory. This gives you four slash commands:
28
+ ---
29
29
 
30
- | Command | What it does |
31
- |---------|-------------|
32
- | `/dgmo-generate <description>` | Picks the best diagram type automatically |
33
- | `/dgmo-sequence <flow>` | Generates a sequence diagram |
34
- | `/dgmo-flowchart <process>` | Generates a flowchart |
35
- | `/dgmo-chart <data description>` | Generates a data chart |
30
+ ## Claude Code Skill (slash command)
36
31
 
37
- ### Setup
32
+ Installs a `/dgmo` slash command that gives Claude full dgmo context — all chart types, CLI flags, workflow, and tips.
38
33
 
39
34
  ```bash
40
- # Copy skills into your project
41
- cp -r node_modules/@diagrammo/dgmo/.claude/skills/dgmo-* .claude/skills/
42
-
43
- # Or if dgmo is installed globally
44
- cp -r $(npm root -g)/@diagrammo/dgmo/.claude/skills/dgmo-* .claude/skills/
35
+ dgmo --install-claude-skill
45
36
  ```
46
37
 
47
- ### Usage examples
38
+ This copies a skill file into `~/.claude/commands/`, making `/dgmo` available in every Claude Code session.
48
39
 
49
- ```
50
- /dgmo-generate an ER diagram for a blog with users, posts, and comments
51
- /dgmo-sequence the OAuth2 authorization code flow
52
- /dgmo-flowchart CI/CD pipeline with build, test, and deploy stages
53
- /dgmo-chart quarterly revenue: Q1 100, Q2 120, Q3 110, Q4 130
54
- ```
40
+ ---
55
41
 
56
42
  ## Claude Code — CLAUDE.md snippet
57
43
 
58
- Add this to your project's `CLAUDE.md` to teach Claude about DGMO without installing skills:
44
+ To teach Claude about DGMO in a specific project without the global skill, add this to your `CLAUDE.md`:
59
45
 
60
46
  ```markdown
61
47
  ## DGMO Diagrams
62
48
 
63
- When the user asks for a diagram, generate a `.dgmo` file. DGMO is a text-based diagram language.
49
+ When the user asks for a diagram, generate a `.dgmo` file.
64
50
 
65
51
  Quick reference:
66
- - Sequence: `A -> B: message` or `A -message-> B`
52
+ - Sequence: `A -message-> B` or `A <-response- B`
67
53
  - Flowchart: `(Start) -> [Process] -> <Decision?> -yes-> (End)`
68
54
  - Bar chart: `chart: bar` then `Label: value` lines
69
55
  - ER diagram: `chart: er` then table definitions and `table1 1--* table2` relationships
70
56
  - Org chart: `chart: org` then indented hierarchy
71
57
 
72
- Full reference: see `node_modules/@diagrammo/dgmo/docs/language-reference.md`
58
+ Full reference: `node_modules/@diagrammo/dgmo/docs/language-reference.md`
73
59
 
74
- Render with: `dgmo file.dgmo -o output.svg` or `dgmo file.dgmo -o url` for shareable link.
60
+ Render with: `dgmo file.dgmo` (PNG) or `dgmo file.dgmo -o url` (shareable link).
75
61
  ```
76
62
 
63
+ ---
64
+
77
65
  ## Other AI Tools — Prompt Files
78
66
 
79
- DGMO ships prompt files for popular AI coding tools. These are included in the npm package:
67
+ DGMO ships context files for popular AI coding tools, included in the npm package and auto-loaded when present in a project root.
80
68
 
81
69
  | File | Tool | How it works |
82
70
  |------|------|-------------|
@@ -84,42 +72,37 @@ DGMO ships prompt files for popular AI coding tools. These are included in the n
84
72
  | `.cursorrules` | Cursor | Auto-loaded when present in project root |
85
73
  | `.windsurfrules` | Windsurf | Auto-loaded when present in project root |
86
74
 
87
- ### Setup
88
-
89
75
  Copy the relevant file into your project root:
90
76
 
91
77
  ```bash
92
- # From node_modules
78
+ # From node_modules (if installed as a dependency)
93
79
  cp node_modules/@diagrammo/dgmo/.cursorrules .
94
80
  cp node_modules/@diagrammo/dgmo/.windsurfrules .
95
81
  mkdir -p .github && cp node_modules/@diagrammo/dgmo/.github/copilot-instructions.md .github/
96
82
 
97
- # Or from global install
83
+ # From global npm install
98
84
  cp $(npm root -g)/@diagrammo/dgmo/.cursorrules .
99
85
  ```
100
86
 
101
- Each file contains a condensed DGMO syntax reference with examples for the most common diagram types, all 29 chart types listed, rendering commands, and common mistakes to avoid.
87
+ Each file contains a condensed DGMO syntax reference with examples, all chart types listed, rendering commands, and common mistakes to avoid.
102
88
 
103
- ## Rendering
89
+ ---
104
90
 
105
- If the `dgmo` CLI is installed, diagrams can be rendered:
91
+ ## Rendering commands
106
92
 
107
93
  ```bash
108
- # Install
109
- npm install -g @diagrammo/dgmo # or: brew install diagrammo/dgmo/dgmo
110
-
111
- # Render
112
94
  dgmo diagram.dgmo # PNG output
113
95
  dgmo diagram.dgmo -o output.svg # SVG output
114
- dgmo diagram.dgmo -o url # Shareable URL
115
-
116
- # AI-friendly JSON output
117
- dgmo diagram.dgmo -o output.svg --json
118
- dgmo --chart-types --json # List all chart types
96
+ dgmo diagram.dgmo -o url # Shareable diagrammo.app URL
97
+ dgmo diagram.dgmo -o url --copy # URL copied to clipboard
98
+ dgmo --chart-types # List all supported chart types
99
+ dgmo --chart-types --json # Machine-readable chart type list
119
100
  ```
120
101
 
102
+ ---
103
+
121
104
  ## Supported chart types
122
105
 
123
106
  Run `dgmo --chart-types` for the full list, or see `docs/language-reference.md`.
124
107
 
125
- 32 types: bar, line, area, pie, doughnut, radar, polar-area, bar-stacked, scatter, sankey, chord, function, heatmap, funnel, slope, wordcloud, arc, timeline, venn, quadrant, sequence, flowchart, state, class, er, org, kanban, c4, initiative-status, sitemap, infra.
108
+ 29 types: bar, line, area, multi-line, pie, doughnut, radar, polar-area, bar-stacked, scatter, sankey, chord, function, heatmap, funnel, slope, wordcloud, arc, timeline, venn, quadrant, sequence, flowchart, class, er, org, kanban, c4, initiative-status, infra.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  "dist",
27
27
  "src",
28
28
  "docs",
29
- ".claude/skills",
29
+ ".claude/commands",
30
30
  ".github/copilot-instructions.md",
31
31
  ".cursorrules",
32
32
  ".windsurfrules"
@@ -39,7 +39,8 @@
39
39
  "test": "vitest run",
40
40
  "test:watch": "vitest",
41
41
  "gallery": "pnpm build && node scripts/generate-gallery.mjs",
42
- "check:duplication": "jscpd ./src"
42
+ "check:duplication": "jscpd ./src",
43
+ "postinstall": "node -e \"console.log('\\n💡 Claude Code user? Run: dgmo --install-claude-skill\\n')\""
43
44
  },
44
45
  "dependencies": {
45
46
  "@dagrejs/dagre": "^2.0.4",
package/src/c4/layout.ts CHANGED
@@ -109,17 +109,31 @@ const LEGEND_CAPSULE_PAD = 4;
109
109
  // Post-Layout Crossing Reduction
110
110
  // ============================================================
111
111
 
112
+ interface NodeGeometry {
113
+ y: number;
114
+ width: number;
115
+ height: number;
116
+ }
117
+
118
+ // Large penalty per edge-node collision — dominates the distance term so the
119
+ // sifter strongly prefers orderings where edges don't pass through other nodes.
120
+ const EDGE_NODE_COLLISION_WEIGHT = 5000;
121
+
112
122
  /**
113
123
  * Compute penalty for an edge ordering. Uses degree-weighted edge distance:
114
124
  * long edges to high-degree nodes are penalized more than to low-degree nodes.
115
125
  * This places shared/important nodes closer to their neighbors, reducing
116
126
  * visual edge congestion.
117
127
  *
128
+ * When nodeGeometry is provided, also adds a heavy penalty for each case where
129
+ * a straight-line edge bounding box overlaps another node — driving the sifter
130
+ * to prefer orderings that avoid edge-node collisions.
118
131
  */
119
132
  function computeEdgePenalty(
120
133
  edgeList: { source: string; target: string }[],
121
134
  nodePositions: Map<string, number>,
122
- degrees: Map<string, number>
135
+ degrees: Map<string, number>,
136
+ nodeGeometry?: Map<string, NodeGeometry>
123
137
  ): number {
124
138
  let penalty = 0;
125
139
 
@@ -135,6 +149,48 @@ function computeEdgePenalty(
135
149
  penalty += dist * weight;
136
150
  }
137
151
 
152
+ // Edge-node collision penalty: for each edge A→B, check if any other node C
153
+ // has its bounding box inside the straight-line bounding box of the edge.
154
+ // Uses x from nodePositions (updated per permutation) and y/size from nodeGeometry.
155
+ if (nodeGeometry) {
156
+ for (const edge of edgeList) {
157
+ const geomA = nodeGeometry.get(edge.source);
158
+ const geomB = nodeGeometry.get(edge.target);
159
+ if (!geomA || !geomB) continue;
160
+
161
+ const ax = nodePositions.get(edge.source) ?? 0;
162
+ const bx = nodePositions.get(edge.target) ?? 0;
163
+ const ay = geomA.y;
164
+ const by = geomB.y;
165
+
166
+ // Skip edges within the same rank — they have no vertical span to check
167
+ if (ay === by) continue;
168
+
169
+ const edgeMinX = Math.min(ax, bx);
170
+ const edgeMaxX = Math.max(ax, bx);
171
+ const edgeMinY = Math.min(ay, by);
172
+ const edgeMaxY = Math.max(ay, by);
173
+
174
+ for (const [name, geomC] of nodeGeometry) {
175
+ if (name === edge.source || name === edge.target) continue;
176
+ const cx = nodePositions.get(name) ?? 0;
177
+ const cy = geomC.y;
178
+ const hw = geomC.width / 2;
179
+ const hh = geomC.height / 2;
180
+
181
+ // AABB overlap: node C's box intersects the edge's straight-line bounding box
182
+ if (
183
+ cx + hw > edgeMinX &&
184
+ cx - hw < edgeMaxX &&
185
+ cy + hh > edgeMinY &&
186
+ cy - hh < edgeMaxY
187
+ ) {
188
+ penalty += EDGE_NODE_COLLISION_WEIGHT;
189
+ }
190
+ }
191
+ }
192
+ }
193
+
138
194
  return penalty;
139
195
  }
140
196
 
@@ -160,6 +216,13 @@ function reduceCrossings(
160
216
  degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + 1);
161
217
  }
162
218
 
219
+ // Build geometry map for edge-node collision scoring
220
+ const nodeGeometry = new Map<string, NodeGeometry>();
221
+ for (const name of g.nodes()) {
222
+ const pos = g.node(name);
223
+ if (pos) nodeGeometry.set(name, { y: pos.y, width: pos.width, height: pos.height });
224
+ }
225
+
163
226
  // Group nodes by rank
164
227
  const rankMap = new Map<number, string[]>();
165
228
  for (const name of g.nodes()) {
@@ -216,7 +279,7 @@ function reduceCrossings(
216
279
  }
217
280
 
218
281
  // Current penalty
219
- const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees);
282
+ const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees, nodeGeometry);
220
283
 
221
284
  // Try permutations (feasible for partition sizes ≤ 8)
222
285
  let bestPerm = [...partition];
@@ -229,7 +292,7 @@ function reduceCrossings(
229
292
  for (let i = 0; i < perm.length; i++) {
230
293
  testPositions.set(perm[i]!, xSlots[i]!);
231
294
  }
232
- const penalty = computeEdgePenalty(edgeList, testPositions, degrees);
295
+ const penalty = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
233
296
  if (penalty < bestPenalty) {
234
297
  bestPenalty = penalty;
235
298
  bestPerm = [...perm];
@@ -248,14 +311,14 @@ function reduceCrossings(
248
311
  for (let k = 0; k < workingOrder.length; k++) {
249
312
  testPositions.set(workingOrder[k]!, xSlots[k]!);
250
313
  }
251
- const before = computeEdgePenalty(edgeList, testPositions, degrees);
314
+ const before = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
252
315
 
253
316
  [workingOrder[i], workingOrder[i + 1]] = [workingOrder[i + 1]!, workingOrder[i]!];
254
317
  const testPositions2 = new Map(basePositions);
255
318
  for (let k = 0; k < workingOrder.length; k++) {
256
319
  testPositions2.set(workingOrder[k]!, xSlots[k]!);
257
320
  }
258
- const after = computeEdgePenalty(edgeList, testPositions2, degrees);
321
+ const after = computeEdgePenalty(edgeList, testPositions2, degrees, nodeGeometry);
259
322
 
260
323
  if (after < before) {
261
324
  improved = true;
@@ -603,19 +666,12 @@ export function computeC4NodeDimensions(
603
666
  // Legend Helpers
604
667
  // ============================================================
605
668
 
606
- function computeLegendGroups(
607
- tagGroups: OrgTagGroup[],
608
- usedValuesByGroup?: Map<string, Set<string>>
609
- ): C4LegendGroup[] {
669
+ function computeLegendGroups(tagGroups: OrgTagGroup[]): C4LegendGroup[] {
610
670
  const result: C4LegendGroup[] = [];
611
671
 
612
672
  for (const group of tagGroups) {
613
673
  const entries: C4LegendEntry[] = [];
614
674
  for (const entry of group.entries) {
615
- if (usedValuesByGroup) {
616
- const used = usedValuesByGroup.get(group.name.toLowerCase());
617
- if (!used?.has(entry.value.toLowerCase())) continue;
618
- }
619
675
  entries.push({ value: entry.value, color: entry.color });
620
676
  }
621
677
  if (entries.length === 0) continue;
@@ -813,20 +869,9 @@ export function layoutC4Context(
813
869
  let totalWidth = nodes.length > 0 ? maxX - minX + MARGIN * 2 : 0;
814
870
  let totalHeight = nodes.length > 0 ? maxY - minY + MARGIN * 2 : 0;
815
871
 
816
- // Legend
817
- const usedValuesByGroup = new Map<string, Set<string>>();
818
- for (const el of contextElements) {
819
- for (const group of parsed.tagGroups) {
820
- const key = group.name.toLowerCase();
821
- const val = el.metadata[key];
822
- if (val) {
823
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, new Set());
824
- usedValuesByGroup.get(key)!.add(val.toLowerCase());
825
- }
826
- }
827
- }
828
-
829
- const legendGroups = computeLegendGroups(parsed.tagGroups, usedValuesByGroup);
872
+ // Legend: show all defined tag groups and entries so users see the full
873
+ // tag vocabulary regardless of which elements are visible at this view level.
874
+ const legendGroups = computeLegendGroups(parsed.tagGroups);
830
875
 
831
876
  // Position legend below diagram
832
877
  if (legendGroups.length > 0) {
@@ -1236,20 +1281,7 @@ export function layoutC4Containers(
1236
1281
  let totalWidth = maxX - minX + MARGIN * 2;
1237
1282
  let totalHeight = maxY - minY + MARGIN * 2;
1238
1283
 
1239
- // Legend
1240
- const usedValuesByGroup = new Map<string, Set<string>>();
1241
- for (const el of [...containers, ...externals]) {
1242
- for (const group of parsed.tagGroups) {
1243
- const key = group.name.toLowerCase();
1244
- const val = el.metadata[key];
1245
- if (val) {
1246
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, new Set());
1247
- usedValuesByGroup.get(key)!.add(val.toLowerCase());
1248
- }
1249
- }
1250
- }
1251
-
1252
- const legendGroups = computeLegendGroups(parsed.tagGroups, usedValuesByGroup);
1284
+ const legendGroups = computeLegendGroups(parsed.tagGroups);
1253
1285
 
1254
1286
  // Position legend below diagram
1255
1287
  if (legendGroups.length > 0) {
@@ -1722,24 +1754,8 @@ export function layoutC4Components(
1722
1754
  let totalWidth = maxX - minX + MARGIN * 2;
1723
1755
  let totalHeight = maxY - minY + MARGIN * 2;
1724
1756
 
1725
- // Legend
1726
- const usedValuesByGroup = new Map<string, Set<string>>();
1727
- for (const el of [...components, ...externals]) {
1728
- for (const group of parsed.tagGroups) {
1729
- const key = group.name.toLowerCase();
1730
- // Check element + ancestors for inherited values
1731
- let val = el.metadata[key];
1732
- if (!val && components.includes(el)) {
1733
- val = targetContainer.metadata[key] ?? system.metadata[key];
1734
- }
1735
- if (val) {
1736
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, new Set());
1737
- usedValuesByGroup.get(key)!.add(val.toLowerCase());
1738
- }
1739
- }
1740
- }
1741
1757
 
1742
- const legendGroups = computeLegendGroups(parsed.tagGroups, usedValuesByGroup);
1758
+ const legendGroups = computeLegendGroups(parsed.tagGroups);
1743
1759
 
1744
1760
  // Position legend below diagram
1745
1761
  if (legendGroups.length > 0) {
@@ -2104,20 +2120,7 @@ export function layoutC4Deployment(
2104
2120
  let totalWidth = maxX - minX + MARGIN * 2;
2105
2121
  let totalHeight = maxY - minY + MARGIN * 2;
2106
2122
 
2107
- // Legend
2108
- const usedValuesByGroup = new Map<string, Set<string>>();
2109
- for (const r of refEntries) {
2110
- for (const group of parsed.tagGroups) {
2111
- const key = group.name.toLowerCase();
2112
- const val = r.element.metadata[key];
2113
- if (val) {
2114
- if (!usedValuesByGroup.has(key)) usedValuesByGroup.set(key, new Set());
2115
- usedValuesByGroup.get(key)!.add(val.toLowerCase());
2116
- }
2117
- }
2118
- }
2119
-
2120
- const legendGroups = computeLegendGroups(parsed.tagGroups, usedValuesByGroup);
2123
+ const legendGroups = computeLegendGroups(parsed.tagGroups);
2121
2124
 
2122
2125
  if (legendGroups.length > 0) {
2123
2126
  const legendY = totalHeight + MARGIN;