@diagrammo/dgmo 0.8.2 → 0.8.4

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 (120) hide show
  1. package/.claude/commands/dgmo-diagram-this.md +60 -0
  2. package/.claude/commands/dgmo-document-project.md +128 -0
  3. package/.claude/commands/dgmo.md +185 -50
  4. package/.cursorrules +32 -37
  5. package/.github/copilot-instructions.md +35 -44
  6. package/.windsurfrules +32 -37
  7. package/README.md +4 -4
  8. package/dist/cli.cjs +189 -194
  9. package/dist/editor.cjs +336 -0
  10. package/dist/editor.cjs.map +1 -0
  11. package/dist/editor.d.cts +27 -0
  12. package/dist/editor.d.ts +27 -0
  13. package/dist/editor.js +305 -0
  14. package/dist/editor.js.map +1 -0
  15. package/dist/index.cjs +3699 -1564
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +7 -6
  18. package/dist/index.d.ts +7 -6
  19. package/dist/index.js +3699 -1564
  20. package/dist/index.js.map +1 -1
  21. package/docs/language-reference.md +822 -1060
  22. package/gallery/fixtures/arc.dgmo +18 -0
  23. package/gallery/fixtures/area.dgmo +19 -0
  24. package/gallery/fixtures/bar-stacked.dgmo +10 -0
  25. package/gallery/fixtures/bar.dgmo +10 -0
  26. package/gallery/fixtures/c4-full.dgmo +52 -0
  27. package/gallery/fixtures/c4.dgmo +17 -0
  28. package/gallery/fixtures/chord.dgmo +12 -0
  29. package/gallery/fixtures/class-basic.dgmo +14 -0
  30. package/gallery/fixtures/class-full.dgmo +43 -0
  31. package/gallery/fixtures/doughnut.dgmo +8 -0
  32. package/gallery/fixtures/flowchart-basic.dgmo +3 -0
  33. package/gallery/fixtures/flowchart-colors.dgmo +5 -0
  34. package/gallery/fixtures/flowchart-complex.dgmo +17 -0
  35. package/gallery/fixtures/flowchart-decision.dgmo +5 -0
  36. package/gallery/fixtures/flowchart-full.dgmo +13 -0
  37. package/gallery/fixtures/flowchart-groups.dgmo +10 -0
  38. package/gallery/fixtures/flowchart-loop.dgmo +7 -0
  39. package/gallery/fixtures/flowchart-nested.dgmo +7 -0
  40. package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
  41. package/gallery/fixtures/function.dgmo +8 -0
  42. package/gallery/fixtures/funnel.dgmo +7 -0
  43. package/gallery/fixtures/gantt-full.dgmo +49 -0
  44. package/gallery/fixtures/gantt.dgmo +42 -0
  45. package/gallery/fixtures/heatmap.dgmo +8 -0
  46. package/gallery/fixtures/infra-full.dgmo +78 -0
  47. package/gallery/fixtures/infra-overload.dgmo +25 -0
  48. package/gallery/fixtures/infra.dgmo +47 -0
  49. package/gallery/fixtures/initiative-status-full.dgmo +46 -0
  50. package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
  51. package/gallery/fixtures/initiative-status.dgmo +9 -0
  52. package/gallery/fixtures/line.dgmo +19 -0
  53. package/gallery/fixtures/multi-line.dgmo +11 -0
  54. package/gallery/fixtures/org-basic.dgmo +16 -0
  55. package/gallery/fixtures/org-full.dgmo +69 -0
  56. package/gallery/fixtures/org-teams.dgmo +25 -0
  57. package/gallery/fixtures/pie.dgmo +9 -0
  58. package/gallery/fixtures/polar-area.dgmo +8 -0
  59. package/gallery/fixtures/quadrant.dgmo +18 -0
  60. package/gallery/fixtures/radar.dgmo +8 -0
  61. package/gallery/fixtures/sankey.dgmo +31 -0
  62. package/gallery/fixtures/scatter.dgmo +21 -0
  63. package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
  64. package/gallery/fixtures/sequence-tags.dgmo +41 -0
  65. package/gallery/fixtures/sequence.dgmo +35 -0
  66. package/gallery/fixtures/sitemap-basic.dgmo +12 -0
  67. package/gallery/fixtures/sitemap-full.dgmo +156 -0
  68. package/gallery/fixtures/slope.dgmo +8 -0
  69. package/gallery/fixtures/spr-eras.dgmo +62 -0
  70. package/gallery/fixtures/state.dgmo +30 -0
  71. package/gallery/fixtures/timeline-intraday.dgmo +14 -0
  72. package/gallery/fixtures/timeline.dgmo +32 -0
  73. package/gallery/fixtures/venn.dgmo +10 -0
  74. package/gallery/fixtures/wordcloud.dgmo +24 -0
  75. package/package.json +51 -2
  76. package/src/c4/layout.ts +372 -90
  77. package/src/c4/parser.ts +113 -62
  78. package/src/chart.ts +149 -64
  79. package/src/class/parser.ts +84 -28
  80. package/src/class/renderer.ts +2 -2
  81. package/src/cli.ts +179 -77
  82. package/src/completion.ts +381 -182
  83. package/src/d3.ts +1026 -428
  84. package/src/dgmo-mermaid.ts +16 -13
  85. package/src/dgmo-router.ts +70 -24
  86. package/src/echarts.ts +682 -169
  87. package/src/editor/dgmo.grammar +69 -0
  88. package/src/editor/dgmo.grammar.d.ts +2 -0
  89. package/src/editor/dgmo.grammar.js +18 -0
  90. package/src/editor/dgmo.grammar.terms.d.ts +5 -0
  91. package/src/editor/dgmo.grammar.terms.js +35 -0
  92. package/src/editor/highlight.ts +36 -0
  93. package/src/editor/index.ts +28 -0
  94. package/src/editor/keywords.ts +220 -0
  95. package/src/editor/tokens.ts +30 -0
  96. package/src/er/parser.ts +55 -29
  97. package/src/er/renderer.ts +112 -53
  98. package/src/gantt/calculator.ts +91 -29
  99. package/src/gantt/parser.ts +291 -97
  100. package/src/gantt/renderer.ts +1120 -350
  101. package/src/graph/flowchart-parser.ts +48 -75
  102. package/src/graph/state-parser.ts +54 -27
  103. package/src/infra/parser.ts +161 -177
  104. package/src/infra/renderer.ts +723 -271
  105. package/src/infra/types.ts +0 -1
  106. package/src/initiative-status/parser.ts +144 -56
  107. package/src/kanban/parser.ts +27 -19
  108. package/src/org/layout.ts +111 -44
  109. package/src/org/parser.ts +71 -27
  110. package/src/org/resolver.ts +3 -3
  111. package/src/palettes/index.ts +3 -2
  112. package/src/render.ts +1 -2
  113. package/src/sequence/parser.ts +209 -100
  114. package/src/sitemap/parser.ts +73 -44
  115. package/src/utils/arrows.ts +2 -22
  116. package/src/utils/duration.ts +39 -21
  117. package/src/utils/legend-constants.ts +0 -2
  118. package/src/utils/parsing.ts +82 -72
  119. package/src/utils/tag-groups.ts +4 -41
  120. package/src/infra/serialize.ts +0 -67
