@diagrammo/dgmo 0.4.2 → 0.4.3

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.
Files changed (59) hide show
  1. package/.claude/skills/dgmo-chart/SKILL.md +28 -0
  2. package/.claude/skills/dgmo-generate/SKILL.md +1 -0
  3. package/.claude/skills/dgmo-sequence/SKILL.md +24 -1
  4. package/.cursorrules +27 -2
  5. package/.github/copilot-instructions.md +36 -3
  6. package/.windsurfrules +27 -2
  7. package/README.md +12 -3
  8. package/dist/cli.cjs +197 -154
  9. package/dist/index.cjs +8371 -3200
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +502 -58
  12. package/dist/index.d.ts +502 -58
  13. package/dist/index.js +8594 -3444
  14. package/dist/index.js.map +1 -1
  15. package/docs/ai-integration.md +1 -1
  16. package/docs/language-reference.md +336 -17
  17. package/docs/migration-sequence-color-to-tags.md +98 -0
  18. package/package.json +1 -1
  19. package/src/c4/renderer.ts +1 -20
  20. package/src/class/renderer.ts +1 -11
  21. package/src/cli.ts +40 -0
  22. package/src/d3.ts +92 -2
  23. package/src/dgmo-router.ts +11 -0
  24. package/src/echarts.ts +74 -8
  25. package/src/er/parser.ts +29 -3
  26. package/src/er/renderer.ts +1 -15
  27. package/src/graph/flowchart-parser.ts +7 -30
  28. package/src/graph/flowchart-renderer.ts +62 -69
  29. package/src/graph/layout.ts +5 -0
  30. package/src/graph/state-parser.ts +388 -0
  31. package/src/graph/state-renderer.ts +496 -0
  32. package/src/graph/types.ts +4 -2
  33. package/src/index.ts +42 -1
  34. package/src/infra/compute.ts +1113 -0
  35. package/src/infra/layout.ts +575 -0
  36. package/src/infra/parser.ts +559 -0
  37. package/src/infra/renderer.ts +1509 -0
  38. package/src/infra/roles.ts +60 -0
  39. package/src/infra/serialize.ts +67 -0
  40. package/src/infra/types.ts +221 -0
  41. package/src/infra/validation.ts +192 -0
  42. package/src/initiative-status/layout.ts +56 -61
  43. package/src/initiative-status/renderer.ts +13 -13
  44. package/src/kanban/renderer.ts +1 -24
  45. package/src/org/layout.ts +28 -37
  46. package/src/org/parser.ts +16 -1
  47. package/src/org/renderer.ts +159 -121
  48. package/src/org/resolver.ts +90 -23
  49. package/src/palettes/color-utils.ts +30 -0
  50. package/src/render.ts +2 -0
  51. package/src/sequence/parser.ts +202 -42
  52. package/src/sequence/renderer.ts +576 -113
  53. package/src/sequence/tag-resolution.ts +163 -0
  54. package/src/sitemap/collapse.ts +187 -0
  55. package/src/sitemap/layout.ts +738 -0
  56. package/src/sitemap/parser.ts +489 -0
  57. package/src/sitemap/renderer.ts +774 -0
  58. package/src/sitemap/types.ts +42 -0
  59. package/src/utils/tag-groups.ts +119 -0
