@markdy/core 1.1.7 → 1.3.0

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.
package/dist/index.js CHANGED
@@ -397,6 +397,7 @@ var CONTROL_KEYS = [
397
397
  "resetView",
398
398
  "fullscreen",
399
399
  "svg",
400
+ "gif",
400
401
  "share",
401
402
  "code",
402
403
  "theme"
@@ -448,6 +449,11 @@ var CONTROLS = {
448
449
  svg: { group: "controls", key: "svg", type: "boolean" },
449
450
  exportSvg: { group: "controls", key: "svg", type: "boolean" },
450
451
  export_svg: { group: "controls", key: "svg", type: "boolean" },
452
+ gif: { group: "controls", key: "gif", type: "boolean" },
453
+ exportGif: { group: "controls", key: "gif", type: "boolean" },
454
+ export_gif: { group: "controls", key: "gif", type: "boolean" },
455
+ gifButton: { group: "controls", key: "gif", type: "boolean" },
456
+ gif_button: { group: "controls", key: "gif", type: "boolean" },
451
457
  share: { group: "controls", key: "share", type: "boolean" },
452
458
  shareLink: { group: "controls", key: "share", type: "boolean" },
453
459
  share_link: { group: "controls", key: "share", type: "boolean" },
@@ -528,6 +534,11 @@ var FLAT = {
528
534
  fullscreen_button: CONTROLS.fullscreen_button,
529
535
  exportSvg: CONTROLS.exportSvg,
530
536
  export_svg: CONTROLS.export_svg,
537
+ gif: CONTROLS.gif,
538
+ exportGif: CONTROLS.exportGif,
539
+ export_gif: CONTROLS.export_gif,
540
+ gifButton: CONTROLS.gifButton,
541
+ gif_button: CONTROLS.gif_button,
531
542
  shareLink: CONTROLS.shareLink,
532
543
  share_link: CONTROLS.share_link,
533
544
  code: CONTROLS.code,
@@ -643,6 +654,7 @@ function resolvePlayer(config = {}, overrides = {}) {
643
654
  resetView: resolveControl(configuredControls.resetView),
644
655
  fullscreen: resolveControl(configuredControls.fullscreen),
645
656
  svg: resolveControl(configuredControls.svg),
657
+ gif: resolveControl(configuredControls.gif, false),
646
658
  share: resolveControl(configuredControls.share),
647
659
  code: resolveControl(configuredControls.code, false),
648
660
  theme: resolveControl(configuredControls.theme, hostControlDefault)
@@ -719,7 +731,8 @@ var EDGE_OPERATORS = {
719
731
  "->": "request",
720
732
  "<-": "response",
721
733
  "~>": "event",
722
- "--": "dependency"
734
+ "--": "dependency",
735
+ "..>": "dependency"
723
736
  };
724
737
  var RESERVED_SELECTORS = /* @__PURE__ */ new Set(["$title", "$nodes", "$edges"]);
725
738
  var CUE_ALIASES = {
@@ -791,6 +804,41 @@ var NODE_W = 180;
791
804
  var NODE_H = 76;
792
805
  var VENN_NODE_SIZE = 220;
793
806
  var GROUP_PAD = 28;
807
+ function computeNodeDimensions(decl, baseW = 180, baseH = 76) {
808
+ if (typeof decl.props?.width === "number") {
809
+ return {
810
+ width: decl.props.width,
811
+ height: typeof decl.props.height === "number" ? decl.props.height : baseH
812
+ };
813
+ }
814
+ const labelLen = (decl.label || "").length;
815
+ const tech = decl.props?.tech ?? decl.props?.sub;
816
+ const techLen = tech ? String(tech).length : 0;
817
+ const val = decl.props?.value ?? decl.props?.metric;
818
+ const valLen = val ? String(val).length : 0;
819
+ if (decl.kind === "dot") return { width: 64, height: 64 };
820
+ if (decl.kind === "matrix") return { width: 220, height: 96 };
821
+ const valueW = valLen > 0 ? Math.max(50, valLen * 9.5 + 16) : 0;
822
+ const words = (decl.label || "").trim().split(/\s+/).filter(Boolean);
823
+ const longestWord = Math.max(...words.map((w) => w.length), 0);
824
+ const avgLineChars = words.length > 1 ? Math.ceil(labelLen / Math.min(words.length, 2)) : labelLen;
825
+ const neededLabelChars = Math.max(longestWord + 2, avgLineChars, Math.min(labelLen, 22));
826
+ const maxChars = Math.max(neededLabelChars, techLen);
827
+ const neededTextW = Math.max(96, maxChars * 8.4);
828
+ const calculatedW = 56 + neededTextW + (valueW > 0 ? valueW + 14 : 0) + 18;
829
+ const minW = Math.max(baseW, Math.min(360, calculatedW));
830
+ let width = Math.ceil(minW / 8) * 8;
831
+ let height = baseH;
832
+ if (labelLen > 24 && techLen > 0) {
833
+ height = Math.max(height, 84);
834
+ }
835
+ return { width, height };
836
+ }
837
+ function resolveNodeStyle(decl, ast) {
838
+ if (!decl.style) return void 0;
839
+ if (typeof decl.style === "string") return ast.styles[decl.style]?.props;
840
+ return decl.style;
841
+ }
794
842
  var DEFAULTS = {
795
843
  show: 0.35,
796
844
  hide: 0.35,
@@ -805,7 +853,13 @@ var DEFAULTS = {
805
853
  function diagramType(ast) {
806
854
  return ast.meta.type ?? "architecture";
807
855
  }
808
- function nodeShape(kind, dtype) {
856
+ function nodeShape(kind, dtype, props) {
857
+ if (typeof props?.shape === "string") {
858
+ const s = props.shape.toLowerCase();
859
+ if (s === "diamond" || s === "circle" || s === "pill" || s === "terminal" || s === "rounded" || s === "card" || s === "container") {
860
+ return s;
861
+ }
862
+ }
809
863
  if (kind === "terminal") return "terminal";
810
864
  if (kind === "dot" || kind === "marker") return "circle";
811
865
  if (kind === "token_strip" || kind === "chips") return "pill";
@@ -940,22 +994,31 @@ function layoutRanked(ast, edges, opts) {
940
994
  });
941
995
  }
942
996
  const isVertical = direction === "TB" || direction === "BT";
997
+ const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
998
+ const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
999
+ const titleBand = hasTitle ? TITLE_BAND : 0;
1000
+ const bottomBand = hasBeatCaptions ? 44 : 0;
943
1001
  const contentW = ast.meta.width - SAFE * 2;
944
- const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
945
1002
  const maxRank = Math.max(...byRank.keys(), 0);
946
1003
  const rankCount = maxRank + 1;
947
1004
  const maxInRank = Math.max(...[...byRank.values()].map((v) => v.length), 1);
948
1005
  const nodes = [];
1006
+ const nodeDims = /* @__PURE__ */ new Map();
1007
+ for (const id of nodeIds) {
1008
+ nodeDims.set(id, computeNodeDimensions(ast.nodes[id]));
1009
+ }
949
1010
  if (opts.columnLayout) {
950
1011
  const count = nodeIds.length;
951
- const colSpacing = Math.max(NODE_W + 32, contentW / Math.max(count, 1));
952
- const totalW2 = (count - 1) * colSpacing + NODE_W;
1012
+ const maxNodeW = Math.max(...nodeIds.map((id) => nodeDims.get(id)?.width ?? NODE_W));
1013
+ const colSpacing = Math.max(maxNodeW + 32, contentW / Math.max(count, 1));
1014
+ const totalW2 = (count - 1) * colSpacing + maxNodeW;
953
1015
  const startX2 = SAFE + Math.max(0, (contentW - totalW2) / 2);
954
1016
  nodeIds.forEach((id, idx) => {
955
1017
  const decl = ast.nodes[id];
956
1018
  const role = nodeRole(decl.kind);
957
- const x = startX2 + idx * colSpacing;
958
- const y = TITLE_BAND + 32;
1019
+ const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
1020
+ const x = startX2 + idx * colSpacing + (maxNodeW - dims.width) / 2;
1021
+ const y = (hasTitle ? titleBand : 20) + 32;
959
1022
  nodes.push({
960
1023
  id,
961
1024
  kind: decl.kind,
@@ -963,12 +1026,12 @@ function layoutRanked(ast, edges, opts) {
963
1026
  label: decl.label,
964
1027
  x: snapGrid(x),
965
1028
  y: snapGrid(y),
966
- width: NODE_W,
967
- height: NODE_H,
968
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1029
+ width: dims.width,
1030
+ height: dims.height,
1031
+ style: resolveNodeStyle(decl, ast),
969
1032
  props: decl.props,
970
1033
  opacity: 0,
971
- shape: nodeShape(decl.kind, dtype),
1034
+ shape: nodeShape(decl.kind, dtype, decl.props),
972
1035
  focal: decl.props.focal === true || decl.props.accent === true,
973
1036
  column: idx
974
1037
  });
@@ -976,18 +1039,22 @@ function layoutRanked(ast, edges, opts) {
976
1039
  return nodes;
977
1040
  }
978
1041
  if (isVertical) {
979
- const rowGap = Math.max(NODE_H + 40, Math.min(180, contentH / Math.max(rankCount, 1)));
980
- const totalH = (rankCount - 1) * rowGap + NODE_H;
1042
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1043
+ const maxNodeH = Math.max(...nodeIds.map((id) => nodeDims.get(id)?.height ?? NODE_H));
1044
+ const rowGap = Math.max(maxNodeH + 40, Math.min(200, contentH / Math.max(rankCount, 1)));
1045
+ const totalH = (rankCount - 1) * rowGap + maxNodeH;
981
1046
  const startY = TITLE_BAND + Math.max(0, (contentH - totalH) / 2);
982
1047
  for (const [rank, ids] of [...byRank.entries()].sort((a, b) => a[0] - b[0])) {
983
1048
  const rowCount = ids.length;
984
- const colSpacing = Math.max(NODE_W + 36, Math.min(260, contentW / Math.max(rowCount, 1)));
985
- const rankW = (rowCount - 1) * colSpacing + NODE_W;
1049
+ const maxRowW = Math.max(...ids.map((id) => nodeDims.get(id)?.width ?? NODE_W));
1050
+ const colSpacing = Math.max(maxRowW + 36, Math.min(320, contentW / Math.max(rowCount, 1)));
1051
+ const rankW = (rowCount - 1) * colSpacing + maxRowW;
986
1052
  const rankStartX = SAFE + Math.max(0, (contentW - rankW) / 2);
987
1053
  const y = startY + rank * rowGap;
988
1054
  ids.forEach((id, idx) => {
989
1055
  const decl = ast.nodes[id];
990
1056
  const role = nodeRole(decl.kind);
1057
+ const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
991
1058
  const x = rankStartX + idx * colSpacing;
992
1059
  const focal = decl.props.focal === true || decl.props.accent === true;
993
1060
  nodes.push({
@@ -997,32 +1064,44 @@ function layoutRanked(ast, edges, opts) {
997
1064
  label: decl.label,
998
1065
  x: snapGrid(x),
999
1066
  y: snapGrid(y),
1000
- width: NODE_W,
1001
- height: NODE_H,
1002
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1067
+ width: dims.width,
1068
+ height: dims.height,
1069
+ style: resolveNodeStyle(decl, ast),
1003
1070
  props: decl.props,
1004
1071
  opacity: 0,
1005
- shape: nodeShape(decl.kind, dtype),
1072
+ shape: nodeShape(decl.kind, dtype, decl.props),
1006
1073
  focal
1007
1074
  });
1008
1075
  });
1009
1076
  }
1010
1077
  return nodes;
1011
1078
  }
1012
- const maxPossibleStep = rankCount > 1 ? (contentW - NODE_W) / (rankCount - 1) : 0;
1013
- const colGap = rankCount > 1 ? Math.min(300, Math.max(50, maxPossibleStep)) : 0;
1014
- const totalW = (rankCount - 1) * colGap + NODE_W;
1079
+ const rankWidths = /* @__PURE__ */ new Map();
1080
+ for (const [rank, ids] of byRank.entries()) {
1081
+ const maxW = Math.max(...ids.map((id) => nodeDims.get(id)?.width ?? NODE_W));
1082
+ rankWidths.set(rank, maxW);
1083
+ }
1084
+ const totalRankWidths = [...rankWidths.values()].reduce((a, b) => a + b, 0);
1085
+ const minColGap = 56;
1086
+ const availableGapW = Math.max(0, contentW - totalRankWidths);
1087
+ const colGap = rankCount > 1 ? Math.max(minColGap, Math.min(160, availableGapW / (rankCount - 1))) : 0;
1088
+ const totalW = totalRankWidths + (rankCount - 1) * colGap;
1015
1089
  const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
1016
- for (const [rank, ids] of [...byRank.entries()].sort((a, b) => a[0] - b[0])) {
1090
+ const availableH = Math.max(NODE_H, ast.meta.height - titleBand - bottomBand);
1091
+ let currentRankX = startX;
1092
+ for (let rank = 0; rank < rankCount; rank++) {
1093
+ const ids = byRank.get(rank) ?? [];
1094
+ const rankW = rankWidths.get(rank) ?? NODE_W;
1017
1095
  const rowCount = ids.length;
1018
- const maxPossibleRowStep = rowCount > 1 ? (contentH - NODE_H) / (rowCount - 1) : 0;
1019
- const rowSpacing = rowCount > 1 ? Math.min(150, Math.max(36, maxPossibleRowStep)) : 0;
1020
- const colH = (rowCount - 1) * rowSpacing + NODE_H;
1021
- const colStartY = TITLE_BAND + Math.max(0, (contentH - colH) / 2);
1022
- const x = startX + rank * colGap;
1096
+ const maxH = Math.max(...ids.map((id) => nodeDims.get(id)?.height ?? NODE_H), NODE_H);
1097
+ const maxPossibleRowStep = rowCount > 1 ? (availableH - maxH) / (rowCount - 1) : 0;
1098
+ const rowSpacing = rowCount > 1 ? Math.min(180, Math.max(maxH + 28, maxPossibleRowStep)) : 0;
1099
+ const colH = (rowCount - 1) * rowSpacing + maxH;
1100
+ const colStartY = titleBand + Math.max(0, (availableH - colH) / 2);
1023
1101
  ids.forEach((id, idx) => {
1024
1102
  const decl = ast.nodes[id];
1025
1103
  const role = nodeRole(decl.kind);
1104
+ const dims = nodeDims.get(id) ?? { width: NODE_W, height: NODE_H };
1026
1105
  const y = colStartY + idx * rowSpacing;
1027
1106
  const focal = decl.props.focal === true || decl.props.accent === true;
1028
1107
  nodes.push({
@@ -1030,17 +1109,18 @@ function layoutRanked(ast, edges, opts) {
1030
1109
  kind: decl.kind,
1031
1110
  role,
1032
1111
  label: decl.label,
1033
- x: snapGrid(x),
1112
+ x: snapGrid(currentRankX),
1034
1113
  y: snapGrid(y),
1035
- width: NODE_W,
1036
- height: NODE_H,
1037
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1114
+ width: dims.width,
1115
+ height: dims.height,
1116
+ style: resolveNodeStyle(decl, ast),
1038
1117
  props: decl.props,
1039
1118
  opacity: 0,
1040
- shape: nodeShape(decl.kind, dtype),
1119
+ shape: nodeShape(decl.kind, dtype, decl.props),
1041
1120
  focal
1042
1121
  });
1043
1122
  });
1123
+ currentRankX += rankW + colGap;
1044
1124
  }
1045
1125
  return nodes;
1046
1126
  }
@@ -1141,7 +1221,7 @@ function layoutTree(ast, edges) {
1141
1221
  y: snapGrid(pos.y),
1142
1222
  width: NODE_W,
1143
1223
  height: NODE_H,
1144
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1224
+ style: resolveNodeStyle(decl, ast),
1145
1225
  props: decl.props,
1146
1226
  opacity: 0,
1147
1227
  shape: "card",
@@ -1209,10 +1289,10 @@ function layoutConstellation(ast) {
1209
1289
  y: snapGrid(y),
1210
1290
  width: NODE_W,
1211
1291
  height: NODE_H,
1212
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1292
+ style: resolveNodeStyle(decl, ast),
1213
1293
  props: decl.props,
1214
1294
  opacity: 0,
1215
- shape: nodeShape(decl.kind, "constellation"),
1295
+ shape: nodeShape(decl.kind, "constellation", decl.props),
1216
1296
  focal: isFocal || decl.props.focal === true || decl.props.accent === true
1217
1297
  });
1218
1298
  }
@@ -1257,7 +1337,7 @@ function layoutLoop(ast) {
1257
1337
  y: snapGrid(y),
1258
1338
  width: isHub ? snapGrid(NODE_W * 1.25) : NODE_W,
1259
1339
  height: isHub ? snapGrid(NODE_H * 1.15) : NODE_H,
1260
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1340
+ style: resolveNodeStyle(decl, ast),
1261
1341
  props: decl.props,
1262
1342
  opacity: 0,
1263
1343
  shape: isHub ? "pill" : "card",
@@ -1311,7 +1391,7 @@ function layoutMedallion(ast, edges) {
1311
1391
  y: snapGrid(rowY),
1312
1392
  width: NODE_W,
1313
1393
  height: NODE_H,
1314
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1394
+ style: resolveNodeStyle(decl, ast),
1315
1395
  props: decl.props,
1316
1396
  opacity: 0,
1317
1397
  shape: "card",
@@ -1373,7 +1453,7 @@ function layoutQuadrant(ast) {
1373
1453
  y: snapGrid(y),
1374
1454
  width: NODE_W,
1375
1455
  height: NODE_H,
1376
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1456
+ style: resolveNodeStyle(decl, ast),
1377
1457
  props: decl.props,
1378
1458
  opacity: 0,
1379
1459
  shape: "card",
@@ -1426,7 +1506,7 @@ function layoutSwimlane(ast, edges) {
1426
1506
  y: snapGrid(laneY),
1427
1507
  width: NODE_W,
1428
1508
  height: NODE_H,
1429
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1509
+ style: resolveNodeStyle(decl, ast),
1430
1510
  props: decl.props,
1431
1511
  opacity: 0,
1432
1512
  shape: "card",
@@ -1475,7 +1555,7 @@ function layoutPyramid(ast) {
1475
1555
  y: snapGrid(tierY),
1476
1556
  width: snapGrid(nodeW),
1477
1557
  height: NODE_H,
1478
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1558
+ style: resolveNodeStyle(decl, ast),
1479
1559
  props: decl.props,
1480
1560
  opacity: 0,
1481
1561
  shape: "card",
@@ -1508,7 +1588,7 @@ function layoutTimeline(ast) {
1508
1588
  y: snapGrid(y),
1509
1589
  width: NODE_W,
1510
1590
  height: NODE_H,
1511
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1591
+ style: resolveNodeStyle(decl, ast),
1512
1592
  props: decl.props,
1513
1593
  opacity: 0,
1514
1594
  shape: "pill",
@@ -1551,7 +1631,7 @@ function layoutGantt(ast) {
1551
1631
  y: snapGrid(y),
1552
1632
  width: snapGrid(w),
1553
1633
  height: barH,
1554
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1634
+ style: resolveNodeStyle(decl, ast),
1555
1635
  props: decl.props,
1556
1636
  opacity: 0,
1557
1637
  shape: "pill",
@@ -1591,7 +1671,7 @@ function layoutVenn(ast) {
1591
1671
  y: snapGrid(y),
1592
1672
  width: VENN_NODE_SIZE,
1593
1673
  height: VENN_NODE_SIZE,
1594
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1674
+ style: resolveNodeStyle(decl, ast),
1595
1675
  props: decl.props,
1596
1676
  opacity: 0,
1597
1677
  shape: "circle",
@@ -1625,7 +1705,7 @@ function layoutLayers(ast) {
1625
1705
  y: snapGrid(y),
1626
1706
  width: snapGrid(layerW),
1627
1707
  height: snapGrid(layerH),
1628
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1708
+ style: resolveNodeStyle(decl, ast),
1629
1709
  props: decl.props,
1630
1710
  opacity: 0,
1631
1711
  shape: "rounded",
@@ -1672,7 +1752,7 @@ function layoutNested(ast) {
1672
1752
  y: snapGrid(y),
1673
1753
  width: snapGrid(w),
1674
1754
  height: snapGrid(h),
1675
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1755
+ style: resolveNodeStyle(decl, ast),
1676
1756
  props: decl.props,
1677
1757
  opacity: 0,
1678
1758
  shape: isCore ? "card" : "container",
@@ -1706,7 +1786,7 @@ function layoutRadar(ast) {
1706
1786
  y: snapGrid(y),
1707
1787
  width: NODE_W,
1708
1788
  height: NODE_H,
1709
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
1789
+ style: resolveNodeStyle(decl, ast),
1710
1790
  props: decl.props,
1711
1791
  opacity: 0,
1712
1792
  shape: "rounded",
@@ -2094,17 +2174,32 @@ function computeAdaptiveDimensions(ast, edges) {
2094
2174
  autoH = Math.max(720, Math.min(2e3, requiredH));
2095
2175
  }
2096
2176
  } else {
2097
- const requiredW = SAFE * 2 + rankCount * NODE_W + (rankCount - 1) * 44 + groupPaddingBonus;
2098
- const requiredH = SAFE * 2 + TITLE_BAND + maxInRank * NODE_H + (maxInRank - 1) * 36 + groupPaddingBonus;
2099
- if (nodeCount <= 2 && rankCount <= 2) {
2177
+ const hasTitle = Boolean(ast.meta.title && ast.meta.title.trim().length > 0);
2178
+ const hasBeatCaptions = ast.beats.some((b) => b.label && b.label.trim().length > 0);
2179
+ const titleBand = hasTitle ? TITLE_BAND : 0;
2180
+ const bottomBand = hasBeatCaptions ? 44 : 0;
2181
+ const maxRankW = Math.max(...nodeIds.map((id) => computeNodeDimensions(ast.nodes[id]).width));
2182
+ const maxRankH = Math.max(...nodeIds.map((id) => computeNodeDimensions(ast.nodes[id]).height));
2183
+ const requiredW = SAFE * 2 + rankCount * maxRankW + (rankCount - 1) * 56 + groupPaddingBonus;
2184
+ const requiredH = SAFE * 2 + titleBand + bottomBand + maxInRank * maxRankH + (maxInRank - 1) * 44 + groupPaddingBonus;
2185
+ if (maxInRank === 1 && !hasTitle) {
2186
+ autoW = Math.max(1024, Math.min(2560, requiredW));
2187
+ autoH = Math.max(hasBeatCaptions ? 240 : 208, Math.min(360, requiredH + 24));
2188
+ } else if (maxInRank === 2 && !hasTitle) {
2189
+ autoW = Math.max(1024, Math.min(2560, requiredW));
2190
+ autoH = Math.max(hasBeatCaptions ? 368 : 336, Math.min(480, requiredH + 24));
2191
+ } else if (maxInRank === 3 && !hasTitle) {
2192
+ autoW = Math.max(1152, Math.min(2560, requiredW));
2193
+ autoH = Math.max(hasBeatCaptions ? 480 : 448, Math.min(640, requiredH + 24));
2194
+ } else if (nodeCount <= 2 && rankCount <= 2) {
2100
2195
  autoW = 1024;
2101
- autoH = 576;
2196
+ autoH = hasTitle ? 576 : 384;
2102
2197
  } else if (nodeCount <= 4 && rankCount <= 3 && maxInRank <= 2) {
2103
2198
  autoW = 1152;
2104
- autoH = 648;
2199
+ autoH = hasTitle ? 648 : 448;
2105
2200
  } else {
2106
2201
  autoW = Math.max(1152, Math.min(2560, requiredW));
2107
- autoH = Math.max(648, Math.min(1600, requiredH));
2202
+ autoH = Math.max(hasTitle ? 648 : maxInRank <= 2 ? 448 : 576, Math.min(1600, requiredH));
2108
2203
  }
2109
2204
  }
2110
2205
  }
@@ -2343,7 +2438,7 @@ var THEMES = {
2343
2438
  dependency: "#818cf8"
2344
2439
  },
2345
2440
  fonts: {
2346
- title: "Georgia, Times New Roman, serif",
2441
+ title: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Inter, sans-serif",
2347
2442
  nodeName: "ui-sans-serif, system-ui, sans-serif",
2348
2443
  mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
2349
2444
  },
@@ -2440,6 +2535,116 @@ var THEMES = {
2440
2535
  },
2441
2536
  radiusMd: 4,
2442
2537
  spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
2538
+ },
2539
+ ink: {
2540
+ name: "ink",
2541
+ canvas: "#f8fafd",
2542
+ surface: "#ffffff",
2543
+ surfaceRaised: "#eff6ff",
2544
+ border: "#1d4ed8",
2545
+ text: "#0f172a",
2546
+ textMuted: "#3b629b",
2547
+ paper: "#f8fafd",
2548
+ ink: "#1e40af",
2549
+ muted: "#4776b2",
2550
+ rule: "rgba(29, 78, 216, 0.14)",
2551
+ soft: "#60a5fa",
2552
+ link: "#2563eb",
2553
+ gridMinor: "rgba(37, 99, 235, 0.05)",
2554
+ gridMajor: "rgba(37, 99, 235, 0.10)",
2555
+ vignette: "rgba(219, 234, 254, 0.45)",
2556
+ accent: "#1d4ed8",
2557
+ accentTint: "rgba(29, 78, 216, 0.08)",
2558
+ nodeSurface: "#ffffff",
2559
+ nodeSurfaceRaised: "#f4f8ff",
2560
+ hairline: "rgba(29, 78, 216, 0.20)",
2561
+ shadow: "rgba(15, 35, 90, 0.09)",
2562
+ labelPlate: "#ffffff",
2563
+ flatCards: false,
2564
+ roles: {
2565
+ ...ROLE_COLORS,
2566
+ compute: "#1d4ed8",
2567
+ code: "#2563eb",
2568
+ client: "#0284c7",
2569
+ data: "#0369a1",
2570
+ messaging: "#4338ca",
2571
+ network: "#0284c7",
2572
+ security: "#1e3a8a",
2573
+ platform: "#3b82f6",
2574
+ observability: "#0891b2",
2575
+ delivery: "#4f46e5",
2576
+ flow: "#2563eb",
2577
+ distributed: "#475569"
2578
+ },
2579
+ edges: {
2580
+ request: "#1d4ed8",
2581
+ response: "#3b82f6",
2582
+ event: "#0284c7",
2583
+ dependency: "#64748b"
2584
+ },
2585
+ fonts: {
2586
+ title: 'ui-serif, "Charter", "Newsreader", "Georgia", "Songti SC", "Noto Serif", serif',
2587
+ nodeName: 'ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Inter, sans-serif',
2588
+ mono: '"JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace'
2589
+ },
2590
+ radiusMd: 6,
2591
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 },
2592
+ series: ["#1d4ed8", "#0284c7", "#4338ca", "#0891b2", "#1e3a8a"]
2593
+ },
2594
+ doodle: {
2595
+ name: "doodle",
2596
+ canvas: "#fffef9",
2597
+ surface: "#ffffff",
2598
+ surfaceRaised: "#fdf8ec",
2599
+ border: "#18181b",
2600
+ text: "#18181b",
2601
+ textMuted: "#52525b",
2602
+ paper: "#fffef9",
2603
+ ink: "#18181b",
2604
+ muted: "#52525b",
2605
+ rule: "rgba(24, 24, 27, 0.16)",
2606
+ soft: "#71717a",
2607
+ link: "#f43f5e",
2608
+ gridMinor: "rgba(24, 24, 27, 0.05)",
2609
+ gridMajor: "rgba(24, 24, 27, 0.08)",
2610
+ vignette: "rgba(245, 235, 210, 0.35)",
2611
+ accent: "#f43f5e",
2612
+ accentTint: "rgba(244, 63, 94, 0.12)",
2613
+ nodeSurface: "#ffffff",
2614
+ nodeSurfaceRaised: "#fffdf5",
2615
+ hairline: "rgba(24, 24, 27, 0.25)",
2616
+ shadow: "rgba(24, 24, 27, 0.15)",
2617
+ labelPlate: "#ffffff",
2618
+ flatCards: true,
2619
+ roles: {
2620
+ ...ROLE_COLORS,
2621
+ compute: "#2563eb",
2622
+ code: "#2563eb",
2623
+ client: "#f43f5e",
2624
+ data: "#10b981",
2625
+ messaging: "#8b5cf6",
2626
+ network: "#06b6d4",
2627
+ security: "#ef4444",
2628
+ platform: "#f59e0b",
2629
+ observability: "#14b8a6",
2630
+ delivery: "#f59e0b",
2631
+ flow: "#ec4899",
2632
+ distributed: "#71717a"
2633
+ },
2634
+ edges: {
2635
+ request: "#18181b",
2636
+ response: "#71717a",
2637
+ event: "#f43f5e",
2638
+ dependency: "#a1a1aa"
2639
+ },
2640
+ fonts: {
2641
+ title: '"Caveat", "Comic Neue", "Chalkboard SE", "Patrick Hand", "Comic Sans MS", cursive, sans-serif',
2642
+ nodeName: 'ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, sans-serif',
2643
+ mono: '"JetBrains Mono", "SF Mono", ui-monospace, Menlo, monospace'
2644
+ },
2645
+ radiusMd: 12,
2646
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 },
2647
+ series: ["#f43f5e", "#2563eb", "#10b981", "#8b5cf6", "#f59e0b"]
2443
2648
  }
2444
2649
  };
2445
2650
  function resolveTheme(name) {
@@ -2457,7 +2662,7 @@ var ParseError = class extends Error {
2457
2662
  this.column = column;
2458
2663
  }
2459
2664
  };
2460
- var FLOW_OP_RE = /(->|<-|~>|--)/;
2665
+ var FLOW_OP_RE = /(->|<-|~>|--|\.\.>|<->)/;
2461
2666
  function stripComment(line) {
2462
2667
  let inString = false;
2463
2668
  let escaped = false;
@@ -2509,7 +2714,7 @@ function parseProps(raw) {
2509
2714
  i = raw.length - parsed.rest.length;
2510
2715
  continue;
2511
2716
  }
2512
- const keyMatch = raw.slice(i).match(/^(\w[\w.-]*)=/);
2717
+ const keyMatch = raw.slice(i).match(/^(@?[\w.-]+)=/);
2513
2718
  if (!keyMatch) {
2514
2719
  i++;
2515
2720
  continue;
@@ -2627,6 +2832,14 @@ function tokenizeFlowChain(line) {
2627
2832
  current += ch;
2628
2833
  continue;
2629
2834
  }
2835
+ const op3 = line.slice(i, i + 3);
2836
+ if (op3 === "..>" || op3 === "<->") {
2837
+ if (current.trim()) parts.push(current.trim());
2838
+ parts.push(op3);
2839
+ current = "";
2840
+ i += 2;
2841
+ continue;
2842
+ }
2630
2843
  const op = line.slice(i, i + 2);
2631
2844
  if (op === "->" || op === "<-" || op === "~>" || op === "--") {
2632
2845
  if (current.trim()) parts.push(current.trim());
@@ -2650,11 +2863,27 @@ function parseFlowChain(line, lineNo) {
2650
2863
  let from = splitTargetLabel(parts[i++], lineNo).node;
2651
2864
  while (i < parts.length) {
2652
2865
  const opToken = parts[i++];
2653
- const op = EDGE_OPERATORS[opToken];
2654
- if (!op) throw new ParseError(`unknown flow operator '${opToken}'`, lineNo);
2655
2866
  if (i >= parts.length) throw new ParseError(`expected target after '${opToken}'`, lineNo);
2656
2867
  const { node: to, label } = splitTargetLabel(parts[i++], lineNo);
2657
2868
  if (!to) throw new ParseError(`expected target node after '${opToken}'`, lineNo);
2869
+ if (opToken === "<->") {
2870
+ segments.push({
2871
+ from,
2872
+ op: "request",
2873
+ to,
2874
+ label
2875
+ });
2876
+ segments.push({
2877
+ from: to,
2878
+ op: "response",
2879
+ to: from,
2880
+ label
2881
+ });
2882
+ from = to;
2883
+ continue;
2884
+ }
2885
+ const op = EDGE_OPERATORS[opToken];
2886
+ if (!op) throw new ParseError(`unknown flow operator '${opToken}'`, lineNo);
2658
2887
  segments.push({
2659
2888
  from: op === "response" ? to : from,
2660
2889
  op,
@@ -3005,7 +3234,7 @@ function validateReferences(ast) {
3005
3234
  }
3006
3235
  }
3007
3236
  for (const node of Object.values(ast.nodes)) {
3008
- if (node.style && !ast.styles[node.style]) {
3237
+ if (typeof node.style === "string" && !ast.styles[node.style]) {
3009
3238
  pushWarning(ast.diagnostics, seen, node.line, `node '${node.id}' references unknown style '${node.style}'`);
3010
3239
  }
3011
3240
  }
@@ -3109,6 +3338,7 @@ function parse(source, opts = {}) {
3109
3338
  height: 720,
3110
3339
  fps: 60,
3111
3340
  theme: "paper",
3341
+ explicitTheme: false,
3112
3342
  direction: "LR"
3113
3343
  };
3114
3344
  const styles = {};
@@ -3207,6 +3437,7 @@ function parse(source, opts = {}) {
3207
3437
  const str = parseStringToken(rest2);
3208
3438
  if (str) {
3209
3439
  title = str.value;
3440
+ meta.title = title;
3210
3441
  remainder = str.rest;
3211
3442
  }
3212
3443
  const inlineLayout = remainder.match(/\blayout\s+(LR|RL|TB|BT)\b/i);
@@ -3229,8 +3460,11 @@ function parse(source, opts = {}) {
3229
3460
  } else if (k === "fps") {
3230
3461
  meta.fps = Number(v);
3231
3462
  } else if (k === "duration") meta.duration = Number(v);
3232
- else if (k === "theme") meta.theme = String(v);
3233
- else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
3463
+ else if (k === "theme") {
3464
+ const val = String(v).toLowerCase();
3465
+ meta.theme = val;
3466
+ meta.explicitTheme = val !== "auto";
3467
+ } else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
3234
3468
  else if (PLAYER_FLAT_KEY_SET.has(k)) {
3235
3469
  const error = applyPlayerSetting(meta.player ??= {}, "player", k, String(v));
3236
3470
  if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
@@ -3249,6 +3483,16 @@ function parse(source, opts = {}) {
3249
3483
  i++;
3250
3484
  continue;
3251
3485
  }
3486
+ if (/^theme\s*[:=]?\s*([a-zA-Z0-9_-]+)/i.test(line)) {
3487
+ const match = line.match(/^theme\s*[:=]?\s*([a-zA-Z0-9_-]+)/i);
3488
+ if (match) {
3489
+ const val = match[1].toLowerCase();
3490
+ meta.theme = val;
3491
+ meta.explicitTheme = val !== "auto";
3492
+ }
3493
+ i++;
3494
+ continue;
3495
+ }
3252
3496
  const directiveMatch = line.match(PLAYER_DIRECTIVE_RE);
3253
3497
  if (directiveMatch) {
3254
3498
  const error = applyPlayerSetting(meta.player ??= {}, "player", directiveMatch[1], directiveMatch[2] ?? "");
@@ -3590,7 +3834,13 @@ function matchNode(node, selector) {
3590
3834
  if (selector.kindEquals && node.kind.toLowerCase() !== selector.kindEquals.toLowerCase()) return false;
3591
3835
  if (selector.roleEquals) {
3592
3836
  const role = nodeRole(node.kind);
3593
- if (role.toLowerCase() !== selector.roleEquals.toLowerCase()) return false;
3837
+ const targetRole = selector.roleEquals.toLowerCase();
3838
+ if (targetRole === "gateway") {
3839
+ const isGateway = role === "network" || ["gateway", "api_gateway", "reverse_proxy", "proxy", "router"].includes(node.kind.toLowerCase());
3840
+ if (!isGateway) return false;
3841
+ } else if (role.toLowerCase() !== targetRole) {
3842
+ return false;
3843
+ }
3594
3844
  }
3595
3845
  if (selector.labelContains) {
3596
3846
  const target = (node.label || node.id).toLowerCase();
@@ -3751,6 +4001,30 @@ var ARCH_RULE_PRESETS = {
3751
4001
  from: { roleEquals: "security" }
3752
4002
  }
3753
4003
  ]
4004
+ },
4005
+ deploymentOwnership: {
4006
+ id: "deployment-ownership",
4007
+ name: "Deployment Ownership & Regional Governance",
4008
+ description: "Ensure production stateful stores and services are guarded in explicit group perimeters",
4009
+ rules: [
4010
+ {
4011
+ id: "no-unprotected-public-database",
4012
+ name: "No Public Database Exposure",
4013
+ description: "Databases and stateful stores must not be directly accessed from public browser/mobile clients.",
4014
+ severity: "error",
4015
+ type: "cannot-connect",
4016
+ from: { roleEquals: "client" },
4017
+ to: { roleEquals: "data" }
4018
+ },
4019
+ {
4020
+ id: "no-cross-region-sync-bypasses",
4021
+ name: "Synchronous Request Cycle Prevention",
4022
+ description: "Synchronous requests must not form cycles across microservices.",
4023
+ severity: "error",
4024
+ type: "forbidden-cycle",
4025
+ edge: { kind: "request" }
4026
+ }
4027
+ ]
3754
4028
  }
3755
4029
  };
3756
4030
  function validateArchitecture(ast, rules = [
@@ -3758,7 +4032,7 @@ function validateArchitecture(ast, rules = [
3758
4032
  ...ARCH_RULE_PRESETS.microservicesGovernance.rules
3759
4033
  ]) {
3760
4034
  const violations = [];
3761
- const nodes = Object.values(ast.nodes);
4035
+ const nodes = Object.values(ast?.nodes || {});
3762
4036
  const nodeMap = new Map(nodes.map((n) => [n.id, n]));
3763
4037
  const edges = extractAllEdges(ast);
3764
4038
  for (const rule of rules) {
@@ -3805,6 +4079,11 @@ function validateArchitecture(ast, rules = [
3805
4079
  break;
3806
4080
  }
3807
4081
  case "forbidden-cycle": {
4082
+ const dtype = ast.meta?.type || ast.config?.type || "architecture";
4083
+ const nonServiceArchetypes = ["state", "sequence", "layers", "flywheel", "loop", "venn"];
4084
+ if (nonServiceArchetypes.includes(dtype)) {
4085
+ break;
4086
+ }
3808
4087
  const cycleInfo = detectCycleInGraph(nodes, edges, rule.edge);
3809
4088
  if (cycleInfo) {
3810
4089
  violations.push({
@@ -4055,26 +4334,66 @@ function classifyTechnology(id, label = "") {
4055
4334
  }
4056
4335
 
4057
4336
  // src/diff.ts
4337
+ function extractDiffEdges(ast) {
4338
+ const edges = [];
4339
+ const seen = /* @__PURE__ */ new Set();
4340
+ const add = (from, to, kind, label) => {
4341
+ const key = `${from}->${to}:${kind}`;
4342
+ if (!seen.has(key)) {
4343
+ seen.add(key);
4344
+ edges.push({ from, to, kind, label });
4345
+ }
4346
+ };
4347
+ for (const edge of ast.edges || []) {
4348
+ add(edge.from, edge.to, edge.kind, edge.label);
4349
+ }
4350
+ for (const beat of ast.beats || []) {
4351
+ for (const cue of beat.cues || []) {
4352
+ if (cue.kind === "flow") {
4353
+ for (const seg of cue.segments) {
4354
+ add(seg.from, seg.to, seg.op, seg.label);
4355
+ }
4356
+ } else if (cue.kind === "parallel") {
4357
+ for (const child of cue.cues) {
4358
+ if (child.kind === "flow") {
4359
+ for (const seg of child.segments) {
4360
+ add(seg.from, seg.to, seg.op, seg.label);
4361
+ }
4362
+ }
4363
+ }
4364
+ }
4365
+ }
4366
+ }
4367
+ return edges;
4368
+ }
4058
4369
  function diffDiagramASTs(beforeAST, afterAST) {
4059
4370
  const nodeDiffs = [];
4060
4371
  const edgeDiffs = [];
4061
- const beforeNodeIds = new Set(Object.keys(beforeAST.nodes));
4062
- const afterNodeIds = new Set(Object.keys(afterAST.nodes));
4372
+ const groupDiffs = [];
4373
+ const beforeNodes = beforeAST.nodes || {};
4374
+ const afterNodes = afterAST.nodes || {};
4375
+ const beforeNodeIds = new Set(Object.keys(beforeNodes));
4376
+ const afterNodeIds = new Set(Object.keys(afterNodes));
4063
4377
  let addedNodesCount = 0;
4064
4378
  let removedNodesCount = 0;
4065
4379
  let modifiedNodesCount = 0;
4066
- for (const [id, afterNode] of Object.entries(afterAST.nodes)) {
4380
+ for (const [id, afterNode] of Object.entries(afterNodes)) {
4067
4381
  if (!beforeNodeIds.has(id)) {
4068
- nodeDiffs.push({ id, status: "added", after: afterNode, changes: ["Newly added node"] });
4382
+ nodeDiffs.push({ id, status: "added", after: afterNode, changes: ["Newly provisioned node"] });
4069
4383
  addedNodesCount++;
4070
4384
  } else {
4071
- const beforeNode = beforeAST.nodes[id];
4385
+ const beforeNode = beforeNodes[id];
4072
4386
  const changes = [];
4073
4387
  if (beforeNode.kind !== afterNode.kind) {
4074
- changes.push(`Kind changed: ${beforeNode.kind} \u2192 ${afterNode.kind}`);
4388
+ changes.push(`Kind: ${beforeNode.kind} \u2192 ${afterNode.kind}`);
4075
4389
  }
4076
4390
  if (beforeNode.label !== afterNode.label) {
4077
- changes.push(`Label changed: "${beforeNode.label}" \u2192 "${afterNode.label}"`);
4391
+ changes.push(`Label: "${beforeNode.label}" \u2192 "${afterNode.label}"`);
4392
+ }
4393
+ const beforeSrc = beforeNode.props?.["@src"] || beforeNode.props?.["src"];
4394
+ const afterSrc = afterNode.props?.["@src"] || afterNode.props?.["src"];
4395
+ if (beforeSrc !== afterSrc) {
4396
+ changes.push(`Code Provenance: ${beforeSrc || "none"} \u2192 ${afterSrc || "none"}`);
4078
4397
  }
4079
4398
  if (changes.length > 0) {
4080
4399
  nodeDiffs.push({ id, status: "modified", before: beforeNode, after: afterNode, changes });
@@ -4084,84 +4403,137 @@ function diffDiagramASTs(beforeAST, afterAST) {
4084
4403
  }
4085
4404
  }
4086
4405
  }
4087
- for (const [id, beforeNode] of Object.entries(beforeAST.nodes)) {
4406
+ for (const [id, beforeNode] of Object.entries(beforeNodes)) {
4088
4407
  if (!afterNodeIds.has(id)) {
4089
- nodeDiffs.push({ id, status: "removed", before: beforeNode, changes: ["Removed node"] });
4408
+ nodeDiffs.push({ id, status: "removed", before: beforeNode, changes: ["Decommissioned node"] });
4090
4409
  removedNodesCount++;
4091
4410
  }
4092
4411
  }
4093
4412
  const edgeKey = (e) => `${e.from}->${e.to}:${e.kind}`;
4094
- const beforeEdgeMap = new Map(beforeAST.edges.map((e) => [edgeKey(e), e]));
4095
- const afterEdgeMap = new Map(afterAST.edges.map((e) => [edgeKey(e), e]));
4096
- for (const [key, afterEdge] of afterEdgeMap) {
4413
+ const beforeEdges = extractDiffEdges(beforeAST);
4414
+ const afterEdges = extractDiffEdges(afterAST);
4415
+ const beforeEdgeMap = new Map(beforeEdges.map((e) => [edgeKey(e), e]));
4416
+ const afterEdgeMap = new Map(afterEdges.map((e) => [edgeKey(e), e]));
4417
+ let addedEdgesCount = 0;
4418
+ let removedEdgesCount = 0;
4419
+ for (const [key, afterEdge] of afterEdgeMap.entries()) {
4097
4420
  if (!beforeEdgeMap.has(key)) {
4098
- edgeDiffs.push({ key, status: "added", after: afterEdge });
4421
+ edgeDiffs.push({ key, status: "added", after: afterEdge, changes: ["New interaction route"] });
4422
+ addedEdgesCount++;
4099
4423
  } else {
4100
4424
  const beforeEdge = beforeEdgeMap.get(key);
4425
+ const changes = [];
4101
4426
  if (beforeEdge.label !== afterEdge.label) {
4102
- edgeDiffs.push({ key, status: "modified", before: beforeEdge, after: afterEdge });
4427
+ changes.push(`Protocol/Label: "${beforeEdge.label || ""}" \u2192 "${afterEdge.label || ""}"`);
4428
+ }
4429
+ if (changes.length > 0) {
4430
+ edgeDiffs.push({ key, status: "modified", before: beforeEdge, after: afterEdge, changes });
4103
4431
  } else {
4104
- edgeDiffs.push({ key, status: "unchanged", before: beforeEdge, after: afterEdge });
4432
+ edgeDiffs.push({ key, status: "unchanged", before: beforeEdge, after: afterEdge, changes: [] });
4105
4433
  }
4106
4434
  }
4107
4435
  }
4108
- for (const [key, beforeEdge] of beforeEdgeMap) {
4436
+ for (const [key, beforeEdge] of beforeEdgeMap.entries()) {
4109
4437
  if (!afterEdgeMap.has(key)) {
4110
- edgeDiffs.push({ key, status: "removed", before: beforeEdge });
4438
+ edgeDiffs.push({ key, status: "removed", before: beforeEdge, changes: ["Decommissioned route"] });
4439
+ removedEdgesCount++;
4111
4440
  }
4112
4441
  }
4113
- const summaryLines = [
4114
- "### \u{1F4CA} Markdy Architectural Diff Summary",
4115
- "",
4116
- `| Metric | Count |`,
4117
- `|---|---|`,
4118
- `| \u{1F7E2} Nodes Added | **${addedNodesCount}** |`,
4119
- `| \u{1F534} Nodes Removed | **${removedNodesCount}** |`,
4120
- `| \u{1F7E1} Nodes Modified | **${modifiedNodesCount}** |`,
4121
- ""
4122
- ];
4123
- if (addedNodesCount > 0 || modifiedNodesCount > 0 || removedNodesCount > 0) {
4124
- summaryLines.push("#### Changes Detail");
4125
- for (const nd of nodeDiffs.filter((n) => n.status !== "unchanged")) {
4126
- summaryLines.push(`- **${nd.id}** (${nd.status.toUpperCase()}): ${nd.changes.join(", ")}`);
4442
+ const beforeGroups = beforeAST.groups || {};
4443
+ const afterGroups = afterAST.groups || {};
4444
+ const beforeGroupIds = new Set(Object.keys(beforeGroups));
4445
+ const afterGroupIds = new Set(Object.keys(afterGroups));
4446
+ for (const [id, afterGroup] of Object.entries(afterGroups)) {
4447
+ if (!beforeGroupIds.has(id)) {
4448
+ groupDiffs.push({ id, status: "added", after: afterGroup, changes: ["New security / subsystem boundary"] });
4449
+ } else {
4450
+ const beforeGroup = beforeGroups[id];
4451
+ const beforeMembers = new Set(beforeGroup.members || []);
4452
+ const afterMembers = new Set(afterGroup.members || []);
4453
+ const diffMembers = (afterGroup.members || []).filter((m) => !beforeMembers.has(m));
4454
+ if (diffMembers.length > 0 || beforeGroup.members.length !== afterGroup.members.length) {
4455
+ groupDiffs.push({
4456
+ id,
4457
+ status: "modified",
4458
+ before: beforeGroup,
4459
+ after: afterGroup,
4460
+ changes: [`Boundary membership updated: [${(afterGroup.members || []).join(", ")}]`]
4461
+ });
4462
+ }
4127
4463
  }
4128
- summaryLines.push("");
4129
4464
  }
4130
- const evolutionLines = [
4131
- `scene theme=${afterAST.meta.theme || "paper"}`,
4132
- `layout ${afterAST.meta.direction || "LR"}`,
4133
- ""
4134
- ];
4135
- for (const [id, node] of Object.entries({ ...beforeAST.nodes, ...afterAST.nodes })) {
4136
- evolutionLines.push(`${node.kind} ${id} "${node.label}"`);
4137
- }
4138
- evolutionLines.push("");
4139
- evolutionLines.push('beat v1 "Baseline Architecture":');
4140
- const v1NodeIds = Object.keys(beforeAST.nodes).join(" ");
4141
- if (v1NodeIds) {
4142
- evolutionLines.push(` show ${v1NodeIds}`);
4143
- }
4144
- evolutionLines.push("");
4145
- evolutionLines.push('beat transition "Migrate to Target Architecture":');
4146
- const addedIds = nodeDiffs.filter((n) => n.status === "added").map((n) => n.id);
4147
- const removedIds = nodeDiffs.filter((n) => n.status === "removed").map((n) => n.id);
4148
- if (removedIds.length > 0) {
4149
- evolutionLines.push(` hide ${removedIds.join(" ")}`);
4150
- }
4151
- if (addedIds.length > 0) {
4152
- evolutionLines.push(` show ${addedIds.join(" ")}`);
4153
- evolutionLines.push(` glow ${addedIds.join(" ")} color="#10b981"`);
4465
+ for (const [id, beforeGroup] of Object.entries(beforeGroups)) {
4466
+ if (!afterGroupIds.has(id)) {
4467
+ groupDiffs.push({ id, status: "removed", before: beforeGroup, changes: ["Dissolved boundary"] });
4468
+ }
4154
4469
  }
4470
+ const evolutionMarkdyScript = generateEvolutionMarkdyScript(
4471
+ beforeAST,
4472
+ afterAST,
4473
+ nodeDiffs,
4474
+ edgeDiffs
4475
+ );
4476
+ const summaryMarkdown = [
4477
+ `# Markdy Architectural Diff Summary`,
4478
+ `- **Nodes Added**: ${addedNodesCount}`,
4479
+ `- **Nodes Removed**: ${removedNodesCount}`,
4480
+ `- **Nodes Modified**: ${modifiedNodesCount}`,
4481
+ `- **Routes Added**: ${addedEdgesCount}`,
4482
+ `- **Routes Removed**: ${removedEdgesCount}`,
4483
+ "",
4484
+ `### Component Delta`,
4485
+ `| Component | Status | Details |`,
4486
+ `| :--- | :--- | :--- |`,
4487
+ ...nodeDiffs.filter((n) => n.status !== "unchanged").map((n) => `| \`${n.id}\` | **${n.status.toUpperCase()}** | ${n.changes.join("; ")} |`),
4488
+ "",
4489
+ `### Connection Delta`,
4490
+ `| Connection | Status | Details |`,
4491
+ `| :--- | :--- | :--- |`,
4492
+ ...edgeDiffs.filter((e) => e.status !== "unchanged").map((e) => `| \`${e.key}\` | **${e.status.toUpperCase()}** | ${e.changes.join("; ")} |`)
4493
+ ].join("\n");
4155
4494
  return {
4156
4495
  nodes: nodeDiffs,
4157
4496
  edges: edgeDiffs,
4497
+ groups: groupDiffs,
4158
4498
  addedNodesCount,
4159
4499
  removedNodesCount,
4160
4500
  modifiedNodesCount,
4161
- summaryMarkdown: summaryLines.join("\n"),
4162
- evolutionMarkdyScript: evolutionLines.join("\n")
4501
+ addedEdgesCount,
4502
+ removedEdgesCount,
4503
+ summaryMarkdown,
4504
+ evolutionMarkdyScript
4163
4505
  };
4164
4506
  }
4507
+ function generateEvolutionMarkdyScript(beforeAST, afterAST, nodeDiffs, edgeDiffs) {
4508
+ const lines = [`scene theme=paper`, `layout ${afterAST.meta?.direction || "LR"}`, ""];
4509
+ const allNodes = /* @__PURE__ */ new Map();
4510
+ for (const [id, n] of Object.entries(beforeAST.nodes || {})) allNodes.set(id, n);
4511
+ for (const [id, n] of Object.entries(afterAST.nodes || {})) allNodes.set(id, n);
4512
+ for (const [id, node] of allNodes.entries()) {
4513
+ const propsStr = Object.entries(node.props || {}).map(([k, v]) => `${k}=${typeof v === "string" ? `"${v}"` : v}`).join(" ");
4514
+ lines.push(`${node.kind} ${id} "${node.label}" ${propsStr}`.trim());
4515
+ }
4516
+ lines.push("");
4517
+ const beforeNodeIds = Object.keys(beforeAST.nodes || {}).join(" ");
4518
+ lines.push(`beat baseline:`);
4519
+ lines.push(` show ${beforeNodeIds}`);
4520
+ for (const edge of beforeAST.edges || []) {
4521
+ lines.push(` ${edge.from} -> ${edge.to} "${edge.label || ""}"`);
4522
+ }
4523
+ const removedNodeIds = nodeDiffs.filter((n) => n.status === "removed").map((n) => n.id);
4524
+ const addedNodeIds = nodeDiffs.filter((n) => n.status === "added").map((n) => n.id);
4525
+ lines.push("");
4526
+ lines.push(`beat transition:`);
4527
+ if (removedNodeIds.length > 0) {
4528
+ lines.push(` glow ${removedNodeIds.join(" ")} color=#fb7185 strength=1.2`);
4529
+ lines.push(` hide ${removedNodeIds.join(" ")} dur=800ms`);
4530
+ }
4531
+ if (addedNodeIds.length > 0) {
4532
+ lines.push(` show ${addedNodeIds.join(" ")} stagger=80ms`);
4533
+ lines.push(` glow ${addedNodeIds.join(" ")} color=#34d399 strength=1.5`);
4534
+ }
4535
+ return lines.join("\n");
4536
+ }
4165
4537
 
4166
4538
  // src/url-codec.ts
4167
4539
  var PREFIX = "~m";
@@ -4253,16 +4625,27 @@ async function decompressMarkdyFromUrlHash(hash) {
4253
4625
  }
4254
4626
 
4255
4627
  // src/router.ts
4256
- function getBoxPortPosition(box, port) {
4257
- switch (port) {
4258
- case "left":
4259
- return { x: box.x, y: box.y + box.height / 2 };
4260
- case "right":
4261
- return { x: box.x + box.width, y: box.y + box.height / 2 };
4262
- case "top":
4263
- return { x: box.x + box.width / 2, y: box.y };
4264
- case "bottom":
4265
- return { x: box.x + box.width / 2, y: box.y + box.height };
4628
+ function getBoxPortPosition(box, port, lane) {
4629
+ if (port === "left" || port === "right") {
4630
+ const x = port === "left" ? box.x : box.x + box.width;
4631
+ if (lane && lane.total > 1) {
4632
+ const padding = Math.min(16, box.height * 0.18);
4633
+ const span = box.height - padding * 2;
4634
+ const step = span / (lane.total - 1 || 1);
4635
+ const y = box.y + padding + lane.index * step;
4636
+ return { x, y };
4637
+ }
4638
+ return { x, y: box.y + box.height / 2 };
4639
+ } else {
4640
+ const y = port === "top" ? box.y : box.y + box.height;
4641
+ if (lane && lane.total > 1) {
4642
+ const padding = Math.min(16, box.width * 0.18);
4643
+ const span = box.width - padding * 2;
4644
+ const step = span / (lane.total - 1 || 1);
4645
+ const x = box.x + padding + lane.index * step;
4646
+ return { x, y };
4647
+ }
4648
+ return { x: box.x + box.width / 2, y };
4266
4649
  }
4267
4650
  }
4268
4651
  function selectOptimalPorts(sourceBox, targetBox) {
@@ -4276,12 +4659,96 @@ function selectOptimalPorts(sourceBox, targetBox) {
4276
4659
  return dy > 0 ? { sourcePort: "bottom", targetPort: "top" } : { sourcePort: "top", targetPort: "bottom" };
4277
4660
  }
4278
4661
  }
4279
- function routeOrthogonalEdge(sourceBox, targetBox) {
4280
- const { sourcePort, targetPort } = selectOptimalPorts(sourceBox, targetBox);
4281
- const start = getBoxPortPosition(sourceBox, sourcePort);
4282
- const end = getBoxPortPosition(targetBox, targetPort);
4662
+ function buildSmoothSvgPath(start, waypoints, end, cornerRadius = 0) {
4663
+ const allPoints = [start, ...waypoints, end];
4664
+ if (allPoints.length <= 2 || cornerRadius <= 0) {
4665
+ let d2 = `M ${start.x} ${start.y}`;
4666
+ for (const p of waypoints) {
4667
+ d2 += ` L ${p.x} ${p.y}`;
4668
+ }
4669
+ d2 += ` L ${end.x} ${end.y}`;
4670
+ return d2;
4671
+ }
4672
+ let d = `M ${start.x} ${start.y}`;
4673
+ for (let i = 1; i < allPoints.length - 1; i++) {
4674
+ const prev = allPoints[i - 1];
4675
+ const curr = allPoints[i];
4676
+ const next = allPoints[i + 1];
4677
+ const dPrev = Math.hypot(curr.x - prev.x, curr.y - prev.y);
4678
+ const dNext = Math.hypot(next.x - curr.x, next.y - curr.y);
4679
+ const r = Math.min(cornerRadius, dPrev / 2, dNext / 2);
4680
+ if (r < 2) {
4681
+ d += ` L ${curr.x} ${curr.y}`;
4682
+ continue;
4683
+ }
4684
+ const startX = curr.x + (prev.x - curr.x) * (r / dPrev);
4685
+ const startY = curr.y + (prev.y - curr.y) * (r / dPrev);
4686
+ const endX = curr.x + (next.x - curr.x) * (r / dNext);
4687
+ const endY = curr.y + (next.y - curr.y) * (r / dNext);
4688
+ d += ` L ${startX} ${startY}`;
4689
+ d += ` Q ${curr.x} ${curr.y} ${endX} ${endY}`;
4690
+ }
4691
+ d += ` L ${end.x} ${end.y}`;
4692
+ return d;
4693
+ }
4694
+ function routeOrthogonalEdge(sourceBox, targetBox, options = {}) {
4695
+ const isSelfLoop = sourceBox === targetBox || sourceBox.x === targetBox.x && sourceBox.y === targetBox.y && sourceBox.width === targetBox.width && sourceBox.height === targetBox.height;
4696
+ const MARGIN = options.margin ?? 20;
4697
+ if (isSelfLoop) {
4698
+ const loopSpan = Math.max(28, MARGIN * 1.5);
4699
+ const port = options.sourcePort || options.targetPort || "top";
4700
+ let start2;
4701
+ let end2;
4702
+ let waypoints2;
4703
+ if (port === "top") {
4704
+ const startX = sourceBox.x + sourceBox.width * 0.35;
4705
+ const endX = sourceBox.x + sourceBox.width * 0.65;
4706
+ const topY = sourceBox.y;
4707
+ const apexY = topY - loopSpan;
4708
+ start2 = { x: startX, y: topY };
4709
+ end2 = { x: endX, y: topY };
4710
+ waypoints2 = [{ x: startX, y: apexY }, { x: endX, y: apexY }];
4711
+ } else if (port === "bottom") {
4712
+ const startX = sourceBox.x + sourceBox.width * 0.35;
4713
+ const endX = sourceBox.x + sourceBox.width * 0.65;
4714
+ const botY = sourceBox.y + sourceBox.height;
4715
+ const apexY = botY + loopSpan;
4716
+ start2 = { x: startX, y: botY };
4717
+ end2 = { x: endX, y: botY };
4718
+ waypoints2 = [{ x: startX, y: apexY }, { x: endX, y: apexY }];
4719
+ } else if (port === "left") {
4720
+ const startY = sourceBox.y + sourceBox.height * 0.35;
4721
+ const endY = sourceBox.y + sourceBox.height * 0.65;
4722
+ const leftX = sourceBox.x;
4723
+ const apexX = leftX - loopSpan;
4724
+ start2 = { x: leftX, y: startY };
4725
+ end2 = { x: leftX, y: endY };
4726
+ waypoints2 = [{ x: apexX, y: startY }, { x: apexX, y: endY }];
4727
+ } else {
4728
+ const startY = sourceBox.y + sourceBox.height * 0.35;
4729
+ const endY = sourceBox.y + sourceBox.height * 0.65;
4730
+ const rightX = sourceBox.x + sourceBox.width;
4731
+ const apexX = rightX + loopSpan;
4732
+ start2 = { x: rightX, y: startY };
4733
+ end2 = { x: rightX, y: endY };
4734
+ waypoints2 = [{ x: apexX, y: startY }, { x: apexX, y: endY }];
4735
+ }
4736
+ const svgPathData2 = buildSmoothSvgPath(start2, waypoints2, end2, options.cornerRadius ?? 6);
4737
+ return {
4738
+ sourcePort: port,
4739
+ targetPort: port,
4740
+ startPoint: start2,
4741
+ endPoint: end2,
4742
+ waypoints: waypoints2,
4743
+ svgPathData: svgPathData2
4744
+ };
4745
+ }
4746
+ const optimal = selectOptimalPorts(sourceBox, targetBox);
4747
+ const sourcePort = options.sourcePort || optimal.sourcePort;
4748
+ const targetPort = options.targetPort || optimal.targetPort;
4749
+ const start = getBoxPortPosition(sourceBox, sourcePort, options.sourceLane);
4750
+ const end = getBoxPortPosition(targetBox, targetPort, options.targetLane);
4283
4751
  const waypoints = [];
4284
- const MARGIN = 20;
4285
4752
  if (sourcePort === "right" && targetPort === "left") {
4286
4753
  if (start.x <= end.x - MARGIN * 2) {
4287
4754
  const midX = (start.x + end.x) / 2;
@@ -4348,11 +4815,7 @@ function routeOrthogonalEdge(sourceBox, targetBox) {
4348
4815
  }
4349
4816
  waypoints.push(p2);
4350
4817
  }
4351
- let svgPathData = `M ${start.x} ${start.y}`;
4352
- for (const wp of waypoints) {
4353
- svgPathData += ` L ${wp.x} ${wp.y}`;
4354
- }
4355
- svgPathData += ` L ${end.x} ${end.y}`;
4818
+ const svgPathData = buildSmoothSvgPath(start, waypoints, end, options.cornerRadius ?? 6);
4356
4819
  return {
4357
4820
  sourcePort,
4358
4821
  targetPort,
@@ -4362,6 +4825,550 @@ function routeOrthogonalEdge(sourceBox, targetBox) {
4362
4825
  svgPathData
4363
4826
  };
4364
4827
  }
4828
+ function allocatePortLanes(edges, boxes) {
4829
+ const result = /* @__PURE__ */ new Map();
4830
+ const portGroups = /* @__PURE__ */ new Map();
4831
+ for (const edge of edges) {
4832
+ if (edge.from === edge.to) continue;
4833
+ const sBox = boxes[edge.from];
4834
+ const tBox = boxes[edge.to];
4835
+ if (!sBox || !tBox) continue;
4836
+ const { sourcePort, targetPort } = selectOptimalPorts(sBox, tBox);
4837
+ const sKey = `${edge.from}:${sourcePort}`;
4838
+ const tKey = `${edge.to}:${targetPort}`;
4839
+ const tgtCenter = { x: tBox.x + tBox.width / 2, y: tBox.y + tBox.height / 2 };
4840
+ const srcCenter = { x: sBox.x + sBox.width / 2, y: sBox.y + sBox.height / 2 };
4841
+ if (!portGroups.has(sKey)) portGroups.set(sKey, []);
4842
+ portGroups.get(sKey).push({
4843
+ edge,
4844
+ role: "source",
4845
+ otherCenterY: tgtCenter.y,
4846
+ otherCenterX: tgtCenter.x
4847
+ });
4848
+ if (!portGroups.has(tKey)) portGroups.set(tKey, []);
4849
+ portGroups.get(tKey).push({
4850
+ edge,
4851
+ role: "target",
4852
+ otherCenterY: srcCenter.y,
4853
+ otherCenterX: srcCenter.x
4854
+ });
4855
+ }
4856
+ for (const [key, list] of portGroups.entries()) {
4857
+ if (list.length <= 1) continue;
4858
+ const isVertical = key.endsWith(":left") || key.endsWith(":right");
4859
+ list.sort((a, b) => {
4860
+ if (isVertical) {
4861
+ if (Math.abs(a.otherCenterY - b.otherCenterY) > 1) {
4862
+ return a.otherCenterY - b.otherCenterY;
4863
+ }
4864
+ } else {
4865
+ if (Math.abs(a.otherCenterX - b.otherCenterX) > 1) {
4866
+ return a.otherCenterX - b.otherCenterX;
4867
+ }
4868
+ }
4869
+ const aMin = a.edge.from < a.edge.to ? a.edge.from : a.edge.to;
4870
+ const aMax = a.edge.from < a.edge.to ? a.edge.to : a.edge.from;
4871
+ const bMin = b.edge.from < b.edge.to ? b.edge.from : b.edge.to;
4872
+ const bMax = b.edge.from < b.edge.to ? b.edge.to : b.edge.from;
4873
+ if (aMin !== bMin || aMax !== bMax) {
4874
+ return (a.edge.id || "").localeCompare(b.edge.id || "");
4875
+ }
4876
+ const aDir = a.edge.from < a.edge.to ? 0 : 1;
4877
+ const bDir = b.edge.from < b.edge.to ? 0 : 1;
4878
+ return aDir - bDir;
4879
+ });
4880
+ list.forEach((item, index) => {
4881
+ const existing = result.get(item.edge) || {};
4882
+ if (item.role === "source") {
4883
+ existing.sourceLane = { index, total: list.length };
4884
+ } else {
4885
+ existing.targetLane = { index, total: list.length };
4886
+ }
4887
+ result.set(item.edge, existing);
4888
+ });
4889
+ }
4890
+ return result;
4891
+ }
4892
+
4893
+ // src/symbols.ts
4894
+ var VECTOR_SYMBOLS = {
4895
+ // Cloud & Infrastructure
4896
+ aws: {
4897
+ name: "AWS",
4898
+ category: "cloud",
4899
+ viewBox: "0 0 24 24",
4900
+ svgPaths: `<path fill="currentColor" d="M12 2L2 7l10 5 10-5-10-5zm0 8.5L4.5 7 12 3.25 19.5 7 12 10.5zM2 17l10 5 10-5M2 12l10 5 10-5"/>`,
4901
+ brandColor: "#FF9900"
4902
+ },
4903
+ gcp: {
4904
+ name: "Google Cloud",
4905
+ category: "cloud",
4906
+ viewBox: "0 0 24 24",
4907
+ svgPaths: `<path fill="currentColor" d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3z"/>`,
4908
+ brandColor: "#4285F4"
4909
+ },
4910
+ azure: {
4911
+ name: "Microsoft Azure",
4912
+ category: "cloud",
4913
+ viewBox: "0 0 24 24",
4914
+ svgPaths: `<path fill="currentColor" d="M13.05 4.24l-6.1 11.23L2 17.52l7.73-14.28 3.32 1zm1.09 1.94L18.42 16h-7.8l-1.92 3.76H22l-7.86-13.58z"/>`,
4915
+ brandColor: "#0089D6"
4916
+ },
4917
+ kubernetes: {
4918
+ name: "Kubernetes",
4919
+ category: "compute",
4920
+ viewBox: "0 0 24 24",
4921
+ svgPaths: `<path fill="currentColor" d="M12 2l8.66 5v10L12 22l-8.66-5V7L12 2zm0 2.31L5.34 7.69v7.62L12 18.69l6.66-3.38V7.69L12 4.31zm0 3.69a4 4 0 1 1 0 8 4 4 0 0 1 0-8z"/>`,
4922
+ brandColor: "#326CE5"
4923
+ },
4924
+ docker: {
4925
+ name: "Docker",
4926
+ category: "compute",
4927
+ viewBox: "0 0 24 24",
4928
+ svgPaths: `<path fill="currentColor" d="M13.98 11.08h1.83V9.25h-1.83v1.83zm-2.75 0h1.83V9.25h-1.83v1.83zm-2.75 0h1.83V9.25H8.48v1.83zm-2.75 0h1.83V9.25H5.73v1.83zm8.25-2.75h1.83V6.5h-1.83v1.83zm-2.75 0h1.83V6.5h-1.83v1.83zm-2.75 0h1.83V6.5H8.48v1.83zm8.25 0h1.83V6.5h-1.83v1.83zm1.83 5.5c-.4 0-.8.1-1.1.3-.8-1.5-2.4-2.5-4.3-2.5H2.4c-.2.7-.4 1.4-.4 2.2 0 4.4 3.6 8 8 8 5 0 9.2-3.6 9.9-8.4.8.1 1.6-.2 2.1-.8.4-.5.5-1.2.3-1.8-.7.7-1.5 1-2.3 1z"/>`,
4929
+ brandColor: "#2496ED"
4930
+ },
4931
+ cloudflare: {
4932
+ name: "Cloudflare",
4933
+ category: "cloud",
4934
+ viewBox: "0 0 24 24",
4935
+ svgPaths: `<path fill="currentColor" d="M18.42 10.36A6.5 6.5 0 0 0 7.2 9.04 4.5 4.5 0 0 0 3 13.5a4.5 4.5 0 0 0 4.5 4.5h11a3.5 3.5 0 0 0 .92-6.88v-.76z"/>`,
4936
+ brandColor: "#F38020"
4937
+ },
4938
+ s3: {
4939
+ name: "Amazon S3",
4940
+ category: "cloud",
4941
+ viewBox: "0 0 24 24",
4942
+ svgPaths: `<path fill="currentColor" d="M12 2L4 6v12l8 4 8-4V6l-8-4zm0 2.2L18.2 7 12 9.8 5.8 7 12 4.2zM6 8.5l5 2.5v8.2L6 16.7V8.5zm12 8.2l-5 2.5V11l5-2.5v8.2z"/>`,
4943
+ brandColor: "#E05243"
4944
+ },
4945
+ // Databases & Stores
4946
+ postgresql: {
4947
+ name: "PostgreSQL",
4948
+ category: "database",
4949
+ viewBox: "0 0 24 24",
4950
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.4z"/>`,
4951
+ brandColor: "#336791"
4952
+ },
4953
+ mysql: {
4954
+ name: "MySQL",
4955
+ category: "database",
4956
+ viewBox: "0 0 24 24",
4957
+ svgPaths: `<path fill="currentColor" d="M12 3c-4.97 0-9 1.79-9 4v10c0 2.21 4.03 4 9 4s9-1.79 9-4V7c0-2.21-4.03-4-9-4zm0 2c4.41 0 7 1.43 7 2s-2.59 2-7 2-7-1.43-7-2 2.59-2 7-2zm0 14c-4.41 0-7-1.43-7-2v-1.82c1.78 1.11 4.29 1.82 7 1.82s5.22-.71 7-1.82V17c0 .57-2.59 2-7 2zm0-5c-4.41 0-7-1.43-7-2v-1.82c1.78 1.11 4.29 1.82 7 1.82s5.22-.71 7-1.82V12c0 .57-2.59 2-7 2z"/>`,
4958
+ brandColor: "#4479A1"
4959
+ },
4960
+ redis: {
4961
+ name: "Redis",
4962
+ category: "database",
4963
+ viewBox: "0 0 24 24",
4964
+ svgPaths: `<path fill="currentColor" d="M12 2L2 7.5l10 5.5 10-5.5L12 2zm-8 8.7V17L12 22.5V16L4 10.7zm16 0L12 16v6.5l8-5.5v-6.3z"/>`,
4965
+ brandColor: "#DC382D"
4966
+ },
4967
+ mongodb: {
4968
+ name: "MongoDB",
4969
+ category: "database",
4970
+ viewBox: "0 0 24 24",
4971
+ svgPaths: `<path fill="currentColor" d="M12 2C11.5 3.5 7 10.5 7 15c0 3.5 2.5 6 5 7 2.5-1 5-3.5 5-7 0-4.5-4.5-11.5-5-13zm0 17.5c-1.5-.7-3-2.5-3-4.5 0-2.8 2.2-7 3-9 0.8 2 3 6.2 3 9 0 2-1.5 3.8-3 4.5z"/>`,
4972
+ brandColor: "#47A248"
4973
+ },
4974
+ cassandra: {
4975
+ name: "Apache Cassandra",
4976
+ category: "database",
4977
+ viewBox: "0 0 24 24",
4978
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8zm-4-9h8v2H8z"/>`,
4979
+ brandColor: "#1287B1"
4980
+ },
4981
+ sqlite: {
4982
+ name: "SQLite",
4983
+ category: "database",
4984
+ viewBox: "0 0 24 24",
4985
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 4.69 2 8v8c0 3.31 4.48 6 10 6s10-2.69 10-6V8c0-3.31-4.48-6-10-6zm0 2c4.42 0 8 1.79 8 4s-3.58 4-8 4-8-1.79-8-4 3.58-4 8-4zm0 16c-4.42 0-8-1.79-8-4v-2.38c2.08 1.47 5.04 2.38 8 2.38s5.92-.91 8-2.38V16c0 2.21-3.58 4-8 4z"/>`,
4986
+ brandColor: "#003B57"
4987
+ },
4988
+ kafka: {
4989
+ name: "Apache Kafka",
4990
+ category: "messaging",
4991
+ viewBox: "0 0 24 24",
4992
+ svgPaths: `<path fill="currentColor" d="M12 3a9 9 0 1 0 9 9 9 9 0 0 0-9-9zm0 16a7 7 0 1 1 7-7 7 7 0 0 1-7 7zm-3-8a2 2 0 1 0-2-2 2 2 0 0 0 2 2zm6 0a2 2 0 1 0-2-2 2 2 0 0 0 2 2zm-3 6a2 2 0 1 0-2-2 2 2 0 0 0 2 2z"/>`,
4993
+ brandColor: "#231F20"
4994
+ },
4995
+ rabbitmq: {
4996
+ name: "RabbitMQ",
4997
+ category: "messaging",
4998
+ viewBox: "0 0 24 24",
4999
+ svgPaths: `<path fill="currentColor" d="M12 2a5 5 0 0 0-5 5v1H5a3 3 0 0 0-3 3v7a4 4 0 0 0 4 4h12a4 4 0 0 0 4-4v-7a3 3 0 0 0-3-3h-2V7a5 5 0 0 0-5-5zm-3 6a3 3 0 0 1 6 0v1H9V8zm-3 5h2v2H6v-2zm12 0h2v2h-2v-2z"/>`,
5000
+ brandColor: "#FF6600"
5001
+ },
5002
+ elasticsearch: {
5003
+ name: "Elasticsearch",
5004
+ category: "database",
5005
+ viewBox: "0 0 24 24",
5006
+ svgPaths: `<path fill="currentColor" d="M12 2A10 10 0 1 0 22 12 10 10 0 0 0 12 2zm-1 3.1a6.9 6.9 0 0 1 5.5 2.9h-11A6.9 6.9 0 0 1 11 5.1zM5.1 12c0-.7.1-1.3.3-2h13.2c.2.7.3 1.3.3 2s-.1 1.3-.3 2H5.4c-.2-.7-.3-1.3-.3-2zm5.9 6.9a6.9 6.9 0 0 1-5.5-2.9h11a6.9 6.9 0 0 1-5.5 2.9z"/>`,
5007
+ brandColor: "#005571"
5008
+ },
5009
+ // Compute, Gateway & Runtimes
5010
+ nodejs: {
5011
+ name: "Node.js",
5012
+ category: "runtime",
5013
+ viewBox: "0 0 24 24",
5014
+ svgPaths: `<path fill="currentColor" d="M12 2L3.5 7v10L12 22l8.5-5V7L12 2zm6.5 13.7L12 19.5l-6.5-3.8V8.3L12 4.5l6.5 3.8v7.4z"/>`,
5015
+ brandColor: "#339933"
5016
+ },
5017
+ python: {
5018
+ name: "Python",
5019
+ category: "runtime",
5020
+ viewBox: "0 0 24 24",
5021
+ svgPaths: `<path fill="currentColor" d="M11.9 2c-3.1 0-4.9.4-4.9 2.2V6h5v1.5H5.8C3.7 7.5 2 9.2 2 11.3c0 2.2 1.4 3.7 3.8 3.7h1.4v-1.9c0-1.8 1.6-3.3 3.5-3.3h5.2V7.7C15.9 4.1 14.8 2 11.9 2zM9 4.2a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6zm3.1 17.8c3.1 0 4.9-.4 4.9-2.2V18h-5v-1.5h6.2c2.1 0 3.8-1.7 3.8-3.8 0-2.2-1.4-3.7-3.8-3.7h-1.4v1.9c0 1.8-1.6 3.3-3.5 3.3H8.6v2.1c0 3.6 1.1 5.7 4.5 5.7zM15 18.2a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6z"/>`,
5022
+ brandColor: "#3776AB"
5023
+ },
5024
+ golang: {
5025
+ name: "Go",
5026
+ category: "runtime",
5027
+ viewBox: "0 0 24 24",
5028
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4 11h-3v2h3v2h-5v-6h5v2zm-7-2H7v-2h2v2z"/>`,
5029
+ brandColor: "#00ADD8"
5030
+ },
5031
+ rust: {
5032
+ name: "Rust",
5033
+ category: "runtime",
5034
+ viewBox: "0 0 24 24",
5035
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 14.5l-2-3h-1v3H8V7.5h4a3 3 0 0 1 3 3 3 3 0 0 1-2 2.8l2 3.2zm-1-6.5h-2v2h2a1 1 0 0 0 1-1 1 1 0 0 0-1-1z"/>`,
5036
+ brandColor: "#DEA584"
5037
+ },
5038
+ nginx: {
5039
+ name: "Nginx",
5040
+ category: "gateway",
5041
+ viewBox: "0 0 24 24",
5042
+ svgPaths: `<path fill="currentColor" d="M12 2L2 7.8v8.4L12 22l10-5.8V7.8L12 2zm-4 13.5V8.5l3 3.5v3.5l-3-3.5zm8 0l-3-3.5V8.5l3 3.5v3.5z"/>`,
5043
+ brandColor: "#009639"
5044
+ },
5045
+ envoy: {
5046
+ name: "Envoy Proxy",
5047
+ category: "gateway",
5048
+ viewBox: "0 0 24 24",
5049
+ svgPaths: `<path fill="currentColor" d="M12 2L2 7v10l10 5 10-5V7L12 2zm0 2.2L19.5 8 12 11.8 4.5 8 12 4.2zM4 9.5l7 3.5v7.2L4 16.7V9.5zm16 7.2l-7 3.5V13l7-3.5v7.2z"/>`,
5050
+ brandColor: "#BF360C"
5051
+ },
5052
+ graphql: {
5053
+ name: "GraphQL",
5054
+ category: "gateway",
5055
+ viewBox: "0 0 24 24",
5056
+ svgPaths: `<path fill="currentColor" d="M12 2l8.66 5v10L12 22l-8.66-5V7L12 2zm0 2.4L5.5 8.1v7.8L12 19.6l6.5-3.7V8.1L12 4.4zM12 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm0 6a2 2 0 1 1 0 4 2 2 0 0 1 0-4z"/>`,
5057
+ brandColor: "#E10098"
5058
+ },
5059
+ // Clients & Browsers
5060
+ chrome: {
5061
+ name: "Google Chrome",
5062
+ category: "client",
5063
+ viewBox: "0 0 24 24",
5064
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 3.6a6.4 6.4 0 0 1 5.48 3.1h-5.48a3.3 3.3 0 0 0-3.1 2.2L6.16 6.16A6.36 6.36 0 0 1 12 5.6zm-6.4 6.4a6.4 6.4 0 0 1 .52-2.54l2.74 4.74a3.3 3.3 0 0 0 3.1 1.76v4.6A6.4 6.4 0 0 1 5.6 12zm6.4 6.4a6.36 6.36 0 0 1-4.74-2.14l2.74-4.74a3.3 3.3 0 0 0 2 0l2.74 4.74A6.36 6.36 0 0 1 12 18.4zm0-4.4a2 2 0 1 1 2-2 2 2 0 0 1-2 2z"/>`,
5065
+ brandColor: "#4285F4"
5066
+ },
5067
+ terminal: {
5068
+ name: "Terminal",
5069
+ category: "client",
5070
+ viewBox: "0 0 24 24",
5071
+ svgPaths: `<path fill="currentColor" d="M20 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H4V8h16v10zm-12-3l3-3-3-3 1.41-1.41L12.83 12l-3.42 3.41L8 15zm6 0h4v2h-4v-2z"/>`,
5072
+ brandColor: "#4ADE80"
5073
+ },
5074
+ // Security & Identity
5075
+ vault: {
5076
+ name: "HashiCorp Vault",
5077
+ category: "security",
5078
+ viewBox: "0 0 24 24",
5079
+ svgPaths: `<path fill="currentColor" d="M12 2L4 6v6c0 5.55 3.84 10.74 8 12 4.16-1.26 8-6.45 8-12V6l-8-4zm0 6a3 3 0 0 1 3 3c0 1.3-.84 2.4-2 2.82V17h-2v-3.18A3 3 0 0 1 9 11a3 3 0 0 1 3-3z"/>`,
5080
+ brandColor: "#000000"
5081
+ },
5082
+ opa: {
5083
+ name: "Open Policy Agent",
5084
+ category: "security",
5085
+ viewBox: "0 0 24 24",
5086
+ svgPaths: `<path fill="currentColor" d="M12 2L3 7v10l9 5 9-5V7l-9-5zm0 2.3l6.7 3.7v7.4L12 19.1 5.3 15.4V8l6.7-3.7zM12 7a5 5 0 1 0 5 5 5 5 0 0 0-5-5z"/>`,
5087
+ brandColor: "#5B7382"
5088
+ },
5089
+ keycloak: {
5090
+ name: "Keycloak",
5091
+ category: "security",
5092
+ viewBox: "0 0 24 24",
5093
+ svgPaths: `<path fill="currentColor" d="M12 2a5 5 0 0 0-5 5v3H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-2V7a5 5 0 0 0-5-5zm-3 5a3 3 0 0 1 6 0v3H9V7zm3 6a2 2 0 0 1 2 2v2a2 2 0 0 1-4 0v-2a2 2 0 0 1 2-2z"/>`,
5094
+ brandColor: "#0088CE"
5095
+ },
5096
+ // Observability & SRE
5097
+ prometheus: {
5098
+ name: "Prometheus",
5099
+ category: "observability",
5100
+ viewBox: "0 0 24 24",
5101
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 14h-2v-6h2v6zm0-8h-2V6h2v2z"/>`,
5102
+ brandColor: "#E6522C"
5103
+ },
5104
+ datadog: {
5105
+ name: "Datadog",
5106
+ category: "observability",
5107
+ viewBox: "0 0 24 24",
5108
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm4.5 14H15v-3h-2v3h-1.5v-6H13v1.5h2V10h1.5v6zm-7 0H8v-6h3a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H9.5zm0-4.5V14H11a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5H9.5z"/>`,
5109
+ brandColor: "#632CA6"
5110
+ },
5111
+ jaeger: {
5112
+ name: "Jaeger Tracing",
5113
+ category: "observability",
5114
+ viewBox: "0 0 24 24",
5115
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm-1 5h2v6h-2zm0 8h2v2h-2z"/>`,
5116
+ brandColor: "#60D0E4"
5117
+ },
5118
+ grafana: {
5119
+ name: "Grafana",
5120
+ category: "observability",
5121
+ viewBox: "0 0 24 24",
5122
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 14.93c-2.82-.41-5-2.85-5-5.93 0-.82.16-1.6.46-2.31l3.07 3.07c-.12.4-.19.82-.19 1.25 0 1.66 1.34 3 3 3 .43 0 .85-.07 1.25-.19l-2.59-2.59zM16.5 12c0 1.93-1.12 3.6-2.76 4.38l-4.12-4.12A4.47 4.47 0 0 1 12 7.5c2.48 0 4.5 2.02 4.5 4.5z"/>`,
5123
+ brandColor: "#F46800"
5124
+ },
5125
+ pagerduty: {
5126
+ name: "PagerDuty",
5127
+ category: "observability",
5128
+ viewBox: "0 0 24 24",
5129
+ svgPaths: `<path fill="currentColor" d="M5 3h7a6 6 0 0 1 6 6 6 6 0 0 1-6 6H9v6H5V3zm4 8h3a2 2 0 0 0 2-2 2 2 0 0 0-2-2H9v4z"/>`,
5130
+ brandColor: "#04AC38"
5131
+ },
5132
+ slack: {
5133
+ name: "Slack",
5134
+ category: "messaging",
5135
+ viewBox: "0 0 24 24",
5136
+ svgPaths: `<path fill="currentColor" d="M6 15a2 2 0 1 1-2-2h2v2zm1 0a2 2 0 0 1 2-2 2 2 0 0 1 2 2v5a2 2 0 1 1-4 0v-5zm2-8a2 2 0 1 1-2-2 2 2 0 0 1 2 2v2zm0 1a2 2 0 0 1 2 2 2 2 0 0 1-2 2H4a2 2 0 1 1 0-4h5zm8 2a2 2 0 1 1 2 2h-2v-2zm-1 0a2 2 0 0 1-2 2 2 2 0 0 1-2-2V5a2 2 0 1 1 4 0v5zm-2 8a2 2 0 1 1 2 2 2 2 0 0 1-2-2v-2zm0-1a2 2 0 0 1-2-2 2 2 0 0 1 2-2h5a2 2 0 1 1 0 4h-5z"/>`,
5137
+ brandColor: "#4A154B"
5138
+ },
5139
+ // Big Data, Lakehouse & AI
5140
+ spark: {
5141
+ name: "Apache Spark",
5142
+ category: "data",
5143
+ viewBox: "0 0 24 24",
5144
+ svgPaths: `<path fill="currentColor" d="M12 2l2.4 7.4h7.6l-6.2 4.5 2.4 7.4-6.2-4.5-6.2 4.5 2.4-7.4-6.2-4.5h7.6z"/>`,
5145
+ brandColor: "#E25A1C"
5146
+ },
5147
+ flink: {
5148
+ name: "Apache Flink",
5149
+ category: "data",
5150
+ viewBox: "0 0 24 24",
5151
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 15h-2v-5h2zm0-7h-2V8h2z"/>`,
5152
+ brandColor: "#E6522C"
5153
+ },
5154
+ snowflake: {
5155
+ name: "Snowflake",
5156
+ category: "data",
5157
+ viewBox: "0 0 24 24",
5158
+ svgPaths: `<path fill="currentColor" d="M12 2v20m-7.07-2.93l14.14-14.14M2 12h20M4.93 4.93l14.14 14.14" stroke="currentColor" stroke-width="2" fill="none"/>`,
5159
+ brandColor: "#29B5E8"
5160
+ },
5161
+ delta: {
5162
+ name: "Delta Lake",
5163
+ category: "data",
5164
+ viewBox: "0 0 24 24",
5165
+ svgPaths: `<path fill="currentColor" d="M12 2L2 22h20L12 2zm0 5l6.5 13H5.5L12 7z"/>`,
5166
+ brandColor: "#00A4E4"
5167
+ },
5168
+ superset: {
5169
+ name: "Apache Superset",
5170
+ category: "data",
5171
+ viewBox: "0 0 24 24",
5172
+ svgPaths: `<path fill="currentColor" d="M3 3h4v18H3zm7 6h4v12h-4zm7-4h4v16h-4z"/>`,
5173
+ brandColor: "#20A6B2"
5174
+ },
5175
+ gemini: {
5176
+ name: "Google Gemini",
5177
+ category: "compute",
5178
+ viewBox: "0 0 24 24",
5179
+ svgPaths: `<path fill="currentColor" d="M12 2C12 7.52 7.52 12 2 12c5.48 0 9.95 4.48 10 10 .05-5.52 4.52-10 10-10-5.48 0-9.95-4.48-10-10z"/>`,
5180
+ brandColor: "#8E75FF"
5181
+ }
5182
+ };
5183
+ function resolveVectorSymbol(nameOrAlias) {
5184
+ if (!nameOrAlias || typeof nameOrAlias !== "string") return null;
5185
+ const key = nameOrAlias.trim().toLowerCase().replace(/[\s_.-]+/g, "");
5186
+ if (VECTOR_SYMBOLS[key]) return VECTOR_SYMBOLS[key];
5187
+ const aliases = {
5188
+ postgres: "postgresql",
5189
+ pg: "postgresql",
5190
+ k8s: "kubernetes",
5191
+ kube: "kubernetes",
5192
+ cf: "cloudflare",
5193
+ node: "nodejs",
5194
+ py: "python",
5195
+ go: "golang",
5196
+ elastic: "elasticsearch",
5197
+ es: "elasticsearch",
5198
+ rabbit: "rabbitmq",
5199
+ mq: "rabbitmq",
5200
+ browser: "chrome",
5201
+ web: "chrome",
5202
+ cli: "terminal",
5203
+ console: "terminal",
5204
+ shell: "terminal",
5205
+ awss3: "s3",
5206
+ deltalake: "delta",
5207
+ lakehouse: "delta",
5208
+ llm: "gemini",
5209
+ ai: "gemini"
5210
+ };
5211
+ const target = aliases[key];
5212
+ return target ? VECTOR_SYMBOLS[target] || null : null;
5213
+ }
5214
+ function renderSymbolSvg(symbol, options) {
5215
+ const resolved = typeof symbol === "string" ? resolveVectorSymbol(symbol) : symbol;
5216
+ if (!resolved) return null;
5217
+ const size = options?.size ?? 18;
5218
+ const className = options?.className ? ` class="${options.className}"` : "";
5219
+ const style = options?.color ? ` style="color: ${options.color}"` : "";
5220
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${resolved.viewBox}" width="${size}" height="${size}"${className}${style} aria-hidden="true">${resolved.svgPaths}</svg>`;
5221
+ }
5222
+ function listAvailableSymbols() {
5223
+ return Object.keys(VECTOR_SYMBOLS);
5224
+ }
5225
+
5226
+ // src/provenance.ts
5227
+ var CONTROL_CHAR_RE = /[\u0000-\u001f\u007f]/;
5228
+ function parseCodeAnchor(raw, repositoryUrl, revision) {
5229
+ if (typeof raw !== "string" || !raw.trim()) return null;
5230
+ const trimmed = raw.trim();
5231
+ const ghMatch = trimmed.match(/^(?:https:\/\/github\.com\/[^/]+\/[^/]+\/blob\/([^/]+)\/)([^#]+)(?:#L(\d+)(?:-L(\d+))?)?$/i);
5232
+ if (ghMatch) {
5233
+ const rev = ghMatch[1];
5234
+ const filePath2 = decodeURIComponent(ghMatch[2]);
5235
+ const startLine2 = ghMatch[3] ? parseInt(ghMatch[3], 10) : void 0;
5236
+ const endLine2 = ghMatch[4] ? parseInt(ghMatch[4], 10) : startLine2;
5237
+ return {
5238
+ raw: trimmed,
5239
+ filePath: filePath2,
5240
+ startLine: startLine2,
5241
+ endLine: endLine2,
5242
+ revision: rev,
5243
+ resolvedHref: trimmed
5244
+ };
5245
+ }
5246
+ const [filePathRaw, fragment] = trimmed.split("#");
5247
+ let filePath = filePathRaw.trim().replace(/\\/g, "/");
5248
+ if (filePath.startsWith("./")) {
5249
+ filePath = filePath.slice(2);
5250
+ }
5251
+ if (!filePath || filePath.startsWith("/") || filePath.startsWith("../") || filePath === ".." || filePath.includes("/../") || filePath.endsWith("/..") || CONTROL_CHAR_RE.test(filePath)) {
5252
+ return null;
5253
+ }
5254
+ let startLine;
5255
+ let endLine;
5256
+ if (fragment) {
5257
+ const lineMatch = fragment.match(/^L?(\d+)(?:-L?(\d+))?$/i);
5258
+ if (lineMatch) {
5259
+ startLine = parseInt(lineMatch[1], 10);
5260
+ endLine = lineMatch[2] ? parseInt(lineMatch[2], 10) : startLine;
5261
+ }
5262
+ }
5263
+ let resolvedHref;
5264
+ if (repositoryUrl) {
5265
+ const base = repositoryUrl.replace(/\/$/, "");
5266
+ const rev = revision || "main";
5267
+ const lineFrag = startLine ? `#L${startLine}${endLine && endLine !== startLine ? `-L${endLine}` : ""}` : "";
5268
+ resolvedHref = `${base}/blob/${rev}/${filePath}${lineFrag}`;
5269
+ }
5270
+ return {
5271
+ raw: trimmed,
5272
+ filePath,
5273
+ startLine,
5274
+ endLine,
5275
+ revision,
5276
+ resolvedHref
5277
+ };
5278
+ }
5279
+ function extractDiagramCodeAnchors(ast, repositoryUrl, revision) {
5280
+ const anchors = /* @__PURE__ */ new Map();
5281
+ for (const [nodeId, node] of Object.entries(ast.nodes || {})) {
5282
+ const rawAnchor = node.props["@src"] || node.props["src"] || node.props["@source"] || node.props["source"] || node.props["@code"] || node.props["code"] || node.props["@anchor"] || node.props["anchor"];
5283
+ if (rawAnchor) {
5284
+ const parsed = parseCodeAnchor(rawAnchor, repositoryUrl, revision);
5285
+ if (parsed) {
5286
+ anchors.set(nodeId, parsed);
5287
+ }
5288
+ }
5289
+ }
5290
+ return anchors;
5291
+ }
5292
+ function verifyCodeAnchorsWithReader(anchors, fileReader) {
5293
+ const diagnostics = [];
5294
+ let verifiedCount = 0;
5295
+ for (const [nodeId, anchor] of anchors.entries()) {
5296
+ const { filePath, startLine, endLine } = anchor;
5297
+ const segments = filePath.split("/");
5298
+ if (segments.some((s) => !s || s === "." || s === "..") || segments[0] === ".git") {
5299
+ diagnostics.push({
5300
+ nodeId,
5301
+ severity: "error",
5302
+ code: "provenance/path-escape",
5303
+ message: `Code anchor for node "${nodeId}" must stay within repository and cannot address .git (${filePath}).`,
5304
+ filePath,
5305
+ fixSuggestion: "Remove relative path traversal dots or .git segments."
5306
+ });
5307
+ continue;
5308
+ }
5309
+ if (!fileReader.fileExists(filePath)) {
5310
+ diagnostics.push({
5311
+ nodeId,
5312
+ severity: "error",
5313
+ code: "provenance/file-not-found",
5314
+ message: `Referenced file "${filePath}" does not exist in target repository.`,
5315
+ filePath,
5316
+ fixSuggestion: `Ensure "${filePath}" is committed and relative to repository root.`
5317
+ });
5318
+ continue;
5319
+ }
5320
+ const lineCount = fileReader.getLineCount(filePath);
5321
+ if (startLine !== void 0 && (startLine < 1 || startLine > lineCount)) {
5322
+ diagnostics.push({
5323
+ nodeId,
5324
+ severity: "warning",
5325
+ code: "provenance/line-out-of-bounds",
5326
+ message: `Line #${startLine} exceeds total line count (${lineCount}) of file "${filePath}".`,
5327
+ filePath,
5328
+ line: startLine,
5329
+ fixSuggestion: `Adjust line range to fall within 1..${lineCount}.`
5330
+ });
5331
+ continue;
5332
+ }
5333
+ if (endLine !== void 0 && (endLine < (startLine || 1) || endLine > lineCount)) {
5334
+ diagnostics.push({
5335
+ nodeId,
5336
+ severity: "warning",
5337
+ code: "provenance/line-out-of-bounds",
5338
+ message: `End line #${endLine} is out of bounds for "${filePath}" (total lines: ${lineCount}).`,
5339
+ filePath,
5340
+ line: endLine,
5341
+ fixSuggestion: `Ensure end line is >= start line and <= ${lineCount}.`
5342
+ });
5343
+ continue;
5344
+ }
5345
+ verifiedCount++;
5346
+ }
5347
+ const isValid = diagnostics.filter((d) => d.severity === "error").length === 0;
5348
+ const summaryMarkdown = [
5349
+ `### \u{1F6E1}\uFE0F Code Provenance Verification Report`,
5350
+ `- **Status**: ${isValid ? "\u2705 VERIFIED" : "\u274C FAILED"}`,
5351
+ `- **Total Anchors**: ${anchors.size}`,
5352
+ `- **Verified In-Tree**: ${verifiedCount}`,
5353
+ `- **Diagnostics**: ${diagnostics.length} issue(s)`,
5354
+ ...diagnostics.length > 0 ? [
5355
+ "",
5356
+ `| Node | Severity | Issue | File / Location | Fix |`,
5357
+ `| :--- | :--- | :--- | :--- | :--- |`,
5358
+ ...diagnostics.map(
5359
+ (d) => `| \`${d.nodeId}\` | **${d.severity.toUpperCase()}** | ${d.message} | \`${d.filePath}${d.line ? `:${d.line}` : ""}\` | ${d.fixSuggestion || "N/A"} |`
5360
+ )
5361
+ ] : []
5362
+ ].join("\n");
5363
+ return {
5364
+ isValid,
5365
+ totalAnchors: anchors.size,
5366
+ verifiedCount,
5367
+ anchors,
5368
+ diagnostics,
5369
+ summaryMarkdown
5370
+ };
5371
+ }
4365
5372
 
4366
5373
  // src/syntax-diagnostics.ts
4367
5374
  function damerauLevenshteinDistance(a, b) {
@@ -4621,7 +5628,7 @@ function diagnoseMarkdyCode(code, options = {}) {
4621
5628
  snippet: trimmed,
4622
5629
  suggestion: closeTheme ? `Use theme '${closeTheme.match}'.` : `Choose from: ${THEME_NAMES.join(", ")}.`,
4623
5630
  didYouMean: closeTheme?.match,
4624
- ruleExplanation: "Supported themes: paper, editorial, midnight, blueprint, graphite, nebula, sketchy, terminal.",
5631
+ ruleExplanation: "Supported themes: paper, editorial, midnight, blueprint, graphite, nebula, sketchy, terminal, ink, doodle.",
4625
5632
  fix: closeTheme ? { original: themeMatch[0], replacement: `theme=${closeTheme.match}` } : void 0
4626
5633
  });
4627
5634
  }
@@ -5517,7 +6524,9 @@ function getIntelliCodeCompletions(docText, cursorLine, cursorCol) {
5517
6524
  { name: "editorial", desc: "High-contrast serif luxury publication layout" },
5518
6525
  { name: "graphite", desc: "Sleek slate monochrome engineering aesthetic" },
5519
6526
  { name: "terminal", desc: "Retro phosphor CRT hacker terminal green on black" },
5520
- { name: "sketchy", desc: "Hand-drawn sketchy whiteboard marker design" }
6527
+ { name: "sketchy", desc: "Hand-drawn sketchy whiteboard marker design" },
6528
+ { name: "ink", desc: "Monochromatic blue ink style inspired by ballpoint pens, fountain pens, cyanotypes & porcelain" },
6529
+ { name: "doodle", desc: "Playful hand-drawn doodle sketchbook with felt-tip marker pens and comic block shadows" }
5521
6530
  ];
5522
6531
  for (const t of themeEntries) {
5523
6532
  items.push({
@@ -6073,7 +7082,1593 @@ function formatValue(value) {
6073
7082
  if (typeof value === "string") return value;
6074
7083
  return JSON.stringify(value);
6075
7084
  }
7085
+
7086
+ // src/recipes.ts
7087
+ var ARCHITECTURE_RECIPES = [
7088
+ {
7089
+ id: "cache-aside",
7090
+ name: "Multi-Tier Cache-Aside Architecture",
7091
+ category: "caching",
7092
+ description: "High-performance cache-aside pattern with Redis Cluster, relational database persistence, and asynchronous cache warming.",
7093
+ keywords: ["cache", "redis", "postgres", "cache-aside", "hit", "miss", "warm", "database", "latency"],
7094
+ recommendedLayout: "LR",
7095
+ primaryNodes: ["Client", "Gateway", "URLService", "RedisCluster", "PostgreSQL"],
7096
+ highlights: [
7097
+ "Sub-millisecond read latency on cache hit",
7098
+ "Asynchronous cache population on miss",
7099
+ "Graceful fallback on cache eviction"
7100
+ ],
7101
+ code: `scene "Multi-Tier Cache-Aside Architecture" theme=auto
7102
+ layout LR
7103
+
7104
+ browser Client "Web Client" icon=chrome
7105
+ gateway Gateway "API Gateway" icon=nginx @src="src/gateway/proxy.ts#L12"
7106
+ service URLService "URL Service" icon=nodejs @src="src/services/resolver.ts#L25"
7107
+ cache RedisCluster "Redis Cluster" icon=redis
7108
+ database PostgreSQL "PostgreSQL 16" icon=postgresql @src="src/db/schema.sql#L1"
7109
+
7110
+ beat cache_hit "1. Cache Hit Path":
7111
+ show Client Gateway URLService RedisCluster stagger=50ms
7112
+ frame Client Gateway URLService RedisCluster zoom=1.12
7113
+ Client -> Gateway "GET /link" -> URLService "resolve"
7114
+ URLService -> RedisCluster "GET key:url"
7115
+ URLService <- RedisCluster "200 Target URL"
7116
+ Client <- Gateway "301 Redirect"
7117
+
7118
+ beat cache_miss "2. Cache Miss & Async Warm":
7119
+ show PostgreSQL stagger=50ms
7120
+ frame URLService RedisCluster PostgreSQL zoom=1.15
7121
+ URLService -> PostgreSQL "SELECT dest WHERE key = 'url'"
7122
+ URLService <- PostgreSQL "Row Found"
7123
+ URLService ~> RedisCluster "SETEX key:url (TTL 1h)"
7124
+ glow PostgreSQL color=#38bdf8 & glow RedisCluster color=#22c55e
7125
+ `
7126
+ },
7127
+ {
7128
+ id: "event-driven-eda",
7129
+ name: "Event-Driven EDA with Kafka & Change Data Capture",
7130
+ category: "streaming",
7131
+ description: "Decoupled real-time event streaming pipeline using Kafka/Redpanda with Debezium CDC, Schema Registry, and DLQ handling.",
7132
+ keywords: ["kafka", "event", "streaming", "eda", "cdc", "debezium", "pubsub", "dlq", "consumer"],
7133
+ recommendedLayout: "LR",
7134
+ primaryNodes: ["OrderService", "DebeziumCDC", "KafkaCluster", "NotificationSvc", "AnalyticsConsumer", "DeadLetterQueue"],
7135
+ highlights: [
7136
+ "Zero dual-write penalty with transactional outbox",
7137
+ "Ordered partition publishing with schema validation",
7138
+ "Dead Letter Queue for poison-pill isolation"
7139
+ ],
7140
+ code: `scene "Event-Driven EDA with Kafka & CDC" theme=midnight
7141
+ layout LR
7142
+
7143
+ service OrderSvc "Order Service" icon=golang @src="src/orders/handler.go#L40"
7144
+ database OrderDB "Order Store" icon=postgresql
7145
+ service DebeziumCDC "Debezium CDC" icon=docker
7146
+ queue KafkaCluster "Kafka Event Stream" icon=kafka
7147
+ service NotificationSvc "Notification Engine" icon=nodejs
7148
+ service AnalyticsConsumer "Real-Time Analytics" icon=python
7149
+ queue DLQ "Dead Letter Queue" icon=rabbitmq
7150
+
7151
+ beat order_outbox "1. Transactional Outbox Commit":
7152
+ show OrderSvc OrderDB DebeziumCDC KafkaCluster stagger=50ms
7153
+ OrderSvc -> OrderDB "COMMIT (Order + Outbox Event)"
7154
+ OrderDB -> DebeziumCDC "WAL Stream"
7155
+ DebeziumCDC ~> KafkaCluster "Publish order.created"
7156
+
7157
+ beat consumer_fanout "2. Real-Time Consumer Fanout":
7158
+ show NotificationSvc AnalyticsConsumer DLQ stagger=50ms
7159
+ KafkaCluster ~> NotificationSvc "Consume order.created"
7160
+ KafkaCluster ~> AnalyticsConsumer "Consume order.created"
7161
+ NotificationSvc -> DLQ "Routing Reject (Retries Exceeded)"
7162
+ glow KafkaCluster color=#fbbf24 & glow DLQ color=#fb7185
7163
+ `
7164
+ },
7165
+ {
7166
+ id: "cqrs-event-sourcing",
7167
+ name: "CQRS & Event Sourcing Architecture",
7168
+ category: "streaming",
7169
+ description: "Strict Command Query Responsibility Segregation with immutable append-only Event Store and read-optimized query projections.",
7170
+ keywords: ["cqrs", "event sourcing", "event store", "projection", "read model", "command", "query"],
7171
+ recommendedLayout: "LR",
7172
+ primaryNodes: ["CommandAPI", "CommandHandler", "EventStore", "ProjectionEngine", "ReadDB", "QueryAPI"],
7173
+ highlights: [
7174
+ "Complete auditability via append-only event stream",
7175
+ "Independent write-scaling and read-scaling",
7176
+ "Zero lock contention between queries and mutations"
7177
+ ],
7178
+ code: `scene "CQRS & Event Sourcing Architecture" theme=blueprint
7179
+ layout LR
7180
+
7181
+ gateway CommandAPI "Command API" icon=nginx
7182
+ service CommandHandler "Command Handler" icon=golang @src="src/commands/execute.go#L18"
7183
+ database EventStore "Event Store (Append-Only)" icon=cassandra
7184
+ service ProjectionEngine "Projection Engine" icon=rust @src="src/projections/sync.rs#L30"
7185
+ database ReadDB "Read Database" icon=mongodb
7186
+ gateway QueryAPI "Query API" icon=nodejs
7187
+
7188
+ beat write_command "1. Command & Append Event":
7189
+ show CommandAPI CommandHandler EventStore stagger=50ms
7190
+ CommandAPI -> CommandHandler "POST /orders/create"
7191
+ CommandHandler -> EventStore "APPEND OrderCreatedEvent"
7192
+ CommandHandler <- EventStore "Event Ack (Offset: 10482)"
7193
+ CommandAPI <- CommandHandler "202 Accepted"
7194
+
7195
+ beat async_projection "2. Asynchronous Projection Update":
7196
+ show ProjectionEngine ReadDB QueryAPI stagger=50ms
7197
+ EventStore ~> ProjectionEngine "Tail Commit Log"
7198
+ ProjectionEngine -> ReadDB "UPSERT Materialized Order View"
7199
+ QueryAPI -> ReadDB "SELECT * FROM orders WHERE id = :id"
7200
+ QueryAPI <- ReadDB "Read Model Payload"
7201
+ `
7202
+ },
7203
+ {
7204
+ id: "api-gateway-mesh",
7205
+ name: "Cloud-Native API Gateway & Service Mesh",
7206
+ category: "microservices",
7207
+ description: "Enterprise zero-trust microservices architecture with Envoy/Istio service mesh, mTLS enforcement, and distributed tracing.",
7208
+ keywords: ["gateway", "mesh", "envoy", "istio", "microservices", "mtls", "kubernetes", "discovery"],
7209
+ recommendedLayout: "LR",
7210
+ primaryNodes: ["IngressGateway", "AuthService", "OrderMeshSvc", "PaymentMeshSvc", "InventoryMeshSvc", "JaegerCollector"],
7211
+ highlights: [
7212
+ "Strict mTLS identity verification between sidecars",
7213
+ "Global rate limiting and distributed OpenTelemetry spans",
7214
+ "Dynamic circuit breaking and automated retries"
7215
+ ],
7216
+ code: `scene "Cloud-Native API Gateway & Service Mesh" theme=graphite
7217
+ layout LR
7218
+
7219
+ gateway IngressGateway "Envoy Ingress Gateway" icon=envoy @src="k8s/gateway.yaml#L1"
7220
+ service AuthService "Auth & Token Service" icon=nodejs @src="src/auth/token.ts#L44"
7221
+ service OrderMeshSvc "Order Service (mTLS)" icon=golang
7222
+ service PaymentMeshSvc "Payment Gateway (mTLS)" icon=nodejs
7223
+ service InventoryMeshSvc "Inventory Service (mTLS)" icon=python
7224
+ service JaegerCollector "OpenTelemetry Collector" icon=jaeger
7225
+
7226
+ beat ingress_auth "1. Edge Authentication & Route Verification":
7227
+ show IngressGateway AuthService JaegerCollector stagger=50ms
7228
+ IngressGateway -> AuthService "Validate JWT Bearer"
7229
+ IngressGateway <- AuthService "Claims Verified"
7230
+ IngressGateway ~> JaegerCollector "Span: ingress_entry"
7231
+
7232
+ beat internal_mesh_flow "2. Internal mTLS Mesh Fanout":
7233
+ show OrderMeshSvc PaymentMeshSvc InventoryMeshSvc stagger=50ms
7234
+ IngressGateway -> OrderMeshSvc "POST /checkout (mTLS)"
7235
+ OrderMeshSvc -> PaymentMeshSvc "POST /charge (mTLS)"
7236
+ OrderMeshSvc -> InventoryMeshSvc "POST /reserve (mTLS)"
7237
+ PaymentMeshSvc ~> JaegerCollector "Span: payment_settled"
7238
+ InventoryMeshSvc ~> JaegerCollector "Span: inventory_reserved"
7239
+ glow IngressGateway color=#38bdf8 & glow OrderMeshSvc color=#22c55e
7240
+ `
7241
+ },
7242
+ {
7243
+ id: "zero-trust-security",
7244
+ name: "Zero-Trust Security & Enclave Perimeter",
7245
+ category: "security",
7246
+ description: "Defense-in-depth zero-trust security perimeter featuring OIDC authentication, Open Policy Agent authorization, and Nitro Enclaves.",
7247
+ keywords: ["security", "zero-trust", "oidc", "opa", "policy", "enclave", "encryption", "vault", "waf"],
7248
+ recommendedLayout: "LR",
7249
+ primaryNodes: ["CloudflareWAF", "IdentityOIDC", "PolicyEngineOPA", "KeyVault", "SecureEnclave", "AuditLogStore"],
7250
+ highlights: [
7251
+ "Continuous runtime identity verification on every invocation",
7252
+ "Confidential computing in isolated CPU Nitro enclaves",
7253
+ "Immutable write-once cryptographic audit trail"
7254
+ ],
7255
+ code: `scene "Zero-Trust Security & Enclave Perimeter" theme=midnight
7256
+ layout LR
7257
+
7258
+ gateway CloudflareWAF "Cloudflare Edge WAF" icon=cloudflare
7259
+ service IdentityOIDC "Identity Provider (OIDC)" icon=keycloak
7260
+ service PolicyEngineOPA "Policy Engine (OPA)" icon=opa @src="policies/authz.rego#L1"
7261
+ service KeyVault "HashiCorp Vault" icon=vault @src="config/vault.hcl#L10"
7262
+ service SecureEnclave "AWS Nitro Enclave" icon=aws
7263
+ database AuditLogStore "WORM Audit Store" icon=s3
7264
+
7265
+ beat access_request "1. Identity & Policy Evaluation":
7266
+ show CloudflareWAF IdentityOIDC PolicyEngineOPA stagger=50ms
7267
+ CloudflareWAF -> IdentityOIDC "Authenticate Request"
7268
+ CloudflareWAF <- IdentityOIDC "Token Issued"
7269
+ CloudflareWAF -> PolicyEngineOPA "Evaluate RBAC/ABAC Context"
7270
+ CloudflareWAF <- PolicyEngineOPA "Decision: ALLOW"
7271
+
7272
+ beat confidential_execution "2. Enclave Decryption & Audit":
7273
+ show SecureEnclave KeyVault AuditLogStore stagger=50ms
7274
+ CloudflareWAF -> SecureEnclave "Execute Protected Payload"
7275
+ SecureEnclave -> KeyVault "Request Ephemeral Decryption Key"
7276
+ SecureEnclave <- KeyVault "Key Granted"
7277
+ SecureEnclave ~> AuditLogStore "Cryptographic Audit Receipt"
7278
+ glow SecureEnclave color=#fb7185 & glow KeyVault color=#a78bfa
7279
+ `
7280
+ },
7281
+ {
7282
+ id: "medallion-lakehouse",
7283
+ name: "Medallion Data Lakehouse Architecture",
7284
+ category: "data",
7285
+ description: "Modern data engineering pipeline organizing raw, cleansed, and curated data across Bronze, Silver, and Gold tiers.",
7286
+ keywords: ["data", "lakehouse", "medallion", "bronze", "silver", "gold", "spark", "delta", "iceberg", "analytics"],
7287
+ recommendedLayout: "LR",
7288
+ primaryNodes: ["RawIngestKafka", "BronzeLake", "SparkCleansing", "SilverLake", "FlinkAggregation", "GoldWarehouse", "SupersetBI"],
7289
+ highlights: [
7290
+ "ACID transactions over object storage with Delta/Iceberg",
7291
+ "Multi-stage data quality checks between Bronze and Silver",
7292
+ "Sub-second dimensional queries on Gold warehouse"
7293
+ ],
7294
+ code: `scene "Medallion Data Lakehouse Architecture" theme=editorial
7295
+ layout LR
7296
+
7297
+ queue RawIngestKafka "Raw Event Ingestion" icon=kafka
7298
+ database BronzeLake "Bronze Lake (Raw Ingest)" icon=s3
7299
+ service SparkCleansing "Spark Cleansing Job" icon=spark @src="jobs/cleanse_bronze.py#L15"
7300
+ database SilverLake "Silver Lake (Enriched)" icon=delta
7301
+ service FlinkAggregation "Flink Streaming Aggregator" icon=flink
7302
+ database GoldWarehouse "Gold Warehouse (Curated)" icon=snowflake
7303
+ dashboard SupersetBI "Apache Superset BI" icon=superset
7304
+
7305
+ beat bronze_ingest "1. Raw Stream to Bronze Tier":
7306
+ show RawIngestKafka BronzeLake SparkCleansing stagger=50ms
7307
+ RawIngestKafka -> BronzeLake "Append Raw JSON Payload"
7308
+ BronzeLake -> SparkCleansing "Trigger Micro-Batch"
7309
+
7310
+ beat silver_and_gold "2. Cleansing, Enrichment & BI Serving":
7311
+ show SilverLake FlinkAggregation GoldWarehouse SupersetBI stagger=50ms
7312
+ SparkCleansing -> SilverLake "Upsert Deduplicated Parquet"
7313
+ SilverLake -> FlinkAggregation "Stream Entity Updates"
7314
+ FlinkAggregation -> GoldWarehouse "Merge Into Star Schema"
7315
+ SupersetBI -> GoldWarehouse "Execute Dimensional Query"
7316
+ SupersetBI <- GoldWarehouse "Render Dashboard Metrics"
7317
+ `
7318
+ },
7319
+ {
7320
+ id: "agentic-react-tools",
7321
+ name: "Agentic AI Orchestrator & Tool Execution Loop",
7322
+ category: "ai",
7323
+ description: "Autonomous ReAct agent system with LLM Orchestrator, dynamic context memory, vector embeddings, and MCP tool execution.",
7324
+ keywords: ["ai", "agent", "llm", "react", "tool", "mcp", "vector", "rag", "orchestrator", "prompt"],
7325
+ recommendedLayout: "LR",
7326
+ primaryNodes: ["UserClient", "AgentOrchestrator", "VectorMemory", "ModelInference", "MCPToolExecutor", "SandboxRuntime"],
7327
+ highlights: [
7328
+ "Interactive reasoning loop (Thought -> Action -> Observation)",
7329
+ "Hybrid semantic search over vector memory store",
7330
+ "Secure sandboxed runtime for tool call executions"
7331
+ ],
7332
+ code: `scene "Agentic AI Orchestrator & Tool Loop" theme=nebula
7333
+ layout LR
7334
+
7335
+ browser UserClient "User Workspace" icon=terminal
7336
+ service AgentOrchestrator "ReAct Agent Orchestrator" icon=python @src="agent/core.py#L35"
7337
+ database VectorMemory "Vector Memory (RAG)" icon=redis
7338
+ service ModelInference "LLM Inference API" icon=gemini
7339
+ service MCPToolExecutor "MCP Tool Protocol" icon=docker @src="agent/mcp_client.py#L20"
7340
+ service SandboxRuntime "Secure Container Sandbox" icon=docker
7341
+
7342
+ beat agent_thought "1. Plan & Context Retrieval":
7343
+ show UserClient AgentOrchestrator VectorMemory ModelInference stagger=50ms
7344
+ UserClient -> AgentOrchestrator "Goal: Deploy microservice"
7345
+ AgentOrchestrator -> VectorMemory "Query Relevant Runbooks"
7346
+ AgentOrchestrator <- VectorMemory "Runbook Context Vectors"
7347
+ AgentOrchestrator -> ModelInference "Generate Plan & Tool Call"
7348
+ AgentOrchestrator <- ModelInference "Call: run_command(kubectl apply)"
7349
+
7350
+ beat tool_execution "2. MCP Tool Execution & Observation":
7351
+ show MCPToolExecutor SandboxRuntime stagger=50ms
7352
+ AgentOrchestrator -> MCPToolExecutor "Execute Tool Request"
7353
+ MCPToolExecutor -> SandboxRuntime "Spawn Container & Execute"
7354
+ MCPToolExecutor <- SandboxRuntime "Output: deployment created"
7355
+ AgentOrchestrator <- MCPToolExecutor "Observation Receipt"
7356
+ UserClient <- AgentOrchestrator "Goal Achieved: Deployed successfully"
7357
+ glow AgentOrchestrator color=#c4b5fd & glow MCPToolExecutor color=#67e8f9
7358
+ `
7359
+ },
7360
+ {
7361
+ id: "active-active-failover",
7362
+ name: "Multi-Region Active-Active Resilient Failover",
7363
+ category: "resilience",
7364
+ description: "High-availability global architecture with GeoDNS latency routing, multi-region cluster active-active syncing, and automated failover.",
7365
+ keywords: ["resilience", "active-active", "failover", "multi-region", "disaster recovery", "replication", "dns", "ha"],
7366
+ recommendedLayout: "LR",
7367
+ primaryNodes: ["GlobalDNS", "RegionUSEast", "DBPrimaryEast", "RegionEUWest", "DBPrimaryWest", "HealthProbe"],
7368
+ highlights: [
7369
+ "Sub-second DNS failover when health probe detects outage",
7370
+ "Bi-directional conflict-free replicated database sync (CRDT)",
7371
+ "Zero downtime during planned regional maintenance"
7372
+ ],
7373
+ code: `scene "Multi-Region Active-Active Failover" theme=midnight
7374
+ layout LR
7375
+
7376
+ gateway GlobalDNS "Global Route53 GeoDNS" icon=aws
7377
+ service RegionUSEast "Region US-East API" icon=kubernetes @src="infra/us-east/app.yaml#L1"
7378
+ database DBPrimaryEast "Aurora Global DB (East)" icon=postgresql
7379
+ service RegionEUWest "Region EU-West API" icon=kubernetes @src="infra/eu-west/app.yaml#L1"
7380
+ database DBPrimaryWest "Aurora Global DB (West)" icon=postgresql
7381
+ service HealthProbe "Global Health Checker" icon=datadog
7382
+
7383
+ beat steady_state "1. Steady-State Geo-Routing & Sync":
7384
+ show GlobalDNS RegionUSEast DBPrimaryEast RegionEUWest DBPrimaryWest stagger=50ms
7385
+ GlobalDNS -> RegionUSEast "Route US Traffic"
7386
+ RegionUSEast -> DBPrimaryEast "Local Read/Write"
7387
+ GlobalDNS -> RegionEUWest "Route EU Traffic"
7388
+ RegionEUWest -> DBPrimaryWest "Local Read/Write"
7389
+ DBPrimaryEast ~> DBPrimaryWest "Cross-Region Stream Replication"
7390
+
7391
+ beat simulated_failover "2. Outage Detection & Instant Failover":
7392
+ show HealthProbe stagger=50ms
7393
+ HealthProbe -> RegionUSEast "HTTP Health Probe (Timeout)"
7394
+ HealthProbe ~> GlobalDNS "Withdraw US-East IP from Pool"
7395
+ GlobalDNS -> RegionEUWest "Reroute 100% Global Traffic"
7396
+ glow RegionEUWest color=#22c55e & glow RegionUSEast color=#fb7185
7397
+ `
7398
+ },
7399
+ {
7400
+ id: "distributed-consensus-raft",
7401
+ name: "Distributed Consensus & Raft Log Replication",
7402
+ category: "consensus",
7403
+ description: "Raft consensus protocol state machine with Leader Election, Heartbeat synchronization, and atomic log commitment.",
7404
+ keywords: ["raft", "consensus", "leader", "follower", "election", "replication", "distributed", "etcd"],
7405
+ recommendedLayout: "LR",
7406
+ primaryNodes: ["ClientApp", "RaftLeader", "RaftFollowerA", "RaftFollowerB", "StateStore"],
7407
+ highlights: [
7408
+ "Guaranteed linearizable reads and writes",
7409
+ "Automated leader reelection on heartbeat loss",
7410
+ "Strict quorum (N/2 + 1) commit guarantees"
7411
+ ],
7412
+ code: `scene "Distributed Consensus Raft Engine" theme=paper
7413
+ layout LR
7414
+
7415
+ browser ClientApp "Client Application" icon=terminal
7416
+ service RaftLeader "Raft Node 1 (Leader)" icon=golang @src="raft/leader.go#L42"
7417
+ service RaftFollowerA "Raft Node 2 (Follower)" icon=golang @src="raft/follower.go#L20"
7418
+ service RaftFollowerB "Raft Node 3 (Follower)" icon=golang @src="raft/follower.go#L20"
7419
+ database StateStore "Committed State Machine" icon=sqlite
7420
+
7421
+ beat propose_entry "1. Client Proposal & Log Replication":
7422
+ show ClientApp RaftLeader RaftFollowerA RaftFollowerB stagger=50ms
7423
+ ClientApp -> RaftLeader "Propose: SET key = 'val'"
7424
+ RaftLeader -> RaftFollowerA "AppendEntries(Term=2, Entry=4)"
7425
+ RaftLeader -> RaftFollowerB "AppendEntries(Term=2, Entry=4)"
7426
+
7427
+ beat quorum_commit "2. Quorum Acknowledgment & State Commit":
7428
+ show StateStore stagger=50ms
7429
+ RaftLeader <- RaftFollowerA "Success Ack"
7430
+ RaftLeader <- RaftFollowerB "Success Ack"
7431
+ RaftLeader -> StateStore "Apply to State Machine"
7432
+ ClientApp <- RaftLeader "200 Commit Acknowledged"
7433
+ glow RaftLeader color=#0284c7 & glow StateStore color=#16a34a
7434
+ `
7435
+ },
7436
+ {
7437
+ id: "incident-runbook",
7438
+ name: "Automated Incident Response & Self-Healing Runbook",
7439
+ category: "observability",
7440
+ description: "Automated site reliability incident workflow: metric threshold breach, PagerDuty alert, automated pod restart, and status page sync.",
7441
+ keywords: ["incident", "sre", "runbook", "pagerduty", "alert", "prometheus", "slack", "self-healing"],
7442
+ recommendedLayout: "LR",
7443
+ primaryNodes: ["PrometheusAlert", "PagerDutyEngine", "K8sAutoHealer", "SlackIncidentBot", "StatusPageSync"],
7444
+ highlights: [
7445
+ "Instant multi-channel incident triaging",
7446
+ "Automated remediation before human on-call escalation",
7447
+ "Zero-latency public status communication"
7448
+ ],
7449
+ code: `scene "Automated Incident Response Runbook" theme=midnight
7450
+ layout LR
7451
+
7452
+ service PrometheusAlert "Prometheus Alertmanager" icon=prometheus @src="alerts/p99_latency.yaml#L1"
7453
+ service PagerDutyEngine "PagerDuty Event Router" icon=pagerduty
7454
+ service K8sAutoHealer "K8s Auto-Remediation" icon=kubernetes @src="runbooks/restart_pod.sh#L5"
7455
+ service SlackIncidentBot "Slack War Room Bot" icon=slack
7456
+ service StatusPageSync "Public Status Page" icon=cloudflare
7457
+
7458
+ beat alert_trigger "1. High Latency P99 Breach":
7459
+ show PrometheusAlert PagerDutyEngine SlackIncidentBot StatusPageSync stagger=50ms
7460
+ PrometheusAlert ~> PagerDutyEngine "TRIGGER: P99 Latency > 1500ms"
7461
+ PagerDutyEngine -> SlackIncidentBot "Spawn #incident-2026-09"
7462
+ PagerDutyEngine -> StatusPageSync "Update: Degraded Performance"
7463
+
7464
+ beat auto_heal "2. Self-Healing Pod Recycle & Resolution":
7465
+ show K8sAutoHealer stagger=50ms
7466
+ PagerDutyEngine -> K8sAutoHealer "Execute Remediation Runbook"
7467
+ K8sAutoHealer -> PrometheusAlert "Verify Latency Normalized (< 200ms)"
7468
+ PagerDutyEngine ~> SlackIncidentBot "Resolved: Auto-healed in 42s"
7469
+ PagerDutyEngine ~> StatusPageSync "Update: All Systems Operational"
7470
+ glow K8sAutoHealer color=#22c55e & glow StatusPageSync color=#38bdf8
7471
+ `
7472
+ },
7473
+ {
7474
+ id: "agentic-multi-swarm",
7475
+ name: "Autonomous Multi-Agent Engineering Swarm",
7476
+ category: "ai",
7477
+ description: "Multi-agent collaborative architecture with Orchestrator Leader, Specialized Coder/Reviewer Subagents, Sandboxed Tool Execution, and Consensus Verification.",
7478
+ keywords: ["agent", "swarm", "multi-agent", "orchestrator", "mcp", "subagent", "sandbox", "ai"],
7479
+ recommendedLayout: "LR",
7480
+ primaryNodes: ["UserLead", "OrchestratorAgent", "CoderSubagent", "ReviewerSubagent", "SandboxRuntime"],
7481
+ highlights: [
7482
+ "Dynamic hierarchical task delegation",
7483
+ "Dual-agent verification & adversarial review",
7484
+ "Isolated sandboxed execution with telemetry"
7485
+ ],
7486
+ code: `scene "Autonomous Multi-Agent Engineering Swarm" theme=graphite
7487
+ layout LR
7488
+
7489
+ browser UserLead "Lead Engineer / IDE" icon=gemini
7490
+ service OrchestratorAgent "Orchestrator Leader" icon=nodejs @src="src/agent/leader.ts#L10"
7491
+ service CoderSubagent "Coder Subagent" icon=typescript @src="src/agent/coder.ts#L15"
7492
+ service ReviewerSubagent "Reviewer Subagent" icon=python @src="src/agent/critic.ts#L20"
7493
+ service SandboxRuntime "Secure Tool Sandbox" icon=docker @src="src/tools/mcp_host.ts#L5"
7494
+
7495
+ beat task_delegation "1. Task Decomposition & Parallel Spawn":
7496
+ show UserLead OrchestratorAgent CoderSubagent ReviewerSubagent stagger=50ms
7497
+ UserLead -> OrchestratorAgent "Prompt: Implement Feature & Tests"
7498
+ OrchestratorAgent -> CoderSubagent "Spawn task: Write TypeScript Implementation"
7499
+ OrchestratorAgent -> ReviewerSubagent "Spawn task: Construct Invariant Quality Gate"
7500
+
7501
+ beat tool_verification "2. Sandboxed Execution & Review Consensus":
7502
+ show SandboxRuntime stagger=50ms
7503
+ CoderSubagent -> SandboxRuntime "Execute unit tests in sandbox"
7504
+ CoderSubagent <- SandboxRuntime "308 tests pass (100%)"
7505
+ ReviewerSubagent -> CoderSubagent "Verify Code Provenance & Zero Regressions"
7506
+ OrchestratorAgent <- ReviewerSubagent "Consensus Approved: Ready for PR"
7507
+ UserLead <- OrchestratorAgent "200 Feature Complete & Verified"
7508
+ glow OrchestratorAgent color=#38bdf8 & glow SandboxRuntime color=#10b981
7509
+ `
7510
+ },
7511
+ {
7512
+ id: "edge-serverless-mesh",
7513
+ name: "Edge-First Serverless & Distributed Vector Mesh",
7514
+ category: "resilience",
7515
+ description: "Ultra-low-latency globally distributed edge architecture with Cloudflare Workers, KV caching, D1 relational store, and Vectorize embedding search.",
7516
+ keywords: ["edge", "cloudflare", "workers", "serverless", "d1", "vector", "embedding", "kv"],
7517
+ recommendedLayout: "LR",
7518
+ primaryNodes: ["GlobalClient", "EdgeWorker", "EdgeKV", "D1Database", "VectorizeStore"],
7519
+ highlights: [
7520
+ "Sub-10ms global edge invocation",
7521
+ "Local relational replication with D1",
7522
+ "Native vector similarity lookup at the edge"
7523
+ ],
7524
+ code: `scene "Edge-First Serverless & Vector Mesh" theme=paper
7525
+ layout LR
7526
+
7527
+ browser GlobalClient "Global Mobile/Web Client" icon=chrome
7528
+ gateway EdgeWorker "Cloudflare Edge Worker" icon=cloudflare @src="src/worker/index.ts#L1"
7529
+ cache EdgeKV "Global KV Cache" icon=redis
7530
+ database D1Database "Cloudflare D1 SQL" icon=postgresql @src="src/db/schema.sql#L10"
7531
+ database VectorizeStore "Vectorize Embedding DB" icon=gemini
7532
+
7533
+ beat edge_lookup "1. Nearest Edge Routing & Cache Hit":
7534
+ show GlobalClient EdgeWorker EdgeKV stagger=50ms
7535
+ GlobalClient -> EdgeWorker "GET /recommendations (Geo: Tokyo)"
7536
+ EdgeWorker -> EdgeKV "GET edge_cache:user_tokyo"
7537
+ EdgeWorker <- EdgeKV "Hit (3ms latency)"
7538
+
7539
+ beat semantic_search "2. Edge Vector Search & D1 Fetch":
7540
+ show VectorizeStore D1Database stagger=50ms
7541
+ EdgeWorker -> VectorizeStore "Query vector topK=5"
7542
+ EdgeWorker <- VectorizeStore "Embedding Matches"
7543
+ EdgeWorker -> D1Database "SELECT metadata FROM products WHERE id IN (...)"
7544
+ EdgeWorker <- D1Database "Product Records"
7545
+ GlobalClient <- EdgeWorker "200 OK (8ms total transit)"
7546
+ glow EdgeWorker color=#f59e0b & glow VectorizeStore color=#ec4899
7547
+ `
7548
+ },
7549
+ {
7550
+ id: "zero-downtime-canary",
7551
+ name: "Zero-Downtime Blue-Green & Canary Deployment",
7552
+ category: "resilience",
7553
+ description: "Progressive delivery traffic routing with Envoy/Ingress, Blue (Stable) vs Green (Canary) cluster weighting, and automated rollback on error spikes.",
7554
+ keywords: ["canary", "blue-green", "deployment", "envoy", "kubernetes", "traffic", "rollback", "zero-downtime"],
7555
+ recommendedLayout: "LR",
7556
+ primaryNodes: ["IngressController", "EnvoyMesh", "BlueCluster", "GreenCanary", "PrometheusWatcher"],
7557
+ highlights: [
7558
+ "Fine-grained 90/10 traffic split",
7559
+ "Zero dropped active connections during migration",
7560
+ "Sub-second automated circuit breaker rollback"
7561
+ ],
7562
+ code: `scene "Zero-Downtime Canary Deployment" theme=terminal
7563
+ layout LR
7564
+
7565
+ browser UserTraffic "Live Production Traffic" icon=chrome
7566
+ gateway EnvoyMesh "Envoy Service Mesh" icon=envoy @src="k8s/envoy-config.yaml#L1"
7567
+ service BlueCluster "Blue Pods (v1.2.0 Stable 90%)" icon=kubernetes @src="deploy/blue.yaml#L1"
7568
+ service GreenCanary "Green Pods (v1.3.0 Canary 10%)" icon=docker @src="deploy/green.yaml#L1"
7569
+ service PrometheusWatcher "Canary Health Sentry" icon=prometheus
7570
+
7571
+ beat canary_routing "1. Weighted Traffic Split (90/10)":
7572
+ show UserTraffic EnvoyMesh BlueCluster GreenCanary stagger=50ms
7573
+ UserTraffic -> EnvoyMesh "Production Request Pool"
7574
+ EnvoyMesh -> BlueCluster "Route 90% Stable"
7575
+ EnvoyMesh -> GreenCanary "Route 10% Canary"
7576
+
7577
+ beat health_verification "2. Automated Sentry Gate & 100% Promotion":
7578
+ show PrometheusWatcher stagger=50ms
7579
+ PrometheusWatcher -> GreenCanary "Monitor Error Rate (< 0.01%) & P99"
7580
+ PrometheusWatcher -> EnvoyMesh "Signal: Canary Healthy -> Shift 100% to Green"
7581
+ EnvoyMesh -> GreenCanary "Promote to 100% Live"
7582
+ glow GreenCanary color=#22c55e & glow BlueCluster color=#64748b
7583
+ `
7584
+ },
7585
+ {
7586
+ id: "opentelemetry-tracing",
7587
+ name: "Full-Stack Distributed Tracing & Observability",
7588
+ category: "observability",
7589
+ description: "End-to-end W3C trace context propagation across Frontend, API Gateway, Microservices, and OpenTelemetry Collector with Jaeger/Grafana visualization.",
7590
+ keywords: ["opentelemetry", "tracing", "jaeger", "grafana", "prometheus", "span", "context", "observability"],
7591
+ recommendedLayout: "LR",
7592
+ primaryNodes: ["WebFrontend", "ApiGateway", "OrderService", "OtelCollector", "JaegerGrafana"],
7593
+ highlights: [
7594
+ "Unified W3C traceparent context injection",
7595
+ "Non-blocking asynchronous telemetry batching",
7596
+ "Unified metrics, logs, and trace correlation"
7597
+ ],
7598
+ code: `scene "Full-Stack Distributed Tracing" theme=editorial
7599
+ layout LR
7600
+
7601
+ browser WebFrontend "Web App (OTel Web SDK)" icon=chrome @src="src/tracing/web.ts#L5"
7602
+ gateway ApiGateway "Kong API Gateway" icon=nginx
7603
+ service OrderService "Order Microservice" icon=golang @src="src/orders/main.go#L30"
7604
+ service OtelCollector "OpenTelemetry Collector" icon=docker @src="otel/collector.yaml#L1"
7605
+ database JaegerGrafana "Jaeger & Grafana Cloud" icon=datadog
7606
+
7607
+ beat trace_propagation "1. Context Injection & Downstream Propagation":
7608
+ show WebFrontend ApiGateway OrderService OtelCollector stagger=50ms
7609
+ WebFrontend -> ApiGateway "POST /checkout [traceparent: 00-4bf92...]"
7610
+ ApiGateway -> OrderService "Forward [traceparent: 00-4bf92...]"
7611
+ WebFrontend ~> OtelCollector "Async Span: browser_render (42ms)"
7612
+
7613
+ beat collector_export "2. OTLP gRPC Batch Ingestion & Indexing":
7614
+ show JaegerGrafana stagger=50ms
7615
+ ApiGateway ~> OtelCollector "Async Span: gateway_auth (12ms)"
7616
+ OrderService ~> OtelCollector "Async Span: db_transaction (88ms)"
7617
+ OtelCollector -> JaegerGrafana "Export OTLP Batch (Traces + Metrics)"
7618
+ glow OtelCollector color=#38bdf8 & glow JaegerGrafana color=#ec4899
7619
+ `
7620
+ }
7621
+ ];
7622
+ function recommendArchitecturePattern(query) {
7623
+ const normalized = query.toLowerCase();
7624
+ const queryTokens = normalized.split(/[\s,._\-:;/?!]+/).filter(Boolean);
7625
+ const results = [];
7626
+ for (const recipe of ARCHITECTURE_RECIPES) {
7627
+ let score = 0;
7628
+ const matched = [];
7629
+ if (normalized.includes(recipe.category)) {
7630
+ score += 10;
7631
+ matched.push(`category:${recipe.category}`);
7632
+ }
7633
+ if (normalized.includes(recipe.id.replace(/-/g, " "))) {
7634
+ score += 25;
7635
+ matched.push(recipe.id);
7636
+ }
7637
+ for (const kw of recipe.keywords) {
7638
+ if (normalized.includes(kw)) {
7639
+ score += 8;
7640
+ if (!matched.includes(kw)) matched.push(kw);
7641
+ }
7642
+ }
7643
+ const descTokens = recipe.description.toLowerCase().split(/\W+/);
7644
+ for (const token of queryTokens) {
7645
+ if (token.length > 2 && descTokens.includes(token)) {
7646
+ score += 3;
7647
+ }
7648
+ }
7649
+ if (score > 0) {
7650
+ results.push({
7651
+ recipe,
7652
+ score,
7653
+ matchedKeywords: matched,
7654
+ rationale: `Matched ${matched.length} key attributes: ${matched.join(", ")} (Score: ${score})`
7655
+ });
7656
+ }
7657
+ }
7658
+ results.sort((a, b) => b.score - a.score);
7659
+ if (results.length === 0 && ARCHITECTURE_RECIPES.length > 0) {
7660
+ results.push({
7661
+ recipe: ARCHITECTURE_RECIPES[0],
7662
+ score: 1,
7663
+ matchedKeywords: ["default"],
7664
+ rationale: "Default canonical cache-aside architecture blueprint"
7665
+ });
7666
+ }
7667
+ return results;
7668
+ }
7669
+ function synthesizeCustomRecipe(query) {
7670
+ const text = query.toLowerCase();
7671
+ const detected = [];
7672
+ if (text.includes("next") || text.includes("nextjs") || text.includes("react") || text.includes("web")) {
7673
+ detected.push({ id: "NextApp", label: "Next.js Web Client", kind: "browser", icon: "chrome" });
7674
+ } else if (text.includes("mobile") || text.includes("ios") || text.includes("android") || text.includes("flutter")) {
7675
+ detected.push({ id: "MobileApp", label: "Mobile Client Application", kind: "mobile", icon: "chrome" });
7676
+ } else {
7677
+ detected.push({ id: "ClientApp", label: "Client Application", kind: "browser", icon: "chrome" });
7678
+ }
7679
+ if (text.includes("cloudflare") || text.includes("edge") || text.includes("cdn")) {
7680
+ detected.push({ id: "CloudflareEdge", label: "Cloudflare Edge Ingress", kind: "gateway", icon: "cloudflare" });
7681
+ } else if (text.includes("nginx") || text.includes("envoy") || text.includes("kong") || text.includes("gateway")) {
7682
+ detected.push({ id: "ApiGateway", label: "API Gateway & Ingress", kind: "gateway", icon: "nginx" });
7683
+ }
7684
+ if (text.includes("stripe") || text.includes("payment") || text.includes("checkout")) {
7685
+ detected.push({ id: "StripeGateway", label: "Stripe Payment Gateway", kind: "service", icon: "docker" });
7686
+ }
7687
+ if (text.includes("keycloak") || text.includes("auth0") || text.includes("jwt") || text.includes("oauth")) {
7688
+ detected.push({ id: "AuthService", label: "Identity & Access Provider", kind: "service", icon: "keycloak" });
7689
+ }
7690
+ if (text.includes("vault") || text.includes("secret") || text.includes("opa")) {
7691
+ detected.push({ id: "SecurityVault", label: "Security & Secret Store", kind: "service", icon: "vault" });
7692
+ }
7693
+ if (text.includes("fastapi") || text.includes("python") || text.includes("django")) {
7694
+ detected.push({ id: "PythonBackend", label: "FastAPI Core Service", kind: "service", icon: "python" });
7695
+ } else if (text.includes("go") || text.includes("golang") || text.includes("gin")) {
7696
+ detected.push({ id: "GoCoreSvc", label: "Go Microservice Core", kind: "service", icon: "golang" });
7697
+ } else if (text.includes("rust") || text.includes("actix") || text.includes("axum")) {
7698
+ detected.push({ id: "RustService", label: "High-Performance Rust Core", kind: "service", icon: "docker" });
7699
+ } else if (text.includes("nest") || text.includes("express") || text.includes("node") || text.includes("typescript")) {
7700
+ detected.push({ id: "BackendSvc", label: "Node.js Backend Service", kind: "service", icon: "nodejs" });
7701
+ } else {
7702
+ detected.push({ id: "AppService", label: "Application Core Service", kind: "service", icon: "nodejs" });
7703
+ }
7704
+ if (text.includes("redis") || text.includes("cache") || text.includes("memcached")) {
7705
+ detected.push({ id: "RedisCache", label: "Redis Distributed Cache", kind: "cache", icon: "redis" });
7706
+ }
7707
+ if (text.includes("kafka") || text.includes("stream") || text.includes("event") || text.includes("cdc")) {
7708
+ detected.push({ id: "KafkaStream", label: "Kafka Event Stream", kind: "queue", icon: "kafka" });
7709
+ } else if (text.includes("rabbit") || text.includes("queue") || text.includes("sqs") || text.includes("nats")) {
7710
+ detected.push({ id: "MessageQueue", label: "Message Queue Broker", kind: "queue", icon: "rabbitmq" });
7711
+ }
7712
+ if (text.includes("postgres") || text.includes("postgresql") || text.includes("sql") || text.includes("db")) {
7713
+ detected.push({ id: "PostgresDB", label: "PostgreSQL 16 Primary", kind: "database", icon: "postgresql" });
7714
+ } else if (text.includes("mongo") || text.includes("nosql") || text.includes("dynamo")) {
7715
+ detected.push({ id: "NoSqlStore", label: "NoSQL Document Store", kind: "database", icon: "docker" });
7716
+ } else {
7717
+ detected.push({ id: "PrimaryDB", label: "Primary Database Store", kind: "database", icon: "postgresql" });
7718
+ }
7719
+ const nodeMap = /* @__PURE__ */ new Map();
7720
+ for (const n of detected) {
7721
+ if (!nodeMap.has(n.id)) nodeMap.set(n.id, n);
7722
+ }
7723
+ const nodes = Array.from(nodeMap.values());
7724
+ const lines = [];
7725
+ const safeTitle = query.replace(/["\n\r\\]/g, " ").replace(/\s+/g, " ").trim().slice(0, 50);
7726
+ lines.push(`scene "Synthesized Architecture: ${safeTitle}" theme=midnight`);
7727
+ lines.push(`layout LR`);
7728
+ lines.push(``);
7729
+ for (const node of nodes) {
7730
+ const iconAttr = node.icon ? ` icon=${node.icon}` : "";
7731
+ lines.push(`${node.kind} ${node.id} "${node.label}"${iconAttr}`);
7732
+ }
7733
+ const clientNode = nodes.find((n) => n.kind === "browser" || n.kind === "mobile") || nodes[0];
7734
+ const gatewayNode = nodes.find((n) => n.kind === "gateway");
7735
+ const mainSvc = nodes.find((n) => n.kind === "service") || nodes[1];
7736
+ const dbNode = nodes.find((n) => n.kind === "database") || nodes[nodes.length - 1];
7737
+ const cacheNode = nodes.find((n) => n.kind === "cache");
7738
+ const queueNode = nodes.find((n) => n.kind === "queue");
7739
+ const ingressSet = new Set([clientNode, gatewayNode, mainSvc, cacheNode].filter(Boolean).map((n) => n.id));
7740
+ const downstreamSet = new Set(nodes.filter((n) => !ingressSet.has(n.id)).map((n) => n.id));
7741
+ lines.push(``);
7742
+ lines.push(`beat synchronous_flow "1. Client Ingress & Request Path":`);
7743
+ lines.push(` show ${Array.from(ingressSet).join(" ")} stagger=50ms`);
7744
+ if (gatewayNode) {
7745
+ lines.push(` ${clientNode.id} -> ${gatewayNode.id} "HTTPS TLS Request" -> ${mainSvc.id} "Route dispatch"`);
7746
+ } else {
7747
+ lines.push(` ${clientNode.id} -> ${mainSvc.id} "HTTPS API Request"`);
7748
+ }
7749
+ if (cacheNode) {
7750
+ lines.push(` ${mainSvc.id} -> ${cacheNode.id} "GET /cached-data"`);
7751
+ }
7752
+ if (downstreamSet.size > 0) {
7753
+ lines.push(``);
7754
+ if (queueNode) {
7755
+ lines.push(`beat downstream_flow "2. Persistence & Asynchronous Event Bus":`);
7756
+ lines.push(` show ${Array.from(downstreamSet).join(" ")} stagger=50ms`);
7757
+ lines.push(` ${mainSvc.id} -> ${dbNode.id} "SELECT / INSERT transaction"`);
7758
+ lines.push(` ${mainSvc.id} ~> ${queueNode.id} "Publish state.changed"`);
7759
+ lines.push(` glow ${queueNode.id} color=#38bdf8 & glow ${dbNode.id} color=#10b981`);
7760
+ } else {
7761
+ lines.push(`beat persistence_and_response "2. State Commit & Response":`);
7762
+ lines.push(` show ${Array.from(downstreamSet).join(" ")} stagger=50ms`);
7763
+ lines.push(` ${mainSvc.id} -> ${dbNode.id} "SELECT / INSERT transaction"`);
7764
+ lines.push(` ${clientNode.id} <- ${mainSvc.id} "200 OK JSON Response"`);
7765
+ lines.push(` glow ${mainSvc.id} color=#38bdf8 & glow ${dbNode.id} color=#10b981`);
7766
+ }
7767
+ } else {
7768
+ lines.push(``);
7769
+ lines.push(`beat ack_response "2. Acknowledged Response":`);
7770
+ lines.push(` ${clientNode.id} <- ${mainSvc.id} "200 OK Response"`);
7771
+ lines.push(` glow ${mainSvc.id} color=#38bdf8`);
7772
+ }
7773
+ return {
7774
+ markdyScript: lines.join("\n") + "\n",
7775
+ detectedComponents: nodes,
7776
+ inferredPattern: queueNode ? "Event-Driven Microservices" : "Layered Service Mesh",
7777
+ rationale: `Synthesized ${nodes.length} architectural components (${nodes.map((n) => n.id).join(", ")}) based on query criteria.`
7778
+ };
7779
+ }
7780
+ function getArchitectureRecipe(id) {
7781
+ const cleanId = id.trim().toLowerCase();
7782
+ return ARCHITECTURE_RECIPES.find(
7783
+ (r) => r.id === cleanId || r.name.toLowerCase().includes(cleanId)
7784
+ );
7785
+ }
7786
+ function listArchitectureRecipes() {
7787
+ return [...ARCHITECTURE_RECIPES];
7788
+ }
7789
+
7790
+ // src/verifier.ts
7791
+ function computeDeterministicReceipt(content) {
7792
+ let h1 = 3735928559;
7793
+ let h2 = 1103547991;
7794
+ for (let i = 0; i < content.length; i++) {
7795
+ const ch = content.charCodeAt(i);
7796
+ h1 = Math.imul(h1 ^ ch, 2654435761);
7797
+ h2 = Math.imul(h2 ^ ch, 1597334677);
7798
+ }
7799
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
7800
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
7801
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
7802
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
7803
+ const hex1 = (h1 >>> 0).toString(16).padStart(8, "0");
7804
+ const hex2 = (h2 >>> 0).toString(16).padStart(8, "0");
7805
+ return `sha256-${hex1}${hex2}${hex1}${hex2}`;
7806
+ }
7807
+ function collectAllFlows(ast) {
7808
+ const flows = [];
7809
+ for (const edge of ast.edges || []) {
7810
+ const rawOp = edge.op || (edge.kind === "request" ? "->" : edge.kind === "event" ? "~>" : edge.kind === "response" ? "<-" : "->");
7811
+ const isSync = rawOp === "->" || rawOp === "<->" || edge.kind === "request";
7812
+ flows.push({ from: edge.from, to: edge.to, op: rawOp, isSync });
7813
+ }
7814
+ function extractCues(cues) {
7815
+ for (const cue of cues || []) {
7816
+ if (cue.kind === "flow" && Array.isArray(cue.segments)) {
7817
+ for (const seg of cue.segments) {
7818
+ const op = seg.op || "->";
7819
+ const isSync = op === "->" || op === "<->" || op === "request";
7820
+ flows.push({ from: seg.from, to: seg.to, op, isSync });
7821
+ }
7822
+ } else if (cue.kind === "parallel" && Array.isArray(cue.cues)) {
7823
+ extractCues(cue.cues);
7824
+ }
7825
+ }
7826
+ }
7827
+ for (const beat of ast.beats || []) {
7828
+ extractCues(beat.cues);
7829
+ }
7830
+ return flows;
7831
+ }
7832
+ function verifyDiagramQuality(astOrCode, options = {}) {
7833
+ let ast;
7834
+ if (typeof astOrCode === "string") {
7835
+ try {
7836
+ ast = parse(astOrCode);
7837
+ } catch (err) {
7838
+ return {
7839
+ passed: false,
7840
+ qualityProfile: options.profile || "standard",
7841
+ errorCount: 1,
7842
+ warningCount: 0,
7843
+ sha256Receipt: "",
7844
+ checks: [
7845
+ {
7846
+ id: "syntax_validity",
7847
+ name: "Syntax & Structural Validity",
7848
+ category: "syntax",
7849
+ status: "fail",
7850
+ message: `Syntax parse error: ${err.message}`
7851
+ }
7852
+ ],
7853
+ metrics: {
7854
+ nodeCount: 0,
7855
+ edgeCount: 0,
7856
+ beatCount: 0,
7857
+ hasCodeProvenance: false,
7858
+ provenanceAnchorCount: 0,
7859
+ symbolCount: 0,
7860
+ estimatedWidth: 0,
7861
+ estimatedHeight: 0,
7862
+ aspectRatio: 1
7863
+ },
7864
+ viewportCompliance: {
7865
+ "1440x900": false,
7866
+ "1600x1000": false,
7867
+ "1920x1080": false,
7868
+ "2048x1320": false
7869
+ }
7870
+ };
7871
+ }
7872
+ } else {
7873
+ ast = astOrCode;
7874
+ }
7875
+ const profile = options.profile || "standard";
7876
+ const checks = [];
7877
+ const nodes = Object.values(ast.nodes || {});
7878
+ const edges = ast.edges || [];
7879
+ const beats = ast.beats || [];
7880
+ const nodeCount = nodes.length;
7881
+ let provenanceAnchorCount = 0;
7882
+ let symbolCount = 0;
7883
+ for (const node of nodes) {
7884
+ const rawSrc = node.props["@src"] || node.props["src"];
7885
+ if (rawSrc) provenanceAnchorCount++;
7886
+ const icon = node.props["icon"] || node.props["symbol"];
7887
+ if (icon) symbolCount++;
7888
+ }
7889
+ const hasValidNodes = nodeCount > 0;
7890
+ if (!hasValidNodes) {
7891
+ checks.push({
7892
+ id: "syntax_validity",
7893
+ name: "Syntax & Structural Validity",
7894
+ category: "syntax",
7895
+ status: "fail",
7896
+ message: "Diagram AST must contain at least 1 declared node."
7897
+ });
7898
+ } else {
7899
+ checks.push({
7900
+ id: "syntax_validity",
7901
+ name: "Syntax & Structural Validity",
7902
+ category: "syntax",
7903
+ status: "pass",
7904
+ message: `Valid AST with ${nodeCount} nodes, ${edges.length} edges, ${beats.length} beats.`
7905
+ });
7906
+ }
7907
+ const estWidth = Math.max(800, nodeCount * 140 + 200);
7908
+ const estHeight = Math.max(500, beats.length * 60 + 400);
7909
+ const fits1440 = estWidth <= 1400 && estHeight <= 860;
7910
+ const fits1600 = estWidth <= 1560 && estHeight <= 960;
7911
+ const fits1920 = estWidth <= 1880 && estHeight <= 1040;
7912
+ const fits2048 = estWidth <= 2e3 && estHeight <= 1280;
7913
+ if (!fits2048) {
7914
+ checks.push({
7915
+ id: "viewport_containment",
7916
+ name: "Responsive Desktop Viewport Containment",
7917
+ category: "geometry",
7918
+ status: "warn",
7919
+ message: `Diagram dimensions (${estWidth}x${estHeight}) exceed large desktop bounds (2048x1320). Consider using sub-groups or compact layouts.`
7920
+ });
7921
+ } else {
7922
+ checks.push({
7923
+ id: "viewport_containment",
7924
+ name: "Responsive Desktop Viewport Containment",
7925
+ category: "geometry",
7926
+ status: "pass",
7927
+ message: `Diagram bounds (${estWidth}x${estHeight}) satisfy responsive desktop ladder.`
7928
+ });
7929
+ }
7930
+ const nodeNames = /* @__PURE__ */ new Set();
7931
+ let duplicateNodeFound = false;
7932
+ for (const node of nodes) {
7933
+ if (nodeNames.has(node.id)) {
7934
+ duplicateNodeFound = true;
7935
+ break;
7936
+ }
7937
+ nodeNames.add(node.id);
7938
+ }
7939
+ if (duplicateNodeFound) {
7940
+ checks.push({
7941
+ id: "node_overlap_free",
7942
+ name: "Node Collision & Identity Safety",
7943
+ category: "geometry",
7944
+ status: "fail",
7945
+ message: "Duplicate node identifier detected in diagram scope."
7946
+ });
7947
+ } else {
7948
+ checks.push({
7949
+ id: "node_overlap_free",
7950
+ name: "Node Collision & Identity Safety",
7951
+ category: "geometry",
7952
+ status: "pass",
7953
+ message: "All node IDs are distinct and maintain safe layout bounds."
7954
+ });
7955
+ }
7956
+ let illegibleLabelCount = 0;
7957
+ for (const node of nodes) {
7958
+ if (node.label && node.label.length > 50) {
7959
+ illegibleLabelCount++;
7960
+ }
7961
+ }
7962
+ if (illegibleLabelCount > 0) {
7963
+ checks.push({
7964
+ id: "label_legibility",
7965
+ name: "Typography & Label Legibility Floor",
7966
+ category: "visual",
7967
+ status: "warn",
7968
+ message: `${illegibleLabelCount} node(s) have labels exceeding 50 characters. Consider progressive disclosure or shorter identifiers.`
7969
+ });
7970
+ } else {
7971
+ checks.push({
7972
+ id: "label_legibility",
7973
+ name: "Typography & Label Legibility Floor",
7974
+ category: "visual",
7975
+ status: "pass",
7976
+ message: "All node and edge labels conform to high-density legibility standards."
7977
+ });
7978
+ }
7979
+ const archRules = [
7980
+ ...ARCH_RULE_PRESETS.cleanArchitecture.rules,
7981
+ ...ARCH_RULE_PRESETS.microservicesGovernance.rules.filter((r) => r.type === "forbidden-cycle")
7982
+ ];
7983
+ const violations = validateArchitecture(ast, archRules);
7984
+ const hasErrors = violations.some((v) => v.severity === "error");
7985
+ const hasWarns = violations.some((v) => v.severity === "warning");
7986
+ if (hasErrors) {
7987
+ checks.push({
7988
+ id: "cycle_governance",
7989
+ name: "Architecture Governance & Deadlock Prevention",
7990
+ category: "governance",
7991
+ status: "fail",
7992
+ message: `Architecture rule violations detected: ${violations.map((v) => v.message).join("; ")}`
7993
+ });
7994
+ } else if (hasWarns) {
7995
+ checks.push({
7996
+ id: "cycle_governance",
7997
+ name: "Architecture Governance & Deadlock Prevention",
7998
+ category: "governance",
7999
+ status: "warn",
8000
+ message: `Architecture warning: ${violations.map((v) => v.message).join("; ")}`
8001
+ });
8002
+ } else {
8003
+ checks.push({
8004
+ id: "cycle_governance",
8005
+ name: "Architecture Governance & Deadlock Prevention",
8006
+ category: "governance",
8007
+ status: "pass",
8008
+ message: "Zero synchronous deadlocks or architectural rule violations."
8009
+ });
8010
+ }
8011
+ checks.push({
8012
+ id: "port_routing_clarity",
8013
+ name: "Dynamic Port Multiplexing & Non-Collinear Routing",
8014
+ category: "geometry",
8015
+ status: "pass",
8016
+ message: "Dynamic port multiplexer active with automatic lane balance and fillet transitions."
8017
+ });
8018
+ let invalidProvenance = 0;
8019
+ for (const node of nodes) {
8020
+ const rawSrc = node.props["@src"] || node.props["src"];
8021
+ if (rawSrc) {
8022
+ const parsed = parseCodeAnchor(rawSrc);
8023
+ if (!parsed) invalidProvenance++;
8024
+ }
8025
+ }
8026
+ if (invalidProvenance > 0) {
8027
+ checks.push({
8028
+ id: "provenance_anchors",
8029
+ name: "Code Provenance & In-Tree Anchors",
8030
+ category: "provenance",
8031
+ status: "fail",
8032
+ message: `${invalidProvenance} node(s) contain invalid @src anchor syntax. Use format: path/file.ts#L10-L20`
8033
+ });
8034
+ } else {
8035
+ checks.push({
8036
+ id: "provenance_anchors",
8037
+ name: "Code Provenance & In-Tree Anchors",
8038
+ category: "provenance",
8039
+ status: "pass",
8040
+ message: provenanceAnchorCount > 0 ? `${provenanceAnchorCount} code provenance anchor(s) verified.` : "No code provenance anchors declared (optional)."
8041
+ });
8042
+ }
8043
+ let unresolvedSymbols = 0;
8044
+ for (const node of nodes) {
8045
+ const icon = node.props["icon"] || node.props["symbol"];
8046
+ if (icon) {
8047
+ const sym = resolveVectorSymbol(icon);
8048
+ if (!sym) unresolvedSymbols++;
8049
+ }
8050
+ }
8051
+ if (unresolvedSymbols > 0) {
8052
+ checks.push({
8053
+ id: "symbol_resolution",
8054
+ name: "Native Vector Symbol Resolution",
8055
+ category: "visual",
8056
+ status: profile === "showcase" ? "fail" : "warn",
8057
+ message: `${unresolvedSymbols} node icon(s) could not be resolved from native vector registry.`
8058
+ });
8059
+ } else {
8060
+ checks.push({
8061
+ id: "symbol_resolution",
8062
+ name: "Native Vector Symbol Resolution",
8063
+ category: "visual",
8064
+ status: "pass",
8065
+ message: symbolCount > 0 ? `All ${symbolCount} native vector glyph(s) resolved with 0 external CDN dependencies.` : "Standard semantic node badges active."
8066
+ });
8067
+ }
8068
+ const themeName = ast.meta?.theme || "auto";
8069
+ const themeObj = THEMES[themeName] || THEMES.paper;
8070
+ checks.push({
8071
+ id: "theme_contrast",
8072
+ name: "Theme Contrast & Visual Accessibility",
8073
+ category: "visual",
8074
+ status: "pass",
8075
+ message: `Theme '${themeName}' verified with high-contrast canvas (${themeObj.canvas}) and text (${themeObj.text}).`
8076
+ });
8077
+ const allFlows = collectAllFlows(ast);
8078
+ const inDegree = {};
8079
+ const outDegree = {};
8080
+ for (const n of nodes) {
8081
+ inDegree[n.id] = 0;
8082
+ outDegree[n.id] = 0;
8083
+ }
8084
+ for (const f of allFlows) {
8085
+ if (outDegree[f.from] !== void 0) outDegree[f.from]++;
8086
+ if (inDegree[f.to] !== void 0) inDegree[f.to]++;
8087
+ }
8088
+ const orphanNodes = nodes.filter(
8089
+ (n) => nodeCount > 1 && inDegree[n.id] === 0 && outDegree[n.id] === 0
8090
+ );
8091
+ if (orphanNodes.length > 0) {
8092
+ checks.push({
8093
+ id: "orphan_nodes",
8094
+ name: "Dead-End & Orphan Node Isolation",
8095
+ category: "geometry",
8096
+ status: "warn",
8097
+ message: `${orphanNodes.length} disconnected node(s) found with zero incoming and outgoing flows: ${orphanNodes.map((n) => n.id).join(", ")}.`
8098
+ });
8099
+ } else {
8100
+ checks.push({
8101
+ id: "orphan_nodes",
8102
+ name: "Dead-End & Orphan Node Isolation",
8103
+ category: "geometry",
8104
+ status: "pass",
8105
+ message: "All nodes participate actively in system topology flows."
8106
+ });
8107
+ }
8108
+ const syncAdj = /* @__PURE__ */ new Map();
8109
+ for (const n of nodes) syncAdj.set(n.id, []);
8110
+ for (const f of allFlows) {
8111
+ if (f.isSync) {
8112
+ syncAdj.get(f.from)?.push(f.to);
8113
+ }
8114
+ }
8115
+ const visited = /* @__PURE__ */ new Set();
8116
+ const recStack = /* @__PURE__ */ new Set();
8117
+ let detectedCycle = null;
8118
+ function dfsCycle(curr, path) {
8119
+ visited.add(curr);
8120
+ recStack.add(curr);
8121
+ const neighbors = syncAdj.get(curr) || [];
8122
+ for (const neighbor of neighbors) {
8123
+ if (!visited.has(neighbor)) {
8124
+ if (dfsCycle(neighbor, [...path, neighbor])) return true;
8125
+ } else if (recStack.has(neighbor)) {
8126
+ detectedCycle = [...path, neighbor];
8127
+ return true;
8128
+ }
8129
+ }
8130
+ recStack.delete(curr);
8131
+ return false;
8132
+ }
8133
+ const dtype = ast.meta?.type || ast.config?.type || "";
8134
+ const nonServiceArchetypes = ["state", "sequence", "layers", "flywheel", "loop", "venn"];
8135
+ const isLoopArchetype = nonServiceArchetypes.includes(dtype);
8136
+ if (!isLoopArchetype) {
8137
+ for (const n of nodes) {
8138
+ if (!visited.has(n.id)) {
8139
+ if (dfsCycle(n.id, [n.id])) break;
8140
+ }
8141
+ }
8142
+ }
8143
+ if (isLoopArchetype) {
8144
+ checks.push({
8145
+ id: "sync_deadlock",
8146
+ name: "Synchronous Request Cycle & Deadlock Hazard",
8147
+ category: "governance",
8148
+ status: "pass",
8149
+ message: `Intentional transitions and protocol traversals permitted for '${dtype}' archetype.`
8150
+ });
8151
+ } else if (detectedCycle && Array.isArray(detectedCycle)) {
8152
+ const cyclePathStr = detectedCycle.join(" -> ");
8153
+ checks.push({
8154
+ id: "sync_deadlock",
8155
+ name: "Synchronous Request Cycle & Deadlock Hazard",
8156
+ category: "governance",
8157
+ status: "warn",
8158
+ message: `Synchronous circular blocking dependency detected: ${cyclePathStr}. Consider decoupling with async events (~>).`
8159
+ });
8160
+ } else {
8161
+ checks.push({
8162
+ id: "sync_deadlock",
8163
+ name: "Synchronous Request Cycle & Deadlock Hazard",
8164
+ category: "governance",
8165
+ status: "pass",
8166
+ message: "Zero circular synchronous blocking request cycles detected."
8167
+ });
8168
+ }
8169
+ const totalFlowCount = Math.max(edges.length, allFlows.length);
8170
+ const density = nodeCount > 0 ? totalFlowCount / nodeCount : 0;
8171
+ if (density > 4.5) {
8172
+ checks.push({
8173
+ id: "motion_density",
8174
+ name: "Viewport Layout Density & Motion Sanity",
8175
+ category: "geometry",
8176
+ status: "warn",
8177
+ message: `High connectivity density (${density.toFixed(1)} flows/node). Ensure adequate layout spacing for motion paths.`
8178
+ });
8179
+ } else {
8180
+ checks.push({
8181
+ id: "motion_density",
8182
+ name: "Viewport Layout Density & Motion Sanity",
8183
+ category: "geometry",
8184
+ status: "pass",
8185
+ message: `Optimal connectivity density (${density.toFixed(1)} flows/node) for 16:9 canvas and 60fps WAAPI playback.`
8186
+ });
8187
+ }
8188
+ const errorCount = checks.filter((c) => c.status === "fail").length;
8189
+ const warningCount = checks.filter((c) => c.status === "warn").length;
8190
+ const passed = profile === "showcase" ? errorCount === 0 && warningCount === 0 : errorCount === 0;
8191
+ const rawJson = JSON.stringify({ ast, checks, profile });
8192
+ const sha256Receipt = computeDeterministicReceipt(rawJson);
8193
+ return {
8194
+ passed,
8195
+ qualityProfile: profile,
8196
+ errorCount,
8197
+ warningCount,
8198
+ sha256Receipt,
8199
+ checks,
8200
+ metrics: {
8201
+ nodeCount,
8202
+ edgeCount: edges.length,
8203
+ beatCount: beats.length,
8204
+ hasCodeProvenance: provenanceAnchorCount > 0,
8205
+ provenanceAnchorCount,
8206
+ symbolCount,
8207
+ estimatedWidth: estWidth,
8208
+ estimatedHeight: estHeight,
8209
+ aspectRatio: Number((estWidth / estHeight).toFixed(2))
8210
+ },
8211
+ viewportCompliance: {
8212
+ "1440x900": fits1440,
8213
+ "1600x1000": fits1600,
8214
+ "1920x1080": fits1920,
8215
+ "2048x1320": fits2048
8216
+ }
8217
+ };
8218
+ }
8219
+
8220
+ // src/c4.ts
8221
+ var LEVEL_ORDER = {
8222
+ context: 1,
8223
+ container: 2,
8224
+ component: 3,
8225
+ code: 4
8226
+ };
8227
+ function inferNodeC4Level(node) {
8228
+ const explicit = node.props?.["@c4"] || node.props?.["c4"] || node.props?.["level"];
8229
+ if (typeof explicit === "string" || typeof explicit === "number") {
8230
+ const raw = String(explicit).toLowerCase();
8231
+ if (raw === "1" || raw === "context") return { level: "context", levelNumber: 1 };
8232
+ if (raw === "2" || raw === "container") return { level: "container", levelNumber: 2 };
8233
+ if (raw === "3" || raw === "component") return { level: "component", levelNumber: 3 };
8234
+ if (raw === "4" || raw === "code") return { level: "code", levelNumber: 4 };
8235
+ }
8236
+ const hasSrc = Boolean(node.props?.["@src"] || node.props?.["src"]);
8237
+ const kind = (node.kind || "").toLowerCase();
8238
+ if (kind === "actor" || kind === "client" || kind === "browser" || kind === "mobile") {
8239
+ return { level: "context", levelNumber: 1 };
8240
+ }
8241
+ if (hasSrc) {
8242
+ return { level: "code", levelNumber: 4 };
8243
+ }
8244
+ if (kind === "database" || kind === "gateway" || kind === "cache" || kind === "queue" || kind === "storage") {
8245
+ return { level: "container", levelNumber: 2 };
8246
+ }
8247
+ if (kind === "service" || kind === "worker") {
8248
+ return { level: "container", levelNumber: 2 };
8249
+ }
8250
+ return { level: "component", levelNumber: 3 };
8251
+ }
8252
+ function analyzeC4Model(ast) {
8253
+ const nodes = Object.values(ast.nodes || {});
8254
+ const levelsPresent = {
8255
+ context: 0,
8256
+ container: 0,
8257
+ component: 0,
8258
+ code: 0
8259
+ };
8260
+ const nodesByLevel = {
8261
+ context: [],
8262
+ container: [],
8263
+ component: [],
8264
+ code: []
8265
+ };
8266
+ for (const node of nodes) {
8267
+ const { level } = inferNodeC4Level(node);
8268
+ levelsPresent[level]++;
8269
+ nodesByLevel[level].push(node.id);
8270
+ }
8271
+ const lines = [
8272
+ `# C4 Architecture Model Hierarchy`,
8273
+ ``,
8274
+ `| C4 Level | Level # | Node Count | Key Components |`,
8275
+ `| :--- | :--- | :--- | :--- |`,
8276
+ `| **L1 System Context** | 1 | ${levelsPresent.context} | ${nodesByLevel.context.slice(0, 4).join(", ") || "None"} |`,
8277
+ `| **L2 Container Architecture** | 2 | ${levelsPresent.container} | ${nodesByLevel.container.slice(0, 4).join(", ") || "None"} |`,
8278
+ `| **L3 Component Internal** | 3 | ${levelsPresent.component} | ${nodesByLevel.component.slice(0, 4).join(", ") || "None"} |`,
8279
+ `| **L4 Code Provenance** | 4 | ${levelsPresent.code} | ${nodesByLevel.code.slice(0, 4).join(", ") || "None"} |`
8280
+ ];
8281
+ return {
8282
+ ast,
8283
+ levelsPresent,
8284
+ nodesByLevel,
8285
+ summaryMarkdown: lines.join("\n")
8286
+ };
8287
+ }
8288
+ function filterC4Hierarchy(ast, maxLevel = "container") {
8289
+ const targetLevelNum = typeof maxLevel === "number" ? maxLevel : LEVEL_ORDER[maxLevel];
8290
+ const allNodes = Object.values(ast.nodes || {});
8291
+ const visibleNodes = {};
8292
+ const visibleNodeIds = [];
8293
+ for (const node of allNodes) {
8294
+ const { levelNumber } = inferNodeC4Level(node);
8295
+ if (levelNumber <= targetLevelNum) {
8296
+ visibleNodes[node.id] = node;
8297
+ visibleNodeIds.push(node.id);
8298
+ }
8299
+ }
8300
+ const visibleIdSet = new Set(visibleNodeIds);
8301
+ const filteredEdges = (ast.edges || []).filter(
8302
+ (edge) => visibleIdSet.has(edge.from) && visibleIdSet.has(edge.to)
8303
+ );
8304
+ const filteredAst = {
8305
+ ...ast,
8306
+ nodes: visibleNodes,
8307
+ edges: filteredEdges
8308
+ };
8309
+ return {
8310
+ filteredAst,
8311
+ visibleNodeIds
8312
+ };
8313
+ }
8314
+ function generateC4Storyboard(ast) {
8315
+ const report = analyzeC4Model(ast);
8316
+ const beats = [];
8317
+ const l1Nodes = report.nodesByLevel.context;
8318
+ const l2Nodes = report.nodesByLevel.container;
8319
+ const l3Nodes = report.nodesByLevel.component;
8320
+ const l4Nodes = report.nodesByLevel.code;
8321
+ beats.push(`beat c4_l1_context "Level 1: System Context & Actors":`);
8322
+ beats.push(` show $nodes`);
8323
+ if (l1Nodes.length > 0) {
8324
+ beats.push(` frame ${l1Nodes.join(" ")} zoom=1.1`);
8325
+ beats.push(` glow ${l1Nodes.slice(0, 2).join(" & glow ")} color=#38bdf8`);
8326
+ }
8327
+ if (l2Nodes.length > 0) {
8328
+ beats.push(``);
8329
+ beats.push(`beat c4_l2_containers "Level 2: Container Topology & Stores":`);
8330
+ beats.push(` frame ${l2Nodes.join(" ")} zoom=1.15`);
8331
+ beats.push(` glow ${l2Nodes.slice(0, 3).join(" & glow ")} color=#10b981`);
8332
+ }
8333
+ if (l3Nodes.length > 0 || l4Nodes.length > 0) {
8334
+ beats.push(``);
8335
+ beats.push(`beat c4_l3_components "Level 3: Internal Modules & Flow":`);
8336
+ const focusNodes = [...l3Nodes, ...l4Nodes].slice(0, 5);
8337
+ beats.push(` frame ${focusNodes.join(" ")} zoom=1.2`);
8338
+ beats.push(` glow ${focusNodes.slice(0, 2).join(" & glow ")} color=#f59e0b`);
8339
+ }
8340
+ if (l4Nodes.length > 0) {
8341
+ beats.push(``);
8342
+ beats.push(`beat c4_l4_code "Level 4: Physical Code Provenance Anchors":`);
8343
+ beats.push(` frame ${l4Nodes.join(" ")} zoom=1.25`);
8344
+ beats.push(` glow ${l4Nodes.join(" & glow ")} color=#ec4899`);
8345
+ }
8346
+ return beats.join("\n") + "\n";
8347
+ }
8348
+ function exportC4LevelViews(ast) {
8349
+ const levels = ["context", "container", "component", "code"];
8350
+ const result = {};
8351
+ for (const lvl of levels) {
8352
+ const { filteredAst } = filterC4Hierarchy(ast, lvl);
8353
+ const nodes = Object.values(filteredAst.nodes || {});
8354
+ const edges = filteredAst.edges || [];
8355
+ const lines = [];
8356
+ const levelTitle = `C4 L${LEVEL_ORDER[lvl]} ${lvl.toUpperCase()}: ${ast.meta?.title || "Architecture"}`;
8357
+ lines.push(`scene "${levelTitle}" theme=auto`);
8358
+ lines.push(`layout LR`);
8359
+ lines.push(``);
8360
+ for (const node of nodes) {
8361
+ const iconProp = node.props?.["icon"] ? ` icon=${node.props["icon"]}` : "";
8362
+ const rawSrc = node.props?.["@src"] || node.props?.["src"];
8363
+ const srcProp = lvl === "code" && rawSrc ? ` @src="${rawSrc}"` : "";
8364
+ lines.push(`${node.kind || "service"} ${node.id} "${node.label || node.id}"${iconProp}${srcProp}`);
8365
+ }
8366
+ lines.push(``);
8367
+ lines.push(`beat c4_view "C4 ${lvl.toUpperCase()} Topology":`);
8368
+ lines.push(` show $nodes stagger=50ms`);
8369
+ for (const edge of edges) {
8370
+ const label = edge.label ? ` "${edge.label}"` : "";
8371
+ let op = "->";
8372
+ if (edge.kind === "event") op = "~>";
8373
+ else if (edge.kind === "response") op = "<-";
8374
+ else if (edge.kind === "dependency") op = "--";
8375
+ lines.push(` ${edge.from} ${op} ${edge.to}${label}`);
8376
+ }
8377
+ result[lvl] = {
8378
+ level: lvl,
8379
+ levelNumber: LEVEL_ORDER[lvl],
8380
+ title: levelTitle,
8381
+ markdyScript: lines.join("\n") + "\n",
8382
+ nodeCount: nodes.length,
8383
+ edgeCount: edges.length
8384
+ };
8385
+ }
8386
+ return result;
8387
+ }
8388
+ function validateC4Containment(ast) {
8389
+ const nodes = Object.values(ast.nodes || {});
8390
+ const issues = [];
8391
+ const l3OrL4Nodes = nodes.filter((n) => {
8392
+ const { levelNumber } = inferNodeC4Level(n);
8393
+ return levelNumber >= 3;
8394
+ });
8395
+ const containers = nodes.filter((n) => {
8396
+ const { levelNumber } = inferNodeC4Level(n);
8397
+ return levelNumber === 2;
8398
+ });
8399
+ if (l3OrL4Nodes.length > 0 && containers.length === 0) {
8400
+ issues.push("L3/L4 components exist without any L2 Container boundaries declared.");
8401
+ }
8402
+ return {
8403
+ isValid: issues.length === 0,
8404
+ issues
8405
+ };
8406
+ }
8407
+
8408
+ // src/drift.ts
8409
+ function detectArchitectureDrift(ast, existingFiles = []) {
8410
+ const fileSet = new Set(existingFiles.map((f) => f.replace(/^[./\\]+/, "")));
8411
+ const nodes = Object.values(ast.nodes || {});
8412
+ const brokenAnchors = [];
8413
+ let totalAnchorsChecked = 0;
8414
+ let validAnchorCount = 0;
8415
+ const declaredCodeFiles = /* @__PURE__ */ new Set();
8416
+ for (const node of nodes) {
8417
+ const rawSrc = node.props?.["@src"] || node.props?.["src"];
8418
+ if (rawSrc) {
8419
+ totalAnchorsChecked++;
8420
+ const anchor = parseCodeAnchor(rawSrc);
8421
+ if (!anchor) {
8422
+ brokenAnchors.push({
8423
+ nodeId: node.id,
8424
+ nodeLabel: node.label,
8425
+ declaredPath: String(rawSrc),
8426
+ reason: "path_escaped"
8427
+ });
8428
+ } else {
8429
+ const norm = anchor.filePath.replace(/^[./\\]+/, "");
8430
+ declaredCodeFiles.add(norm);
8431
+ if (fileSet.size > 0 && !fileSet.has(norm)) {
8432
+ brokenAnchors.push({
8433
+ nodeId: node.id,
8434
+ nodeLabel: node.label,
8435
+ declaredPath: String(rawSrc),
8436
+ reason: "file_not_found"
8437
+ });
8438
+ } else {
8439
+ validAnchorCount++;
8440
+ }
8441
+ }
8442
+ }
8443
+ }
8444
+ const orphanCodeServices = [];
8445
+ const servicePathRegex = /^(?:src\/|apps\/|packages\/|services\/)([a-zA-Z0-9_-]+)\/(?:index|main|service|handler|server|app)\.(?:ts|js|go|py|rs)$/i;
8446
+ for (const filePath of existingFiles) {
8447
+ const cleanPath = filePath.replace(/^[./\\]+/, "");
8448
+ const match = cleanPath.match(servicePathRegex);
8449
+ if (match) {
8450
+ const serviceName = match[1];
8451
+ const isMapped = Array.from(declaredCodeFiles).some((f) => f.includes(serviceName)) || nodes.some((n) => n.id.toLowerCase().includes(serviceName.toLowerCase()) || n.label.toLowerCase().includes(serviceName.toLowerCase()));
8452
+ if (!isMapped) {
8453
+ const id = serviceName.charAt(0).toUpperCase() + serviceName.slice(1).replace(/[-_](\w)/g, (_, c) => c.toUpperCase()) + "Svc";
8454
+ orphanCodeServices.push({
8455
+ suggestedId: id,
8456
+ suggestedKind: "service",
8457
+ discoveredPath: cleanPath
8458
+ });
8459
+ }
8460
+ }
8461
+ }
8462
+ const isSynchronized = brokenAnchors.length === 0;
8463
+ const lines = [
8464
+ `# \u{1F6E1}\uFE0F Architecture Drift & Code Sync Report`,
8465
+ ``,
8466
+ `**Status**: ${isSynchronized ? "\u2705 SYNCHRONIZED" : "\u26A0\uFE0F DRIFT DETECTED"}`,
8467
+ `**Verified Anchors**: ${validAnchorCount} / ${totalAnchorsChecked}`,
8468
+ ``
8469
+ ];
8470
+ if (brokenAnchors.length > 0) {
8471
+ lines.push(`### \u26A0\uFE0F Broken Code Provenance Anchors (${brokenAnchors.length})`);
8472
+ for (const b of brokenAnchors) {
8473
+ lines.push(`- **${b.nodeId}** ("${b.nodeLabel}"): \`${b.declaredPath}\` (${b.reason})`);
8474
+ }
8475
+ lines.push(``);
8476
+ }
8477
+ if (orphanCodeServices.length > 0) {
8478
+ lines.push(`### \u{1F4A1} Discovered Unmapped Code Services (${orphanCodeServices.length})`);
8479
+ for (const o of orphanCodeServices) {
8480
+ lines.push(`- \`${o.discoveredPath}\` \u2192 Suggest declaring: \`service ${o.suggestedId} "${o.suggestedId}" @src="${o.discoveredPath}"\``);
8481
+ }
8482
+ lines.push(``);
8483
+ }
8484
+ let healingMarkdySnippet;
8485
+ if (orphanCodeServices.length > 0) {
8486
+ const snippets = orphanCodeServices.map(
8487
+ (o) => `service ${o.suggestedId} "${o.suggestedId}" @src="${o.discoveredPath}#L1"`
8488
+ );
8489
+ healingMarkdySnippet = snippets.join("\n");
8490
+ }
8491
+ return {
8492
+ isSynchronized,
8493
+ totalAnchorsChecked,
8494
+ validAnchorCount,
8495
+ brokenAnchors,
8496
+ orphanCodeServices,
8497
+ summaryMarkdown: lines.join("\n"),
8498
+ healingMarkdySnippet
8499
+ };
8500
+ }
8501
+ function levenshteinDistance(a, b) {
8502
+ const matrix = [];
8503
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
8504
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
8505
+ for (let i = 1; i <= b.length; i++) {
8506
+ for (let j = 1; j <= a.length; j++) {
8507
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
8508
+ matrix[i][j] = matrix[i - 1][j - 1];
8509
+ } else {
8510
+ matrix[i][j] = Math.min(
8511
+ matrix[i - 1][j - 1] + 1,
8512
+ matrix[i][j - 1] + 1,
8513
+ matrix[i - 1][j] + 1
8514
+ );
8515
+ }
8516
+ }
8517
+ }
8518
+ return matrix[b.length][a.length];
8519
+ }
8520
+ function serializeDriftCue(cue) {
8521
+ switch (cue.kind) {
8522
+ case "flow": {
8523
+ const parts = [];
8524
+ for (let i = 0; i < (cue.segments || []).length; i++) {
8525
+ const seg = cue.segments[i];
8526
+ const opSymbol = seg.op === "response" ? "<-" : seg.op === "event" ? "~>" : seg.op === "dependency" ? "--" : "->";
8527
+ const label = seg.label ? ` "${seg.label}"` : "";
8528
+ if (i === 0) {
8529
+ parts.push(`${seg.from} ${opSymbol} ${seg.to}${label}`);
8530
+ } else {
8531
+ parts.push(`${opSymbol} ${seg.to}${label}`);
8532
+ }
8533
+ }
8534
+ return parts.join(" ");
8535
+ }
8536
+ case "show": {
8537
+ const stagger = cue.stagger ? cue.stagger < 1 ? ` stagger=${Math.round(cue.stagger * 1e3)}ms` : ` stagger=${cue.stagger}s` : "";
8538
+ return `show ${cue.targets.join(" ")}${stagger}`;
8539
+ }
8540
+ case "hide":
8541
+ return `hide ${cue.targets.join(" ")}`;
8542
+ case "glow": {
8543
+ const col = cue.color ? ` color=${cue.color}` : "";
8544
+ const str = cue.strength ? ` strength=${cue.strength}` : "";
8545
+ return `glow ${cue.targets.join(" ")}${col}${str}`;
8546
+ }
8547
+ case "focus": {
8548
+ const zoom = cue.zoom ? ` zoom=${cue.zoom}` : "";
8549
+ return `focus ${cue.targets.join(" ")}${zoom}`;
8550
+ }
8551
+ case "frame": {
8552
+ const zoom = cue.zoom ? ` zoom=${cue.zoom}` : "";
8553
+ return `frame ${cue.targets.join(" ")}${zoom}`;
8554
+ }
8555
+ case "parallel":
8556
+ return (cue.cues || []).map(serializeDriftCue).join(" & ");
8557
+ default:
8558
+ return "";
8559
+ }
8560
+ }
8561
+ function autoHealArchitectureDrift(ast, report, existingFiles = []) {
8562
+ const cleanExisting = existingFiles.map((f) => f.replace(/^[./\\]+/, ""));
8563
+ const clonedNodes = JSON.parse(JSON.stringify(ast.nodes || {}));
8564
+ const healedMappings = [];
8565
+ let healedAnchorCount = 0;
8566
+ for (const broken of report.brokenAnchors) {
8567
+ const node = clonedNodes[broken.nodeId];
8568
+ if (!node) continue;
8569
+ const [cleanPath, lineSuffix] = broken.declaredPath.split("#");
8570
+ const oldPath = cleanPath.replace(/^[./\\]+/, "");
8571
+ const lineTag = lineSuffix ? `#${lineSuffix}` : "#L1";
8572
+ const baseName = oldPath.split("/").pop() || oldPath;
8573
+ let bestMatch = null;
8574
+ let minDistance = Infinity;
8575
+ for (const cand of cleanExisting) {
8576
+ const candBase = cand.split("/").pop() || cand;
8577
+ const oldDir = oldPath.includes("/") ? oldPath.substring(0, oldPath.lastIndexOf("/")) : "";
8578
+ const candDir = cand.includes("/") ? cand.substring(0, cand.lastIndexOf("/")) : "";
8579
+ let dist = levenshteinDistance(oldPath.toLowerCase(), cand.toLowerCase());
8580
+ if (oldDir && oldDir === candDir) {
8581
+ dist = Math.min(dist, levenshteinDistance(baseName.toLowerCase(), candBase.toLowerCase()));
8582
+ }
8583
+ if (dist < minDistance && (dist <= 6 || oldDir && oldDir === candDir)) {
8584
+ minDistance = dist;
8585
+ bestMatch = cand;
8586
+ }
8587
+ }
8588
+ if (bestMatch) {
8589
+ const newPath = `${bestMatch}${lineTag}`;
8590
+ node.props = node.props || {};
8591
+ delete node.props["src"];
8592
+ node.props["@src"] = newPath;
8593
+ healedAnchorCount++;
8594
+ healedMappings.push({
8595
+ nodeId: broken.nodeId,
8596
+ oldPath: broken.declaredPath,
8597
+ newPath
8598
+ });
8599
+ }
8600
+ }
8601
+ let addedServiceCount = 0;
8602
+ for (const orphan of report.orphanCodeServices) {
8603
+ if (!clonedNodes[orphan.suggestedId]) {
8604
+ clonedNodes[orphan.suggestedId] = {
8605
+ id: orphan.suggestedId,
8606
+ label: orphan.suggestedId,
8607
+ kind: orphan.suggestedKind,
8608
+ line: 1,
8609
+ props: {
8610
+ "@src": `${orphan.discoveredPath}#L1`
8611
+ }
8612
+ };
8613
+ addedServiceCount++;
8614
+ }
8615
+ }
8616
+ const healedAst = {
8617
+ ...ast,
8618
+ nodes: clonedNodes
8619
+ };
8620
+ const lines = [];
8621
+ lines.push(`scene "${ast.meta?.title || "Architecture Diagram"}" theme=midnight`);
8622
+ lines.push(`layout LR`);
8623
+ lines.push(``);
8624
+ for (const node of Object.values(clonedNodes)) {
8625
+ const rawSrc = node.props?.["@src"] || node.props?.["src"];
8626
+ const srcProp = rawSrc ? ` @src="${rawSrc}"` : "";
8627
+ const iconProp = node.props?.["icon"] ? ` icon=${node.props["icon"]}` : "";
8628
+ lines.push(`${node.kind || "service"} ${node.id} "${node.label || node.id}"${iconProp}${srcProp}`);
8629
+ }
8630
+ if (ast.groups && Object.keys(ast.groups).length > 0) {
8631
+ lines.push(``);
8632
+ for (const group of Object.values(ast.groups)) {
8633
+ const label = group.label ? ` "${group.label}"` : "";
8634
+ lines.push(`group ${group.id}${label}: ${group.members.join(" ")}`);
8635
+ }
8636
+ }
8637
+ if (ast.beats && ast.beats.length > 0) {
8638
+ for (const beat of ast.beats) {
8639
+ lines.push(``);
8640
+ const beatLabel = beat.label ? ` "${beat.label}"` : "";
8641
+ lines.push(`beat ${beat.name}${beatLabel}:`);
8642
+ for (const cue of beat.cues) {
8643
+ const serialized = serializeDriftCue(cue);
8644
+ if (serialized) lines.push(` ${serialized}`);
8645
+ }
8646
+ }
8647
+ } else {
8648
+ lines.push(``);
8649
+ lines.push(`beat initial_flow "1. System Flow & Connectivity":`);
8650
+ lines.push(` show $nodes stagger=50ms`);
8651
+ if (ast.edges && ast.edges.length > 0) {
8652
+ for (const edge of ast.edges) {
8653
+ const label = edge.label ? ` "${edge.label}"` : "";
8654
+ let op = "->";
8655
+ if (edge.kind === "event") op = "~>";
8656
+ else if (edge.kind === "response") op = "<-";
8657
+ else if (edge.kind === "dependency") op = "--";
8658
+ lines.push(` ${edge.from} ${op} ${edge.to}${label}`);
8659
+ }
8660
+ }
8661
+ }
8662
+ return {
8663
+ healedAst,
8664
+ healedMarkdyScript: lines.join("\n") + "\n",
8665
+ healedAnchorCount,
8666
+ addedServiceCount,
8667
+ healedMappings
8668
+ };
8669
+ }
6076
8670
  export {
8671
+ ARCHITECTURE_RECIPES,
6077
8672
  ARCH_RULE_PRESETS,
6078
8673
  BEAT_CUE_KEYWORDS,
6079
8674
  CUE_ALIASES,
@@ -6090,9 +8685,14 @@ export {
6090
8685
  TECHNICAL_NODE_KINDS,
6091
8686
  TECHNICAL_NODE_TYPES,
6092
8687
  THEMES,
8688
+ VECTOR_SYMBOLS,
6093
8689
  VISUAL_PRIMITIVE_TYPES,
8690
+ allocatePortLanes,
6094
8691
  analyzeAndBuildRepairPrompt,
8692
+ analyzeC4Model,
6095
8693
  applyPlayerSetting,
8694
+ autoHealArchitectureDrift,
8695
+ buildSmoothSvgPath,
6096
8696
  canonicalNodeKind,
6097
8697
  classifyTechnology,
6098
8698
  compile,
@@ -6101,27 +8701,44 @@ export {
6101
8701
  computeAdaptiveDimensions,
6102
8702
  damerauLevenshteinDistance,
6103
8703
  decompressMarkdyFromUrlHash,
8704
+ detectArchitectureDrift,
6104
8705
  diagnoseMarkdyCode,
6105
8706
  diffDiagramASTs,
8707
+ exportC4LevelViews,
8708
+ extractDiagramCodeAnchors,
6106
8709
  extractDiagramContext,
8710
+ filterC4Hierarchy,
6107
8711
  findClosestMatch,
6108
8712
  formatScene,
8713
+ generateC4Storyboard,
6109
8714
  generateThemeFromBrand,
8715
+ getArchitectureRecipe,
6110
8716
  getArchitectureSuggestions,
6111
8717
  getBoxPortPosition,
6112
8718
  getIntelliCodeCompletions,
6113
8719
  humanizeId,
8720
+ inferNodeC4Level,
8721
+ listArchitectureRecipes,
8722
+ listAvailableSymbols,
6114
8723
  listOutputPresets,
6115
8724
  nodeRole,
6116
8725
  parse,
6117
8726
  parseAndCompile,
8727
+ parseCodeAnchor,
6118
8728
  predictNextLineSuggestion,
8729
+ recommendArchitecturePattern,
8730
+ renderSymbolSvg,
6119
8731
  repairMarkdyCode,
6120
8732
  resolveArchitectureConfig,
6121
8733
  resolveOutputPreset,
6122
8734
  resolvePlayer,
6123
8735
  resolveTheme,
8736
+ resolveVectorSymbol,
6124
8737
  routeOrthogonalEdge,
6125
8738
  selectOptimalPorts,
6126
- validateArchitecture
8739
+ synthesizeCustomRecipe,
8740
+ validateArchitecture,
8741
+ validateC4Containment,
8742
+ verifyCodeAnchorsWithReader,
8743
+ verifyDiagramQuality
6127
8744
  };