package/src/c4/parser.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  measureIndent,
12
12
  extractColor,
13
13
  parsePipeMetadata,
14
- MULTIPLE_PIPE_WARNING,
14
+ MULTIPLE_PIPE_ERROR,
15
15
  parseFirstLine,
16
16
  OPTION_NOCOLON_RE,
17
17
  } from '../utils/parsing';
@@ -39,7 +39,8 @@ const ELEMENT_RE = /^(person|system|container|component)\s+(.+)$/i;
39
39
  const IS_A_RE = /\s+is\s+a(?:n)?\s+(\w+)\s*$/i;
40
40
 
41
41
  /** Matches `Name is a <type>` declarations (new preferred syntax) */
42
- const C4_IS_A_RE = /^([^:]+?)\s+is\s+an?\s+(person|system|container|component|external|database)\b(.*)$/i;
42
+ const C4_IS_A_RE =
43
+ /^([^:]+?)\s+is\s+an?\s+(person|system|container|component|external|database)\b(.*)$/i;
43
44
 
44
45
  /** Matches relationship arrows: `->`, `~>`, `<->`, `<~>` */
45
46
  const RELATIONSHIP_RE = /^(<?-?>|<?~?>)\s*(.+)$/;
@@ -56,14 +57,13 @@ const SECTION_HEADER_RE = /^(containers|components|deployment)\s*$/i;
56
57
  /** Matches `container X` references inside deployment nodes */