@@ -0,0 +1,496 @@
1
+ // ============================================================
2
+ // State Diagram SVG Renderer
3
+ // ============================================================
4
+
5
+ import * as d3Selection from 'd3-selection';
6
+ import * as d3Shape from 'd3-shape';
7
+ import { FONT_FAMILY } from '../fonts';
8
+ import type { PaletteColors } from '../palettes';
9
+ import { mix } from '../palettes/color-utils';
10
+ import type { ParsedGraph } from './types';
11
+ import type { LayoutResult, LayoutNode, LayoutEdge } from './layout';
12
+ import { parseState } from './state-parser';
13
+ import { layoutGraph } from './layout';
14
+
15
+ // ============================================================
16
+ // Constants
17
+ // ============================================================
18
+
19
+ const DIAGRAM_PADDING = 20;
20
+ const MAX_SCALE = 3;
21
+ const NODE_FONT_SIZE = 13;
22
+ const EDGE_LABEL_FONT_SIZE = 11;
23
+ const GROUP_LABEL_FONT_SIZE = 11;
24
+ const EDGE_STROKE_WIDTH = 1.5;
25
+ const NODE_STROKE_WIDTH = 1.5;
26
+ const ARROWHEAD_W = 10;
27
+ const ARROWHEAD_H = 7;
28
+ const PSEUDOSTATE_RADIUS = 10;
29
+ const STATE_CORNER_RADIUS = 10;
30
+ const GROUP_EXTRA_PADDING = 12;
31
+
32
+ // ============================================================
33
+ // Color helpers
34
+ // ============================================================
35
+
36
+ function stateDefaultColor(palette: PaletteColors, colorOff?: boolean): string {
37
+ return colorOff ? palette.textMuted : palette.colors.blue;
38
+ }
39
+
40
+ function stateFill(palette: PaletteColors, isDark: boolean, nodeColor?: string, colorOff?: boolean): string {
41
+ const color = nodeColor ?? stateDefaultColor(palette, colorOff);
42
+ return mix(color, isDark ? palette.surface : palette.bg, 25);
43
+ }
44
+
45
+ function stateStroke(palette: PaletteColors, nodeColor?: string, colorOff?: boolean): string {
46
+ return nodeColor ?? stateDefaultColor(palette, colorOff);
47
+ }
48
+
49
+ // ============================================================
50
+ // Edge path generator
51
+ // ============================================================
52
+
53
+ const lineGenerator = d3Shape.line<{ x: number; y: number }>()
54
+ .x((d) => d.x)
55
+ .y((d) => d.y)
56
+ .curve(d3Shape.curveBasis);
57
+
58
+ // ============================================================
59
+ // Self-loop path
60
+ // ============================================================
61
+
62
+ function selfLoopPath(node: LayoutNode): string {
63
+ const cx = node.x;
64
+ const cy = node.y;
65
+ const r = node.width / 2;
66
+ // Loop from right side, arc above, back to right side
67
+ const startX = cx + r;
68
+ const startY = cy - 5;
69
+ const endX = cx + r;
70
+ const endY = cy + 5;
71
+ const loopR = 25;
72
+ return `M ${startX} ${startY} C ${startX + loopR * 2} ${startY - loopR * 2}, ${endX + loopR * 2} ${endY + loopR * 2}, ${endX} ${endY}`;
73
+ }
74
+
75
+ // ============================================================
76
+ // Main renderer
77
+ // ============================================================
78
+
79
+ type GSelection = d3Selection.Selection<SVGGElement, unknown, null, undefined>;
80
+
81
+ export function renderState(
82
+ container: HTMLDivElement,
83
+ graph: ParsedGraph,
84
+ layout: LayoutResult,
85
+ palette: PaletteColors,
86
+ isDark: boolean,
87
+ onClickItem?: (lineNumber: number) => void,
88
+ exportDims?: { width?: number; height?: number }
89
+ ): void {
90
+ d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
91
+
92
+ const width = exportDims?.width ?? container.clientWidth;
93
+ const height = exportDims?.height ?? container.clientHeight;
94
+ if (width <= 0 || height <= 0) return;
95
+
96
+ const titleHeight = graph.title ? 40 : 0;
97
+
98
+ const diagramW = layout.width;
99
+ const diagramH = layout.height;
100
+ const availH = height - titleHeight;
101
+ const scaleX = (width - DIAGRAM_PADDING * 2) / diagramW;
102
+ const scaleY = (availH - DIAGRAM_PADDING * 2) / diagramH;
103
+ const scale = Math.min(MAX_SCALE, scaleX, scaleY);
104
+
105
+ const scaledW = diagramW * scale;
106
+ const offsetX = (width - scaledW) / 2;
107
+ const offsetY = titleHeight + DIAGRAM_PADDING;
108
+
109
+ const svg = d3Selection
110
+ .select(container)
111
+ .append('svg')
112
+ .attr('width', width)
113
+ .attr('height', height)
114
+ .style('font-family', FONT_FAMILY);
115
+
116
+ // Defs: arrowhead markers
117
+ const defs = svg.append('defs');
118
+
119
+ defs
120
+ .append('marker')
121
+ .attr('id', 'st-arrow')
122
+ .attr('viewBox', `0 0 ${ARROWHEAD_W} ${ARROWHEAD_H}`)
123
+ .attr('refX', ARROWHEAD_W)
124
+ .attr('refY', ARROWHEAD_H / 2)
125
+ .attr('markerWidth', ARROWHEAD_W)
126
+ .attr('markerHeight', ARROWHEAD_H)
127
+ .attr('orient', 'auto')
128
+ .append('polygon')
129
+ .attr('points', `0,0 ${ARROWHEAD_W},${ARROWHEAD_H / 2} 0,${ARROWHEAD_H}`)
130
+ .attr('fill', palette.textMuted);
131
+
132
+ // Custom colored markers
133
+ const edgeColors = new Set<string>();
134
+ for (const edge of layout.edges) {
135
+ if (edge.color) edgeColors.add(edge.color);
136
+ }
137
+ for (const color of edgeColors) {
138
+ const id = `st-arrow-${color.replace('#', '')}`;
139
+ defs
140
+ .append('marker')
141
+ .attr('id', id)
142
+ .attr('viewBox', `0 0 ${ARROWHEAD_W} ${ARROWHEAD_H}`)
143
+ .attr('refX', ARROWHEAD_W)
144
+ .attr('refY', ARROWHEAD_H / 2)
145
+ .attr('markerWidth', ARROWHEAD_W)
146
+ .attr('markerHeight', ARROWHEAD_H)
147
+ .attr('orient', 'auto')
148
+ .append('polygon')
149
+ .attr('points', `0,0 ${ARROWHEAD_W},${ARROWHEAD_H / 2} 0,${ARROWHEAD_H}`)
150
+ .attr('fill', color);
151
+ }
152
+
153
+ // Title
154
+ if (graph.title) {
155
+ const titleEl = svg
156
+ .append('text')
157
+ .attr('class', 'chart-title')
158
+ .attr('x', width / 2)
159
+ .attr('y', 30)
160
+ .attr('text-anchor', 'middle')
161
+ .attr('fill', palette.text)
162
+ .attr('font-size', '20px')
163
+ .attr('font-weight', '700')
164
+ .style('cursor', onClickItem && graph.titleLineNumber ? 'pointer' : 'default')
165
+ .text(graph.title);
166
+
167
+ if (graph.titleLineNumber) {
168
+ titleEl.attr('data-line-number', graph.titleLineNumber);
169
+ if (onClickItem) {
170
+ titleEl
171
+ .on('click', () => onClickItem(graph.titleLineNumber!))
172
+ .on('mouseenter', function () { d3Selection.select(this).attr('opacity', 0.7); })
173
+ .on('mouseleave', function () { d3Selection.select(this).attr('opacity', 1); });
174
+ }
175
+ }
176
+ }
177
+
178
+ // Content group
179
+ const contentG = svg
180
+ .append('g')
181
+ .attr('transform', `translate(${offsetX}, ${offsetY}) scale(${scale})`);
182
+
183
+ // Render groups (background layer)
184
+ for (const group of layout.groups) {
185
+ if (group.width === 0 && group.height === 0) continue;
186
+ const gx = group.x - GROUP_EXTRA_PADDING;
187
+ const gy = group.y - GROUP_EXTRA_PADDING - GROUP_LABEL_FONT_SIZE - 4;
188
+ const gw = group.width + GROUP_EXTRA_PADDING * 2;
189
+ const gh = group.height + GROUP_EXTRA_PADDING * 2 + GROUP_LABEL_FONT_SIZE + 4;
190
+
191
+ const fillColor = group.color
192
+ ? mix(group.color, isDark ? palette.surface : palette.bg, 10)
193
+ : isDark
194
+ ? palette.surface
195
+ : mix(palette.border, palette.bg, 30);
196
+ const strokeColor = group.color ?? palette.textMuted;
197
+
198
+ const groupWrapper = contentG
199
+ .append('g')
200
+ .attr('class', 'st-group-wrapper')
201
+ .attr('data-line-number', String(group.lineNumber))
202
+ .attr('data-group-id', group.id);
203
+
204
+ if (onClickItem) {
205
+ groupWrapper.style('cursor', 'pointer').on('click', () => {
206
+ onClickItem(group.lineNumber);
207
+ });
208
+ }
209
+
210
+ groupWrapper
211
+ .append('rect')
212
+ .attr('x', gx)
213
+ .attr('y', gy)
214
+ .attr('width', gw)
215
+ .attr('height', gh)
216
+ .attr('rx', 6)
217
+ .attr('fill', fillColor)
218
+ .attr('stroke', strokeColor)
219
+ .attr('stroke-width', 1)
220
+ .attr('stroke-opacity', 0.5)
221
+ .attr('class', 'st-group');
222
+
223
+ groupWrapper
224
+ .append('text')
225
+ .attr('x', gx + 8)
226
+ .attr('y', gy + GROUP_LABEL_FONT_SIZE + 4)
227
+ .attr('fill', strokeColor)
228
+ .attr('font-size', GROUP_LABEL_FONT_SIZE)
229
+ .attr('font-weight', 'bold')
230
+ .attr('opacity', 0.7)
231
+ .attr('class', 'st-group-label')
232
+ .text(group.label);
233
+ }
234
+
235
+ // Build self-loop lookup
236
+ const selfLoopEdges = new Set<number>();
237
+ for (const edge of layout.edges) {
238
+ if (edge.source === edge.target) selfLoopEdges.add(edge.lineNumber);
239
+ }
240
+
241
+ // Build node position map for self-loops
242
+ const nodePositionMap = new Map<string, LayoutNode>();
243
+ for (const node of layout.nodes) {
244
+ nodePositionMap.set(node.id, node);
245
+ }
246
+
247
+ // Compute edge label positions with perpendicular offset to hug their path,
248
+ // then resolve remaining collisions.
249
+ const LABEL_CHAR_W = 7;
250
+ const LABEL_PAD = 8;
251
+ const LABEL_H = 16;
252
+ const PERP_OFFSET = 10; // px offset perpendicular to edge direction
253
+
254
+ interface LabelPos { x: number; y: number; w: number; h: number; edgeIdx: number }
255
+ const labelPositions: LabelPos[] = [];
256
+
257
+ for (let ei = 0; ei < layout.edges.length; ei++) {
258
+ const edge = layout.edges[ei];
259
+ if (!edge.label) continue;
260
+ const bgW = edge.label.length * LABEL_CHAR_W + LABEL_PAD;
261
+ let lx: number, ly: number;
262
+
263
+ if (edge.source === edge.target) {
264
+ const node = nodePositionMap.get(edge.source);
265
+ if (!node) continue;
266
+ lx = node.x + node.width / 2 + 30;
267
+ ly = node.y;
268
+ } else if (edge.points.length >= 2) {
269
+ const midIdx = Math.floor(edge.points.length / 2);
270
+ const midPt = edge.points[midIdx];
271
+ // Compute perpendicular offset from edge direction at midpoint
272
+ const prev = edge.points[Math.max(0, midIdx - 1)];
273
+ const next = edge.points[Math.min(edge.points.length - 1, midIdx + 1)];
274
+ const dx = next.x - prev.x;
275
+ const dy = next.y - prev.y;
276
+ const len = Math.sqrt(dx * dx + dy * dy);
277
+ if (len > 0) {
278
+ // Normal vector (right-hand side of travel direction)
279
+ lx = midPt.x + (-dy / len) * PERP_OFFSET;
280
+ ly = midPt.y + (dx / len) * PERP_OFFSET;
281
+ } else {
282
+ lx = midPt.x;
283
+ ly = midPt.y;
284
+ }
285
+ } else {
286
+ continue;
287
+ }
288
+ labelPositions.push({ x: lx, y: ly, w: bgW, h: LABEL_H, edgeIdx: ei });
289
+ }
290
+
291
+ // Resolve remaining label collisions: nudge overlapping labels apart.
292
+ labelPositions.sort((a, b) => a.y - b.y);
293
+ for (let i = 0; i < labelPositions.length; i++) {
294
+ for (let j = i + 1; j < labelPositions.length; j++) {
295
+ const a = labelPositions[i];
296
+ const b = labelPositions[j];
297
+ const overlapX = Math.abs(a.x - b.x) < (a.w + b.w) / 2;
298
+ const overlapY = Math.abs(a.y - b.y) < (a.h + b.h) / 2;
299
+ if (overlapX && overlapY) {
300
+ b.y = a.y + (a.h + b.h) / 2 + 2;
301
+ }
302
+ }
303
+ }
304
+
305
+ // Build lookup: edgeIdx → adjusted label position
306
+ const labelPosMap = new Map<number, LabelPos>();
307
+ for (const lp of labelPositions) labelPosMap.set(lp.edgeIdx, lp);
308
+
309
+ // Render edges (middle layer)
310
+ for (let ei = 0; ei < layout.edges.length; ei++) {
311
+ const edge = layout.edges[ei];
312
+ const edgeG = contentG
313
+ .append('g')
314
+ .attr('class', 'st-edge-group')
315
+ .attr('data-line-number', String(edge.lineNumber));
316
+
317
+ const edgeColor = edge.color ?? palette.textMuted;
318
+ const markerId = edge.color
319
+ ? `st-arrow-${edge.color.replace('#', '')}`
320
+ : 'st-arrow';
321
+
322
+ if (edge.source === edge.target) {
323
+ // Self-loop
324
+ const node = nodePositionMap.get(edge.source);
325
+ if (node) {
326
+ edgeG
327
+ .append('path')
328
+ .attr('d', selfLoopPath(node))
329
+ .attr('fill', 'none')
330
+ .attr('stroke', edgeColor)
331
+ .attr('stroke-width', EDGE_STROKE_WIDTH)
332
+ .attr('marker-end', `url(#${markerId})`)
333
+ .attr('class', 'st-edge');
334
+
335
+ const lp = labelPosMap.get(ei);
336
+ if (edge.label && lp) {
337
+ edgeG.append('rect')
338
+ .attr('x', lp.x - lp.w / 2)
339
+ .attr('y', lp.y - lp.h / 2 - 1)
340
+ .attr('width', lp.w)
341
+ .attr('height', lp.h)
342
+ .attr('rx', 3)
343
+ .attr('fill', palette.bg)
344
+ .attr('opacity', 0.85)
345
+ .attr('class', 'st-edge-label-bg');
346
+ edgeG.append('text')
347
+ .attr('x', lp.x)
348
+ .attr('y', lp.y + 4)
349
+ .attr('text-anchor', 'middle')
350
+ .attr('fill', edgeColor)
351
+ .attr('font-size', EDGE_LABEL_FONT_SIZE)
352
+ .attr('class', 'st-edge-label')
353
+ .text(edge.label);
354
+ }
355
+ }
356
+ } else if (edge.points.length >= 2) {
357
+ const pathD = lineGenerator(edge.points);
358
+ if (pathD) {
359
+ edgeG
360
+ .append('path')
361
+ .attr('d', pathD)
362
+ .attr('fill', 'none')
363
+ .attr('stroke', edgeColor)
364
+ .attr('stroke-width', EDGE_STROKE_WIDTH)
365
+ .attr('marker-end', `url(#${markerId})`)
366
+ .attr('class', 'st-edge');
367
+ }
368
+
369
+ const lp = labelPosMap.get(ei);
370
+ if (edge.label && lp) {
371
+ edgeG.append('rect')
372
+ .attr('x', lp.x - lp.w / 2)
373
+ .attr('y', lp.y - lp.h / 2 - 1)
374
+ .attr('width', lp.w)
375
+ .attr('height', lp.h)
376
+ .attr('rx', 3)
377
+ .attr('fill', palette.bg)
378
+ .attr('opacity', 0.85)
379
+ .attr('class', 'st-edge-label-bg');
380
+ edgeG.append('text')
381
+ .attr('x', lp.x)
382
+ .attr('y', lp.y + 4)
383
+ .attr('text-anchor', 'middle')
384
+ .attr('fill', edgeColor)
385
+ .attr('font-size', EDGE_LABEL_FONT_SIZE)
386
+ .attr('class', 'st-edge-label')
387
+ .text(edge.label);
388
+ }
389
+ }
390
+ }
391
+
392
+ // Render nodes (top layer)
393
+ const colorOff = graph.options?.color === 'off';
394
+ for (const node of layout.nodes) {
395
+ const nodeG = contentG
396
+ .append('g')
397
+ .attr('transform', `translate(${node.x}, ${node.y})`)
398
+ .attr('class', 'st-node')
399
+ .attr('data-line-number', String(node.lineNumber))
400
+ .attr('data-node-id', node.id);
401
+
402
+ if (onClickItem) {
403
+ nodeG.style('cursor', 'pointer').on('click', () => {
404
+ onClickItem(node.lineNumber);
405
+ });
406
+ }
407
+
408
+ if (node.shape === 'pseudostate') {
409
+ // Filled circle
410
+ nodeG
411
+ .append('circle')
412
+ .attr('cx', 0)
413
+ .attr('cy', 0)
414
+ .attr('r', PSEUDOSTATE_RADIUS)
415
+ .attr('fill', palette.text)
416
+ .attr('stroke', 'none');
417
+ } else {
418
+ // State — rounded rectangle
419
+ const w = node.width;
420
+ const h = node.height;
421
+ nodeG
422
+ .append('rect')
423
+ .attr('x', -w / 2)
424
+ .attr('y', -h / 2)
425
+ .attr('width', w)
426
+ .attr('height', h)
427
+ .attr('rx', STATE_CORNER_RADIUS)
428
+ .attr('ry', STATE_CORNER_RADIUS)
429
+ .attr('fill', stateFill(palette, isDark, node.color, colorOff))
430
+ .attr('stroke', stateStroke(palette, node.color, colorOff))
431
+ .attr('stroke-width', NODE_STROKE_WIDTH);
432
+
433
+ // Label
434
+ nodeG
435
+ .append('text')
436
+ .attr('x', 0)
437
+ .attr('y', 0)
438
+ .attr('text-anchor', 'middle')
439
+ .attr('dominant-baseline', 'central')
440
+ .attr('fill', palette.text)
441
+ .attr('font-size', NODE_FONT_SIZE)
442
+ .text(node.label);
443
+ }
444
+ }
445
+ }
446
+
447
+ // ============================================================
448
+ // Export convenience function
449
+ // ============================================================
450
+
451
+ export function renderStateForExport(
452
+ content: string,
453
+ theme: 'light' | 'dark' | 'transparent',
454
+ palette: PaletteColors
455
+ ): string {
456
+ const parsed = parseState(content, palette);
457
+ if (parsed.error || parsed.nodes.length === 0) return '';
458
+
459
+ const layout = layoutGraph(parsed);
460
+ const isDark = theme === 'dark';
461
+
462
+ const container = document.createElement('div');
463
+ const exportWidth = layout.width + DIAGRAM_PADDING * 2;
464
+ const exportHeight = layout.height + DIAGRAM_PADDING * 2 + (parsed.title ? 40 : 0);
465
+ container.style.width = `${exportWidth}px`;
466
+ container.style.height = `${exportHeight}px`;
467
+ container.style.position = 'absolute';
468
+ container.style.left = '-9999px';
469
+ document.body.appendChild(container);
470
+
471
+ try {
472
+ renderState(
473
+ container,
474
+ parsed,
475
+ layout,
476
+ palette,
477
+ isDark,
478
+ undefined,
479
+ { width: exportWidth, height: exportHeight }
480
+ );
481
+
482
+ const svgEl = container.querySelector('svg');
483
+ if (!svgEl) return '';
484
+
485
+ if (theme === 'transparent') {
486
+ svgEl.style.background = 'none';
487
+ }
488
+
489
+ svgEl.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
490
+ svgEl.style.fontFamily = FONT_FAMILY;
491
+
492
+ return svgEl.outerHTML;
493
+ } finally {
494
+ document.body.removeChild(container);
495
+ }
496
+ }
@@ -4,7 +4,9 @@ export type GraphShape =
4
4
  | 'decision' // <> — diamond