57
58
  const CONTAINER_REF_RE = /^container\s+(.+)$/i;
58
59
 
59
- /** Matches indented metadata: `key value` (space-separated, no colon) */
60
- const METADATA_RE = /^([a-z][a-z0-9-]*)\s+(.+)$/i;
60
+ /** Matches indented metadata: `key: value` (colon-separated) */
61
+ const METADATA_RE = /^([a-z][a-z0-9-]*):\s+(.+)$/i;
61
62
 
62
63
  // ============================================================
63
64
  // Helpers
64
65
  // ============================================================
65
66
 
66
-
67
67
  const VALID_ELEMENT_TYPES = new Set<string>([
68
68
  'person',
69
69
  'system',
@@ -81,10 +81,10 @@ const VALID_SHAPES = new Set<string>([
81
81
  ]);
82
82
 
83
83
  /** Known top-level option keys for C4 diagrams. */
84
- const KNOWN_C4_OPTIONS = new Set<string>([
85
- 'layout',
86
- 'direction',
87
- ]);
84
+ const KNOWN_C4_OPTIONS = new Set<string>(['layout']);
85
+
86
+ /** Known C4 boolean options (bare keyword = on). */
87
+ const KNOWN_C4_BOOLEANS = new Set<string>(['direction-tb']);
88
88
 
89
89
  const ALL_CHART_TYPES = [
90
90
  'c4',
@@ -106,9 +106,7 @@ const ALL_CHART_TYPES = [
106
106
  ];
107
107
 
108
108
  /** Map from ParticipantType inference → C4Shape */
109
- function participantTypeToC4Shape(
110
- pType: string,
111
- ): C4Shape {
109
+ function participantTypeToC4Shape(pType: string): C4Shape {
112
110
  switch (pType) {
113
111
  case 'database':
114
112
  return 'database';
@@ -151,7 +149,6 @@ function parseArrowType(arrow: string): C4ArrowType | null {
151
149
  }
152
150
  }
153
151
 
154
-
155
152
  // ============================================================
156
153
  // Stack entry types
157
154
  // ============================================================
@@ -192,10 +189,7 @@ type StackEntry =
192
189
  // Parser
193
190
  // ============================================================
194
191
 
195
- export function parseC4(
196
- content: string,
197
- palette?: PaletteColors,
198
- ): ParsedC4 {
192
+ export function parseC4(content: string, palette?: PaletteColors): ParsedC4 {
199
193
  const result: ParsedC4 = {
200
194
  title: null,
201
195
  titleLineNumber: null,
@@ -208,10 +202,15 @@ export function parseC4(
208
202
  error: null,
209
203
  };
210
204
 
211
- const pushError = (line: number, message: string, severity: 'error' | 'warning' = 'error'): void => {
205
+ const pushError = (
206
+ line: number,
207
+ message: string,
208
+ severity: 'error' | 'warning' = 'error'
209
+ ): void => {
212
210
  const diag = makeDgmoError(line, message, severity);
213
211
  result.diagnostics.push(diag);
214
- if (!result.error && severity === 'error') result.error = formatDgmoError(diag);
212
+ if (!result.error && severity === 'error')
213
+ result.error = formatDgmoError(diag);
215
214
  };
216
215
 
217
216
  const fail = (line: number, message: string): ParsedC4 => {
@@ -286,10 +285,6 @@ export function parseC4(
286
285
  pushError(lineNumber, 'Tag groups must appear before content');
287
286
  continue;
288
287
  }
289
- if (tagBlockMatch.deprecated) {
290
- pushError(lineNumber, `'## ${tagBlockMatch.name}' is no longer supported — use 'tag: ${tagBlockMatch.name}' instead`);
291
- continue;
292
- }
293
288
  currentTagGroup = {
294
289
  name: tagBlockMatch.name,
295
290
  alias: tagBlockMatch.alias,
@@ -297,14 +292,23 @@ export function parseC4(
297
292
  lineNumber,
298
293
  };
299
294
  if (tagBlockMatch.alias) {
300
- aliasMap.set(tagBlockMatch.alias.toLowerCase(), tagBlockMatch.name.toLowerCase());
295
+ aliasMap.set(
296
+ tagBlockMatch.alias.toLowerCase(),
297
+ tagBlockMatch.name.toLowerCase()
298
+ );
301
299
  }
302
300
  result.tagGroups.push(currentTagGroup);
303
301
  continue;
304
302
  }
305
303
 
306
- // Generic header options (space-separated: `key value`)
304
+ // Generic header options (space-separated: `key value` or bare boolean)
307
305
  if (!contentStarted && !currentTagGroup && measureIndent(line) === 0) {
306
+ // Bare boolean options
307
+ if (KNOWN_C4_BOOLEANS.has(trimmed.toLowerCase())) {
308
+ result.options[trimmed.toLowerCase()] = 'on';
309
+ continue;
310
+ }
311
+
308
312
  const optMatch = trimmed.match(OPTION_NOCOLON_RE);
309
313
  if (optMatch) {
310
314
  const key = optMatch[1].trim().toLowerCase();
@@ -323,7 +327,7 @@ export function parseC4(
323
327
  if (!color) {
324
328
  pushError(
325
329
  lineNumber,
326
- `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`,
330
+ `Expected 'Value(color)' in tag group '${currentTagGroup.name}'`
327
331
  );
328
332
  continue;
329
333
  }
@@ -338,7 +342,7 @@ export function parseC4(
338
342
  });
339
343
  continue;
340
344
  }
341
- currentTagGroup = null;
345
+ currentTagGroup = null; // eslint-disable-line no-useless-assignment
342
346
  }
343
347
 
344
348
  // --- Content phase ---
@@ -361,7 +365,10 @@ export function parseC4(
361
365
  }
362
366
 
363
367
  // Check for top-level non-deployment content (section ended)
364
- if (indent === 0 && (C4_IS_A_RE.test(trimmed) || ELEMENT_RE.test(trimmed))) {
368
+ if (
369
+ indent === 0 &&
370
+ (C4_IS_A_RE.test(trimmed) || ELEMENT_RE.test(trimmed))
371
+ ) {
365
372
  inDeployment = false;
366
373
  // Fall through to element parsing below
367
374
  } else {
@@ -371,10 +378,13 @@ export function parseC4(
371
378
  const refName = refMatch[1].trim();
372
379
  if (deployStack.length > 0) {
373
380
  deployStack[deployStack.length - 1].node.containerRefs.push(
374
- refName,
381
+ refName
375
382
  );
376
383
  } else {
377
- pushError(lineNumber, `"container ${refName}" must be inside a deployment node`);
384
+ pushError(
385
+ lineNumber,
386
+ `"container ${refName}" must be inside a deployment node`
387
+ );
378
388
  }
379
389
  continue;
380
390
  }
@@ -382,8 +392,13 @@ export function parseC4(
382
392
  // Otherwise it's a deployment node (possibly with pipe metadata)
383
393
  const segments = trimmed.split('|').map((s) => s.trim());
384
394
  const nodeName = segments[0];
385
- const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, 'warning'));
386
- const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
395
+ const metadata = parsePipeMetadata(segments, aliasMap, () =>
396
+ pushError(lineNumber, MULTIPLE_PIPE_ERROR)
397
+ );
398
+ const shape = inferC4Shape(
399
+ nodeName,
400
+ metadata.tech ?? metadata.technology
401
+ );
387
402
 
388
403
  const dNode: C4DeploymentNode = {
389
404
  name: nodeName,
@@ -417,8 +432,9 @@ export function parseC4(
417
432
  // containers / components must be inside an element
418
433
  const parentEntry = findParentElement(indent, stack);
419
434
  if (parentEntry) {
420
- parentEntry.element.sectionHeader =
421
- sectionType as 'containers' | 'components';
435
+ parentEntry.element.sectionHeader = sectionType as
436
+ | 'containers'
437
+ | 'components';
422
438
  parentEntry.element.sectionHeaderLineNumber = lineNumber;
423
439
  stack.push({
424
440
  kind: 'section',
@@ -427,10 +443,7 @@ export function parseC4(
427
443
  indent,
428
444
  });
429
445
  } else {
430
- pushError(
431
- lineNumber,
432
- `"${sectionType}" must be inside an element`,
433
- );
446
+ pushError(lineNumber, `"${sectionType}" must be inside an element`);
434
447
  }
435
448
  continue;
436
449
  }
@@ -487,9 +500,15 @@ export function parseC4(
487
500
  if (!rawLabel) break; // empty label — fall through to plain arrow
488
501
 
489
502
  // Reject bidirectional arrows
490
- if (arrowType === 'bidirectional' || arrowType === 'bidirectional-async') {
503
+ if (
504
+ arrowType === 'bidirectional' ||
505
+ arrowType === 'bidirectional-async'
506
+ ) {
491
507
  const source = findParentElement(indent, stack)?.element.name ?? '?';
492
- pushError(lineNumber, `Bidirectional arrows are no longer supported. Replace with two separate arrows:\n -${rawLabel}-> ${targetBody}\n ${targetBody} -${rawLabel}-> ${source}`);
508
+ pushError(
509
+ lineNumber,
510
+ `Bidirectional arrows are no longer supported. Replace with two separate arrows:\n -${rawLabel}-> ${targetBody}\n ${targetBody} -${rawLabel}-> ${source}`
511
+ );
493
512
  labeledHandled = true;
494
513
  break;
495
514
  }
@@ -546,11 +565,17 @@ export function parseC4(
546
565
  const arrowType = parseArrowType(relMatch[1]);
547
566
  if (arrowType) {
548
567
  // Reject bidirectional arrows
549
- if (arrowType === 'bidirectional' || arrowType === 'bidirectional-async') {
568
+ if (
569
+ arrowType === 'bidirectional' ||
570
+ arrowType === 'bidirectional-async'
571
+ ) {
550
572
  const arrow = relMatch[1];
551
573
  const target = relMatch[2].trim();
552
574
  const source = findParentElement(indent, stack)?.element.name ?? '?';
553
- pushError(lineNumber, `'${arrow}' bidirectional arrows are no longer supported. Replace with two separate arrows:\n -> ${target}\n ${target} -> ${source}`);
575
+ pushError(
576
+ lineNumber,
577
+ `'${arrow}' bidirectional arrows are no longer supported. Replace with two separate arrows:\n -> ${target}\n ${target} -> ${source}`
578
+ );
554
579
  continue;
555
580
  }
556
581
 
@@ -599,14 +624,22 @@ export function parseC4(
599
624
  let segments: string[];
600
625
  if (remainderTrimmed.startsWith('|')) {
601
626
  // remainder has pipe metadata: "| tech: PostgreSQL, team: Data"
602
- segments = ['', ...remainderTrimmed.substring(1).split('|').map((s) => s.trim())];
627
+ segments = [
628
+ '',
629
+ ...remainderTrimmed
630
+ .substring(1)
631
+ .split('|')
632
+ .map((s) => s.trim()),
633
+ ];
603
634
  } else {
604
635
  segments = [remainderTrimmed];
605
636
  }
606
637
 
607
638
  // Check for additional `is a <shape>` in the name (e.g., already stripped by C4_IS_A_RE won't happen,
608
639
  // but handle remainder like "is a cylinder" after type)
609
- const remainderIsA = remainderTrimmed.match(/^\s*is\s+a(?:n)?\s+(\w+)\s*(.*)$/i);
640
+ const remainderIsA = remainderTrimmed.match(
641
+ /^\s*is\s+a(?:n)?\s+(\w+)\s*(.*)$/i
642
+ );
610
643
  if (remainderIsA) {
611
644
  const shapeName = remainderIsA[1].toLowerCase();
612
645
  if (VALID_SHAPES.has(shapeName)) {
@@ -614,13 +647,19 @@ export function parseC4(
614
647
  } else {
615
648
  pushError(
616
649
  lineNumber,
617
- `Unknown shape "${remainderIsA[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`,
650
+ `Unknown shape "${remainderIsA[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`
618
651
  );
619
652
  }
620
653
  // Re-parse remainder after shape
621
654
  const afterShape = remainderIsA[2].trim();
622
655
  if (afterShape.startsWith('|')) {
623
- segments = ['', ...afterShape.substring(1).split('|').map((s) => s.trim())];
656
+ segments = [
657
+ '',
658
+ ...afterShape
659
+ .substring(1)
660
+ .split('|')
661
+ .map((s) => s.trim()),
662
+ ];
624
663
  } else {
625
664
  segments = [afterShape];
626
665
  }
@@ -635,13 +674,15 @@ export function parseC4(
635
674
  } else {
636
675
  pushError(
637
676
  lineNumber,
638
- `Unknown shape "${nameIsAMatch[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`,
677
+ `Unknown shape "${nameIsAMatch[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`
639
678
  );
640
679
  }
641
680
  namePart = namePart.substring(0, nameIsAMatch.index!).trim();
642
681
  }
643
682
 
644
- const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, 'warning'));
683
+ const metadata = parsePipeMetadata(segments, aliasMap, () =>
684
+ pushError(lineNumber, MULTIPLE_PIPE_ERROR)
685
+ );
645
686
 
646
687
  const shape =
647
688
  explicitShape ??
@@ -663,7 +704,7 @@ export function parseC4(
663
704
  if (existingLine !== undefined) {
664
705
  pushError(
665
706
  lineNumber,
666
- `Duplicate element name "${namePart}" (first defined on line ${existingLine})`,
707
+ `Duplicate element name "${namePart}" (first defined on line ${existingLine})`
667
708
  );
668
709
  } else {
669
710
  knownNames.set(namePart.toLowerCase(), lineNumber);
@@ -677,7 +718,7 @@ export function parseC4(
677
718
  const elementMatch = trimmed.match(ELEMENT_RE);
678
719
  if (elementMatch) {
679
720
  const elementType = elementMatch[1].toLowerCase() as C4ElementType;
680
- let nameAndRest = elementMatch[2];
721
+ const nameAndRest = elementMatch[2];
681
722
 
682
723
  // Split on pipe for inline metadata
683
724
  const segments = nameAndRest.split('|').map((s) => s.trim());
@@ -693,7 +734,7 @@ export function parseC4(
693
734
  } else {
694
735
  pushError(
695
736
  lineNumber,
696
- `Unknown shape "${isAMatch[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`,
737
+ `Unknown shape "${isAMatch[1]}". Valid shapes: ${[...VALID_SHAPES].join(', ')}`
697
738
  );
698
739
  }
699
740
  namePart = namePart.substring(0, isAMatch.index!).trim();
@@ -702,10 +743,12 @@ export function parseC4(
702
743
  // Emit deprecation error with migration hint
703
744
  pushError(
704
745
  lineNumber,
705
- `'${elementMatch[1]} ${namePart}' prefix syntax is no longer supported — use '${namePart} is a ${elementType}' instead`,
746
+ `'${elementMatch[1]} ${namePart}' prefix syntax is no longer supported — use '${namePart} is a ${elementType}' instead`
706
747
  );
707
748
 
708
- const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, 'warning'));
749
+ const metadata = parsePipeMetadata(segments, aliasMap, () =>
750
+ pushError(lineNumber, MULTIPLE_PIPE_ERROR)
751
+ );
709
752
 
710
753
  // Determine shape: explicit > inference
711
754
  const shape =
@@ -728,7 +771,7 @@ export function parseC4(
728
771
  if (existingLine !== undefined) {
729
772
  pushError(
730
773
  lineNumber,
731
- `Duplicate element name "${namePart}" (first defined on line ${existingLine})`,
774
+ `Duplicate element name "${namePart}" (first defined on line ${existingLine})`
732
775
  );
733
776
  } else {
734
777
  knownNames.set(namePart.toLowerCase(), lineNumber);
@@ -747,7 +790,7 @@ export function parseC4(
747
790
  if (parentEntry) {
748
791
  const rawKey = metadataMatch[1].trim().toLowerCase();
749
792
 
750
- // Special case: `import file.dgmo`
793
+ // Special case: `import: file.dgmo`
751
794
  if (rawKey === 'import') {
752
795
  parentEntry.element.importPath = metadataMatch[2].trim();
753
796
  continue;
@@ -792,7 +835,7 @@ export function parseC4(
792
835
  /** Find the nearest parent element entry on the stack at shallower indent. */
793
836
  function findParentElement(
794
837
  indent: number,
795
- stack: StackEntry[],
838
+ stack: StackEntry[]
796
839
  ): ElementStackEntry | null {
797
840
  for (let i = stack.length - 1; i >= 0; i--) {
798
841
  const entry = stack[i];
@@ -818,7 +861,7 @@ function attachElement(
818
861
  element: C4Element,
819
862
  indent: number,
820
863
  stack: StackEntry[],
821
- result: ParsedC4,
864
+ result: ParsedC4
822
865
  ): void {
823
866
  // Find the immediate context: group, section, or parent element
824
867
  let attached = false;
@@ -860,7 +903,11 @@ function attachElement(
860
903
  function validateRelationshipTargets(
861
904
  result: ParsedC4,
862
905
  knownNames: Map<string, number>,
863
- pushWarning: (line: number, message: string, severity?: 'error' | 'warning') => void,
906
+ pushWarning: (
907
+ line: number,
908
+ message: string,
909
+ severity?: 'error' | 'warning'
910
+ ) => void
864
911
  ): void {
865
912
  function walkRels(elements: C4Element[]) {
866
913
  for (const el of elements) {
@@ -869,7 +916,7 @@ function validateRelationshipTargets(
869
916
  pushWarning(
870
917
  rel.lineNumber,
871
918
  `Relationship target "${rel.target}" not found`,
872
- 'warning',
919
+ 'warning'
873
920
  );
874
921
  }
875
922
  }
@@ -887,7 +934,7 @@ function validateRelationshipTargets(
887
934
  pushWarning(
888
935
  rel.lineNumber,
889
936
  `Relationship target "${rel.target}" not found`,
890
- 'warning',
937
+ 'warning'
891
938
  );
892
939
  }
893
940
  }
@@ -896,7 +943,11 @@ function validateRelationshipTargets(
896
943
  function validateDeploymentRefs(
897
944
  result: ParsedC4,
898
945
  knownNames: Map<string, number>,
899
- pushWarning: (line: number, message: string, severity?: 'error' | 'warning') => void,
946
+ pushWarning: (
947
+ line: number,
948
+ message: string,
949
+ severity?: 'error' | 'warning'
950
+ ) => void
900
951
  ): void {
901
952
  function walkDeploy(nodes: C4DeploymentNode[]) {
902
953
  for (const node of nodes) {
@@ -905,7 +956,7 @@ function validateDeploymentRefs(
905
956
  pushWarning(
906
957
  node.lineNumber,
907
958
  `Deployment reference "container ${ref}" not found`,
908
- 'warning',
959
+ 'warning'
909
960
  );
910
961
  }
911
962
  }