5
5
  | 'io' // // — parallelogram
6
6
  | 'subroutine' // [[]] — double-bordered rectangle
7
- | 'document'; // [~] — wavy-bottom rectangle
7
+ | 'document' // [~] — wavy-bottom rectangle
8
+ | 'state' // state diagram — rounded rectangle
9
+ | 'pseudostate'; // [*] — filled circle (start/end)
8
10
 
9
11
  export type GraphDirection = 'TB' | 'LR';
10
12
 
@@ -36,7 +38,7 @@ export interface GraphGroup {
36
38
  import type { DgmoError } from '../diagnostics';
37
39
 
38
40
  export interface ParsedGraph {
39
- type: 'flowchart';
41
+ type: 'flowchart' | 'state';
40
42
  title?: string;
41
43
  titleLineNumber?: number;
42
44
  direction: GraphDirection;
package/src/index.ts CHANGED
@@ -77,6 +77,9 @@ export type { ParsedQuadrant } from './dgmo-mermaid';
77
77
 
78
78
  export { parseFlowchart, looksLikeFlowchart } from './graph/flowchart-parser';
79
79
 
80
+ export { parseState, looksLikeState } from './graph/state-parser';
81
+ export { renderState, renderStateForExport } from './graph/state-renderer';
82
+
80
83
  export { parseClassDiagram, looksLikeClassDiagram } from './class/parser';
81
84
 
82
85
  export type {
@@ -215,11 +218,49 @@ export type {
215
218
 
216
219
  export { renderInitiativeStatus, renderInitiativeStatusForExport } from './initiative-status/renderer';
217
220
 
221
+ export { parseSitemap, looksLikeSitemap } from './sitemap/parser';
222
+
223
+ export type {
224
+ ParsedSitemap,
225
+ SitemapNode,
226
+ SitemapEdge,
227
+ SitemapDirection,
228
+ } from './sitemap/types';
229
+
230
+ export { layoutSitemap } from './sitemap/layout';
231
+ export type {
232
+ SitemapLayoutResult,
233
+ SitemapLayoutNode,
234
+ SitemapLayoutEdge,
235
+ SitemapContainerBounds,
236
+ SitemapLegendGroup,
237
+ SitemapLegendEntry,
238
+ } from './sitemap/layout';
239
+
240
+ export { renderSitemap, renderSitemapForExport } from './sitemap/renderer';
241
+
242
+ export { collapseSitemapTree } from './sitemap/collapse';
243
+
244
+ // ── Infra Chart ────────────────────────────────────────────
245
+ export { parseInfra } from './infra/parser';
246
+ export type { ParsedInfra, InfraNode, InfraEdge, InfraGroup, InfraTagGroup, InfraProperty, InfraDiagnostic, InfraScenario, InfraComputeParams, InfraBehaviorKey } from './infra/types';
247
+ export { INFRA_BEHAVIOR_KEYS } from './infra/types';
248
+ export { computeInfra } from './infra/compute';
249
+ export type { ComputedInfraModel, ComputedInfraNode, ComputedInfraEdge, InfraLatencyPercentiles, InfraAvailabilityPercentiles, InfraCbState } from './infra/types';
250
+ export { validateInfra, validateComputed } from './infra/validation';
251
+ export { inferRoles, collectDiagramRoles } from './infra/roles';
252
+ export type { InfraRole } from './infra/roles';
253
+ export { layoutInfra } from './infra/layout';
254
+ export type { InfraLayoutResult, InfraLayoutNode, InfraLayoutEdge, InfraLayoutGroup } from './infra/layout';
255
+ export { renderInfra, parseAndLayoutInfra, computeInfraLegendGroups } from './infra/renderer';
256
+ export type { InfraLegendGroup, InfraPlaybackState } from './infra/renderer';
257
+ export type { CollapsedSitemapResult } from './sitemap/collapse';
258
+
218
259
  export { collapseOrgTree } from './org/collapse';
219
260
  export type { CollapsedOrgResult } from './org/collapse';
220
261
 
221
262
  export { resolveOrgImports } from './org/resolver';
222
- export type { ReadFileFn, ResolveImportsResult } from './org/resolver';
263
+ export type { ReadFileFn, ResolveImportsResult, ImportSource } from './org/resolver';
223
264
 
224
265
  export { layoutGraph } from './graph/layout';
225
266
  export type {