@markdy/mcp-server 1.0.28 → 1.0.30

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
@@ -717,6 +717,289 @@ function canonicalNodeKind(kind) {
717
717
  return NODE_ALIASES[kind] ?? kind;
718
718
  }
719
719
 
720
+ // ../core/src/themes.ts
721
+ var ROLE_COLORS = {
722
+ compute: "#38bdf8",
723
+ code: "#38bdf8",
724
+ client: "#f59e0b",
725
+ data: "#22c55e",
726
+ messaging: "#a78bfa",
727
+ network: "#60a5fa",
728
+ platform: "#c084fc",
729
+ security: "#fb7185",
730
+ delivery: "#facc15",
731
+ observability: "#2dd4bf",
732
+ flow: "#e879f9",
733
+ distributed: "#94a3b8"
734
+ };
735
+ var EDGE_COLORS = {
736
+ request: "#38bdf8",
737
+ response: "#a78bfa",
738
+ event: "#f59e0b",
739
+ dependency: "#64748b"
740
+ };
741
+ var THEMES = {
742
+ midnight: {
743
+ name: "midnight",
744
+ canvas: "#070d18",
745
+ surface: "#0e1a2c",
746
+ surfaceRaised: "#152538",
747
+ border: "#28384f",
748
+ text: "#f4f7fb",
749
+ textMuted: "#93a4bb",
750
+ gridMinor: "rgba(148, 163, 184, 0.10)",
751
+ gridMajor: "rgba(148, 163, 184, 0.16)",
752
+ vignette: "rgba(2, 6, 23, 0.7)",
753
+ accent: "#38bdf8",
754
+ nodeSurface: "#111f33",
755
+ nodeSurfaceRaised: "#1a2c44",
756
+ hairline: "rgba(148, 163, 184, 0.16)",
757
+ shadow: "rgba(2, 6, 23, 0.6)",
758
+ labelPlate: "#0f1c2e",
759
+ roles: { ...ROLE_COLORS },
760
+ edges: { ...EDGE_COLORS }
761
+ },
762
+ paper: {
763
+ name: "paper",
764
+ canvas: "#f6f8fc",
765
+ surface: "#ffffff",
766
+ surfaceRaised: "#f0f4f9",
767
+ border: "#cbd5e1",
768
+ text: "#0f1c2e",
769
+ textMuted: "#475569",
770
+ gridMinor: "rgba(15, 23, 42, 0.06)",
771
+ gridMajor: "rgba(15, 23, 42, 0.10)",
772
+ vignette: "rgba(226, 232, 240, 0.6)",
773
+ accent: "#0284c7",
774
+ nodeSurface: "#ffffff",
775
+ nodeSurfaceRaised: "#f7fafd",
776
+ hairline: "rgba(15, 23, 42, 0.10)",
777
+ shadow: "rgba(15, 23, 42, 0.14)",
778
+ labelPlate: "#ffffff",
779
+ roles: { ...ROLE_COLORS },
780
+ edges: { ...EDGE_COLORS },
781
+ series: ["#7c8f6f", "#5e7a9b", "#b8915a", "#9c6b50", "#6e6479"]
782
+ },
783
+ blueprint: {
784
+ name: "blueprint",
785
+ canvas: "#0a1a3a",
786
+ surface: "#10214a",
787
+ surfaceRaised: "#182c5c",
788
+ border: "#2c477f",
789
+ text: "#eaf1ff",
790
+ textMuted: "#9fb6e0",
791
+ gridMinor: "rgba(125, 170, 255, 0.12)",
792
+ gridMajor: "rgba(125, 170, 255, 0.2)",
793
+ vignette: "rgba(4, 12, 34, 0.72)",
794
+ accent: "#5ba3ff",
795
+ nodeSurface: "#122452",
796
+ nodeSurfaceRaised: "#1b3068",
797
+ hairline: "rgba(125, 170, 255, 0.18)",
798
+ shadow: "rgba(3, 10, 30, 0.6)",
799
+ labelPlate: "#10214a",
800
+ roles: { ...ROLE_COLORS },
801
+ edges: { ...EDGE_COLORS }
802
+ },
803
+ graphite: {
804
+ name: "graphite",
805
+ canvas: "#111317",
806
+ surface: "#191c22",
807
+ surfaceRaised: "#22262e",
808
+ border: "#343a45",
809
+ text: "#eef1f5",
810
+ textMuted: "#9aa3b1",
811
+ gridMinor: "rgba(160, 170, 185, 0.08)",
812
+ gridMajor: "rgba(160, 170, 185, 0.14)",
813
+ vignette: "rgba(0, 0, 0, 0.6)",
814
+ accent: "#2dd4bf",
815
+ nodeSurface: "#1c2028",
816
+ nodeSurfaceRaised: "#262b34",
817
+ hairline: "rgba(160, 170, 185, 0.14)",
818
+ shadow: "rgba(0, 0, 0, 0.55)",
819
+ labelPlate: "#191c22",
820
+ roles: { ...ROLE_COLORS },
821
+ edges: { ...EDGE_COLORS }
822
+ },
823
+ editorial: {
824
+ name: "editorial",
825
+ canvas: "#fafafa",
826
+ surface: "#ffffff",
827
+ surfaceRaised: "#f8fafc",
828
+ border: "#e2e8f0",
829
+ text: "#0f172a",
830
+ textMuted: "#475569",
831
+ paper: "#fafafa",
832
+ ink: "#0f172a",
833
+ muted: "#6b7280",
834
+ rule: "#e2e8f0",
835
+ soft: "#94a3b8",
836
+ link: "#047857",
837
+ gridMinor: "rgba(15, 23, 42, 0.06)",
838
+ gridMajor: "rgba(15, 23, 42, 0.10)",
839
+ vignette: "rgba(226, 232, 240, 0.5)",
840
+ accent: "#047857",
841
+ accentTint: "rgba(4, 120, 87, 0.08)",
842
+ nodeSurface: "#ffffff",
843
+ nodeSurfaceRaised: "#f8fafc",
844
+ hairline: "#e2e8f0",
845
+ shadow: "rgba(15, 23, 42, 0.08)",
846
+ labelPlate: "#fafafa",
847
+ flatCards: true,
848
+ roles: { ...ROLE_COLORS, compute: "#0f172a", client: "#64748b", data: "#64748b" },
849
+ edges: {
850
+ request: "#334155",
851
+ response: "#64748b",
852
+ event: "#047857",
853
+ dependency: "#94a3b8"
854
+ },
855
+ fonts: {
856
+ title: "Georgia, Times New Roman, serif",
857
+ nodeName: "ui-sans-serif, system-ui, sans-serif",
858
+ mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
859
+ },
860
+ radiusMd: 6,
861
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 },
862
+ series: ["#7c8f6f", "#5e7a9b", "#b8915a", "#9c6b50", "#6e6479"]
863
+ },
864
+ nebula: {
865
+ name: "nebula",
866
+ canvas: "#090b1a",
867
+ surface: "#111735",
868
+ surfaceRaised: "#1a2250",
869
+ border: "rgba(167, 139, 250, 0.28)",
870
+ text: "#eef2ff",
871
+ textMuted: "#a5b4fc",
872
+ paper: "#090b1a",
873
+ ink: "#eef2ff",
874
+ muted: "#818cf8",
875
+ rule: "rgba(129, 140, 248, 0.32)",
876
+ soft: "#67e8f9",
877
+ link: "#67e8f9",
878
+ gridMinor: "rgba(129, 140, 248, 0.08)",
879
+ gridMajor: "rgba(103, 232, 249, 0.16)",
880
+ vignette: "rgba(3, 5, 18, 0.86)",
881
+ accent: "#c4b5fd",
882
+ accentTint: "rgba(196, 181, 253, 0.14)",
883
+ nodeSurface: "#111735",
884
+ nodeSurfaceRaised: "#202a5b",
885
+ hairline: "rgba(196, 181, 253, 0.32)",
886
+ shadow: "rgba(3, 5, 18, 0.72)",
887
+ labelPlate: "#12183b",
888
+ roles: {
889
+ ...ROLE_COLORS,
890
+ compute: "#67e8f9",
891
+ client: "#f9a8d4",
892
+ data: "#a7f3d0",
893
+ network: "#93c5fd",
894
+ flow: "#c4b5fd"
895
+ },
896
+ edges: {
897
+ request: "#67e8f9",
898
+ response: "#c4b5fd",
899
+ event: "#f9a8d4",
900
+ dependency: "#818cf8"
901
+ },
902
+ fonts: {
903
+ title: "Georgia, Times New Roman, serif",
904
+ nodeName: "ui-sans-serif, system-ui, sans-serif",
905
+ mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
906
+ },
907
+ radiusMd: 12,
908
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
909
+ },
910
+ terminal: {
911
+ name: "terminal",
912
+ canvas: "#0a0a0a",
913
+ surface: "#141414",
914
+ surfaceRaised: "#1e1e1e",
915
+ border: "#2b2b2b",
916
+ text: "#f5f5f5",
917
+ textMuted: "#9a9a9a",
918
+ paper: "#141414",
919
+ ink: "#f5f5f5",
920
+ muted: "#9a9a9a",
921
+ rule: "#2b2b2b",
922
+ soft: "#5c5c5c",
923
+ link: "#ff5a36",
924
+ gridMinor: "rgba(255, 255, 255, 0.04)",
925
+ gridMajor: "rgba(255, 255, 255, 0.08)",
926
+ vignette: "rgba(0, 0, 0, 0.8)",
927
+ accent: "#ff5a36",
928
+ accentTint: "rgba(255, 90, 54, 0.15)",
929
+ nodeSurface: "#141414",
930
+ nodeSurfaceRaised: "#222222",
931
+ hairline: "#333333",
932
+ shadow: "rgba(0, 0, 0, 0.8)",
933
+ labelPlate: "#141414",
934
+ flatCards: true,
935
+ roles: {
936
+ ...ROLE_COLORS,
937
+ compute: "#ff5a36",
938
+ client: "#f5f5f5",
939
+ data: "#9a9a9a",
940
+ network: "#38bdf8"
941
+ },
942
+ edges: {
943
+ request: "#ff5a36",
944
+ response: "#9a9a9a",
945
+ event: "#facc15",
946
+ dependency: "#5c5c5c"
947
+ },
948
+ fonts: {
949
+ title: "ui-monospace, SFMono-Regular, Menlo, monospace",
950
+ nodeName: "ui-monospace, SFMono-Regular, Menlo, monospace",
951
+ mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
952
+ },
953
+ radiusMd: 6,
954
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
955
+ },
956
+ sketchy: {
957
+ name: "sketchy",
958
+ canvas: "#fbfaf8",
959
+ surface: "#ffffff",
960
+ surfaceRaised: "#f6f3eb",
961
+ border: "#2d3142",
962
+ text: "#2d3142",
963
+ textMuted: "#4f5d75",
964
+ paper: "#fbfaf8",
965
+ ink: "#2d3142",
966
+ muted: "#4f5d75",
967
+ rule: "rgba(45, 49, 66, 0.18)",
968
+ soft: "#7a8399",
969
+ link: "#eb6c36",
970
+ gridMinor: "rgba(45, 49, 66, 0.05)",
971
+ gridMajor: "rgba(45, 49, 66, 0.08)",
972
+ vignette: "rgba(0, 0, 0, 0.06)",
973
+ accent: "#eb6c36",
974
+ accentTint: "rgba(235, 108, 54, 0.10)",
975
+ nodeSurface: "#ffffff",
976
+ nodeSurfaceRaised: "#fbfaf8",
977
+ hairline: "rgba(45, 49, 66, 0.35)",
978
+ shadow: "rgba(45, 49, 66, 0.08)",
979
+ labelPlate: "#ffffff",
980
+ flatCards: true,
981
+ roles: {
982
+ ...ROLE_COLORS,
983
+ compute: "#2d3142",
984
+ client: "#4f5d75",
985
+ data: "#4f5d75"
986
+ },
987
+ edges: {
988
+ request: "#2d3142",
989
+ response: "#4f5d75",
990
+ event: "#eb6c36",
991
+ dependency: "#7a8399"
992
+ },
993
+ fonts: {
994
+ title: "Georgia, Times New Roman, serif",
995
+ nodeName: "ui-sans-serif, system-ui, sans-serif",
996
+ mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
997
+ },
998
+ radiusMd: 4,
999
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
1000
+ }
1001
+ };
1002
+
720
1003
  // ../core/src/parser.ts
721
1004
  var ParseError = class extends Error {
722
1005
  line;
@@ -2074,56 +2357,1437 @@ function classifyTechnology(id, label = "") {
2074
2357
  };
2075
2358
  }
2076
2359
 
2077
- // ../core/src/ai-healing.ts
2078
- function analyzeAndBuildRepairPrompt(sourceCode) {
2079
- const syntaxErrors = [];
2080
- let ast = null;
2360
+ // ../core/src/syntax-diagnostics.ts
2361
+ function damerauLevenshteinDistance(a, b) {
2362
+ const al = a.length;
2363
+ const bl = b.length;
2364
+ if (al === 0) return bl;
2365
+ if (bl === 0) return al;
2366
+ const matrix = [];
2367
+ for (let i = 0; i <= al; i++) {
2368
+ matrix[i] = [i];
2369
+ }
2370
+ for (let j = 0; j <= bl; j++) {
2371
+ matrix[0][j] = j;
2372
+ }
2373
+ for (let i = 1; i <= al; i++) {
2374
+ for (let j = 1; j <= bl; j++) {
2375
+ const cost = a[i - 1].toLowerCase() === b[j - 1].toLowerCase() ? 0 : 1;
2376
+ let min = Math.min(
2377
+ matrix[i - 1][j] + 1,
2378
+ // deletion
2379
+ matrix[i][j - 1] + 1,
2380
+ // insertion
2381
+ matrix[i - 1][j - 1] + cost
2382
+ // substitution
2383
+ );
2384
+ if (i > 1 && j > 1 && a[i - 1].toLowerCase() === b[j - 2].toLowerCase() && a[i - 2].toLowerCase() === b[j - 1].toLowerCase()) {
2385
+ min = Math.min(min, matrix[i - 2][j - 2] + cost);
2386
+ }
2387
+ matrix[i][j] = min;
2388
+ }
2389
+ }
2390
+ return matrix[al][bl];
2391
+ }
2392
+ var COMMON_CONTRACTIONS = {
2393
+ svc: "service",
2394
+ srv: "service",
2395
+ gw: "gateway",
2396
+ db: "database",
2397
+ app: "application",
2398
+ msg: "message",
2399
+ repo: "repository",
2400
+ cfg: "config",
2401
+ auth: "authenticator",
2402
+ mgr: "manager",
2403
+ fn: "function"
2404
+ };
2405
+ function splitIdentifierIntoTokens(s) {
2406
+ return s.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_\-.]+/g, " ").toLowerCase().trim().split(/\s+/).filter(Boolean);
2407
+ }
2408
+ function expandContractions(s) {
2409
+ const norm = s.toLowerCase().trim();
2410
+ const tokens = splitIdentifierIntoTokens(s);
2411
+ if (tokens.length === 0) return norm;
2412
+ const expandedTokens = tokens.map((t) => COMMON_CONTRACTIONS[t] ?? t);
2413
+ let res = expandedTokens.join("");
2414
+ if (tokens.length === 1) {
2415
+ for (const [k, v] of Object.entries(COMMON_CONTRACTIONS)) {
2416
+ if (k.length >= 2 && norm.length > k.length + 2 && norm.endsWith(k)) {
2417
+ res = norm.slice(0, -k.length) + v;
2418
+ break;
2419
+ }
2420
+ }
2421
+ }
2422
+ return res;
2423
+ }
2424
+ function findClosestMatch(word, candidates, maxDistance = 4) {
2425
+ const normalized = word.toLowerCase().trim();
2426
+ if (!normalized || !/^[a-zA-Z_][\w.-]*$/.test(normalized)) return null;
2427
+ const expandedWord = expandContractions(normalized);
2428
+ let bestMatch = null;
2429
+ let bestDistance = Infinity;
2430
+ const wordConsonants = normalized.replace(/[aeiou_-]/g, "");
2431
+ for (const candidate of candidates) {
2432
+ const candNorm = candidate.toLowerCase().trim();
2433
+ const expandedCand = expandContractions(candNorm);
2434
+ if (candNorm === normalized || expandedCand === expandedWord) {
2435
+ return { match: candidate, distance: 0 };
2436
+ }
2437
+ if (candNorm.startsWith(normalized) || normalized.startsWith(candNorm) || expandedCand.startsWith(expandedWord) || expandedWord.startsWith(expandedCand)) {
2438
+ const dist2 = Math.abs(candNorm.length - normalized.length);
2439
+ if (dist2 < bestDistance) {
2440
+ bestDistance = dist2;
2441
+ bestMatch = candidate;
2442
+ continue;
2443
+ }
2444
+ }
2445
+ if (wordConsonants.length >= 3) {
2446
+ const candConsonants = candNorm.replace(/[aeiou_-]/g, "");
2447
+ if (candConsonants.startsWith(wordConsonants) || wordConsonants.startsWith(candConsonants)) {
2448
+ const dist2 = 1;
2449
+ if (dist2 < bestDistance) {
2450
+ bestDistance = dist2;
2451
+ bestMatch = candidate;
2452
+ continue;
2453
+ }
2454
+ }
2455
+ }
2456
+ const dist = Math.min(
2457
+ damerauLevenshteinDistance(normalized, candNorm),
2458
+ damerauLevenshteinDistance(expandedWord, expandedCand)
2459
+ );
2460
+ const dynamicMax = Math.max(2, Math.min(maxDistance, Math.floor(Math.max(candidate.length, word.length) * 0.5)));
2461
+ if (dist <= dynamicMax && dist < bestDistance) {
2462
+ bestDistance = dist;
2463
+ bestMatch = candidate;
2464
+ }
2465
+ }
2466
+ return bestMatch ? { match: bestMatch, distance: bestDistance } : null;
2467
+ }
2468
+ var TOP_LEVEL_KEYWORDS = [
2469
+ "scene",
2470
+ "layout",
2471
+ "player",
2472
+ "var",
2473
+ "group",
2474
+ "beat",
2475
+ "style",
2476
+ "pattern",
2477
+ "edge",
2478
+ "annotation",
2479
+ "use"
2480
+ ];
2481
+ var CUE_KEYWORDS = [
2482
+ "show",
2483
+ "hide",
2484
+ "glow",
2485
+ "focus",
2486
+ "frame",
2487
+ "use",
2488
+ "pulse",
2489
+ "highlight",
2490
+ "emphasize"
2491
+ ];
2492
+ var LAYOUT_DIRECTIONS = ["LR", "RL", "TB", "BT"];
2493
+ var THEME_NAMES = Object.keys(THEMES);
2494
+ var ALL_NODE_KINDS = Array.from(
2495
+ /* @__PURE__ */ new Set([...Array.from(NODE_KINDS), ...Object.keys(NODE_ALIASES)])
2496
+ );
2497
+ var FOREIGN_SYNTAX_PATTERNS = [
2498
+ {
2499
+ type: "Mermaid",
2500
+ pattern: /^\s*(graph\s+(TB|TD|BT|RL|LR)|flowchart\s+(TB|TD|BT|RL|LR)|classDiagram|sequenceDiagram|erDiagram|stateDiagram|pie\s+title|gitGraph)/i,
2501
+ hint: `Mermaid syntax detected. Convert using 'transpile_to_markdy(source, format="mermaid")'.`
2502
+ },
2503
+ {
2504
+ type: "PlantUML",
2505
+ pattern: /^\s*(@startuml|@startmindmap|@startgantt|@startwbs)/i,
2506
+ hint: "PlantUML syntax detected. Markdy uses declarative 'scene', node declarations, and 'beat' blocks."
2507
+ },
2508
+ {
2509
+ type: "Graphviz DOT",
2510
+ pattern: /^\s*(digraph|strict\s+digraph|graph)\s+\w*\s*\{/i,
2511
+ hint: `Graphviz DOT syntax detected. Markdy uses 'scene', '<kind> <Id> ["Label"]', and 'beat' blocks.`
2512
+ },
2513
+ {
2514
+ type: "D2",
2515
+ pattern: /^\s*(direction\s*:|vars\s*:|classes\s*:)/i,
2516
+ hint: "D2 syntax detected. Markdy uses 'layout LR|TB' and 'beat <id>:' blocks."
2517
+ }
2518
+ ];
2519
+ var INVALID_FLOW_OP_MAP = {
2520
+ "-->": "->",
2521
+ "->>": "->",
2522
+ "==>": "->",
2523
+ "<--": "<-",
2524
+ "<<-": "<-",
2525
+ "<==": "<-",
2526
+ "~~>": "~>",
2527
+ "~>>": "~>",
2528
+ "---": "--",
2529
+ "--->": "->",
2530
+ "-.-": "--",
2531
+ "-..->": "~>",
2532
+ ">--": "->",
2533
+ ">-": "->"
2534
+ };
2535
+ function diagnoseMarkdyCode(code, options = {}) {
2536
+ const issues = [];
2537
+ const lines = code.replace(/\r\n/g, "\n").split("\n");
2538
+ const declaredNodes = /* @__PURE__ */ new Set();
2539
+ const referencedNodes = /* @__PURE__ */ new Set();
2540
+ const checkArchitecture = options.checkArchitecture ?? true;
2541
+ for (const foreign of FOREIGN_SYNTAX_PATTERNS) {
2542
+ if (foreign.pattern.test(code)) {
2543
+ issues.push({
2544
+ line: 1,
2545
+ severity: "error",
2546
+ code: "FOREIGN_DIAGRAM_SYNTAX",
2547
+ message: `${foreign.type} syntax detected instead of MarkdyScript.`,
2548
+ snippet: lines.slice(0, 3).join("\n"),
2549
+ suggestion: foreign.hint,
2550
+ ruleExplanation: `MarkdyScript diagrams begin with 'scene [theme=paper layout=LR]', followed by node declarations ('<kind> <Id> ["Label"]') and dynamic 'beat' blocks.`
2551
+ });
2552
+ break;
2553
+ }
2554
+ }
2555
+ for (let i = 0; i < lines.length; i++) {
2556
+ const line = lines[i].trim();
2557
+ if (!line || line.startsWith("//") || line.startsWith("#")) continue;
2558
+ const nodeMatch = line.match(/^(\w[\w.-]*)\s+(\w[\w.-]*)/);
2559
+ if (nodeMatch) {
2560
+ const head = nodeMatch[1].toLowerCase();
2561
+ const id = nodeMatch[2];
2562
+ if (!TOP_LEVEL_KEYWORDS.includes(head) && !CUE_KEYWORDS.includes(head) && !["show", "hide", "glow", "focus", "frame"].includes(head)) {
2563
+ declaredNodes.add(id);
2564
+ }
2565
+ }
2566
+ }
2567
+ let insideBeat = false;
2568
+ let insideGroup = false;
2569
+ let insidePattern = false;
2570
+ let beatIndent = 0;
2571
+ for (let idx = 0; idx < lines.length; idx++) {
2572
+ const rawLine = lines[idx];
2573
+ const lineNo = idx + 1;
2574
+ const trimmed = rawLine.trim();
2575
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#")) {
2576
+ continue;
2577
+ }
2578
+ const indent = rawLine.match(/^\s*/)?.[0].length ?? 0;
2579
+ if (insideBeat && indent <= beatIndent && !trimmed.startsWith("&")) {
2580
+ insideBeat = false;
2581
+ }
2582
+ if (insideGroup && indent === 0) {
2583
+ insideGroup = false;
2584
+ }
2585
+ if (insidePattern && indent === 0) {
2586
+ insidePattern = false;
2587
+ }
2588
+ const tokens = trimmed.split(/\s+/);
2589
+ const firstWord = tokens[0];
2590
+ const firstWordLower = firstWord.toLowerCase();
2591
+ if (firstWordLower.startsWith("scen") || firstWordLower === "secne" || firstWordLower === "scence") {
2592
+ if (firstWord !== "scene") {
2593
+ issues.push({
2594
+ line: lineNo,
2595
+ severity: "error",
2596
+ code: "TYPO_KEYWORD",
2597
+ message: `Unknown keyword '${firstWord}'. Did you mean 'scene'?`,
2598
+ snippet: trimmed,
2599
+ suggestion: `Replace '${firstWord}' with 'scene'.`,
2600
+ didYouMean: "scene",
2601
+ ruleExplanation: "Every Markdy diagram starts with a 'scene' directive: 'scene theme=paper width=1280 height=720'.",
2602
+ fix: { original: firstWord, replacement: "scene" }
2603
+ });
2604
+ }
2605
+ const themeMatch = trimmed.match(/\btheme=(\w+)/);
2606
+ if (themeMatch) {
2607
+ const themeVal = themeMatch[1].toLowerCase();
2608
+ if (!THEME_NAMES.includes(themeVal)) {
2609
+ const closeTheme = findClosestMatch(themeVal, THEME_NAMES);
2610
+ issues.push({
2611
+ line: lineNo,
2612
+ severity: "warning",
2613
+ code: "UNKNOWN_THEME_OR_PROPERTY",
2614
+ message: `Unknown theme '${themeMatch[1]}'. Available themes: ${THEME_NAMES.join(", ")}.`,
2615
+ snippet: trimmed,
2616
+ suggestion: closeTheme ? `Use theme '${closeTheme.match}'.` : `Choose from: ${THEME_NAMES.join(", ")}.`,
2617
+ didYouMean: closeTheme?.match,
2618
+ ruleExplanation: "Supported themes: paper, editorial, midnight, blueprint, graphite, nebula, sketchy, terminal.",
2619
+ fix: closeTheme ? { original: themeMatch[0], replacement: `theme=${closeTheme.match}` } : void 0
2620
+ });
2621
+ }
2622
+ }
2623
+ const layoutMatch = trimmed.match(/\blayout\s*=?\s*(\w+)/i);
2624
+ if (layoutMatch) {
2625
+ const layoutVal = layoutMatch[1].toUpperCase();
2626
+ if (!LAYOUT_DIRECTIONS.includes(layoutVal)) {
2627
+ issues.push({
2628
+ line: lineNo,
2629
+ severity: "warning",
2630
+ code: "UNKNOWN_THEME_OR_PROPERTY",
2631
+ message: `Invalid layout direction '${layoutMatch[1]}'. Must be one of: LR, RL, TB, BT.`,
2632
+ snippet: trimmed,
2633
+ suggestion: "Use layout LR (left-to-right) or TB (top-to-bottom).",
2634
+ didYouMean: "LR",
2635
+ ruleExplanation: "Layout directions must be LR (Left-to-Right), RL, TB (Top-to-Bottom), or BT.",
2636
+ fix: { original: layoutMatch[0], replacement: "layout LR" }
2637
+ });
2638
+ }
2639
+ }
2640
+ continue;
2641
+ }
2642
+ if (firstWordLower === "layput" || firstWordLower === "layour" || firstWordLower === "layuot" || firstWordLower === "directon") {
2643
+ issues.push({
2644
+ line: lineNo,
2645
+ severity: "error",
2646
+ code: "TYPO_KEYWORD",
2647
+ message: `Typo in layout directive '${firstWord}'. Did you mean 'layout'?`,
2648
+ snippet: trimmed,
2649
+ suggestion: `Change '${firstWord}' to 'layout'.`,
2650
+ didYouMean: "layout",
2651
+ ruleExplanation: "Use 'layout LR' or 'layout TB' to define the diagram orientation.",
2652
+ fix: { original: firstWord, replacement: "layout" }
2653
+ });
2654
+ continue;
2655
+ }
2656
+ if (firstWordLower.startsWith("beat") || firstWordLower === "bea" || firstWordLower === "beats") {
2657
+ insideBeat = true;
2658
+ beatIndent = indent;
2659
+ if (firstWord !== "beat") {
2660
+ issues.push({
2661
+ line: lineNo,
2662
+ severity: "error",
2663
+ code: "TYPO_KEYWORD",
2664
+ message: `Unknown keyword '${firstWord}'. Did you mean 'beat'?`,
2665
+ snippet: trimmed,
2666
+ suggestion: `Replace '${firstWord}' with 'beat'.`,
2667
+ didYouMean: "beat",
2668
+ ruleExplanation: `Storyboard narrative steps start with 'beat <id> ["Caption"]:' followed by indented cues.`,
2669
+ fix: { original: firstWord, replacement: "beat" }
2670
+ });
2671
+ }
2672
+ if (!trimmed.endsWith(":") && !trimmed.endsWith("{")) {
2673
+ issues.push({
2674
+ line: lineNo,
2675
+ severity: "error",
2676
+ code: "MISSING_COLON",
2677
+ message: `Missing colon ':' at the end of beat header.`,
2678
+ snippet: trimmed,
2679
+ suggestion: `Append ':' to the beat declaration: '${trimmed}:'`,
2680
+ ruleExplanation: "Every beat block must end with a colon ':', e.g. `beat checkout \"Process Payment\":`.",
2681
+ fix: { original: trimmed, replacement: `${trimmed}:` }
2682
+ });
2683
+ }
2684
+ continue;
2685
+ }
2686
+ if (firstWordLower === "group" || firstWordLower === "groop" || firstWordLower === "grp") {
2687
+ insideGroup = true;
2688
+ if (firstWord !== "group") {
2689
+ issues.push({
2690
+ line: lineNo,
2691
+ severity: "error",
2692
+ code: "TYPO_KEYWORD",
2693
+ message: `Typo in group keyword '${firstWord}'. Did you mean 'group'?`,
2694
+ snippet: trimmed,
2695
+ suggestion: `Change '${firstWord}' to 'group'.`,
2696
+ didYouMean: "group",
2697
+ fix: { original: firstWord, replacement: "group" }
2698
+ });
2699
+ }
2700
+ if (!trimmed.includes(":")) {
2701
+ issues.push({
2702
+ line: lineNo,
2703
+ severity: "error",
2704
+ code: "MISSING_COLON",
2705
+ message: `Missing colon ':' in group definition.`,
2706
+ snippet: trimmed,
2707
+ suggestion: `Add a colon before group members: e.g. 'group <id> "Label": Node1 Node2'`,
2708
+ ruleExplanation: 'Groups require a colon: `group clients "User Tier": WebApp MobileApp`.'
2709
+ });
2710
+ } else {
2711
+ const colonIdx = trimmed.indexOf(":");
2712
+ const membersRaw = trimmed.slice(colonIdx + 1).trim();
2713
+ if (membersRaw) {
2714
+ const members = membersRaw.split(/\s+/).filter((m) => m && !m.includes("=") && !m.startsWith("$") && /^[a-zA-Z_][\w.-]*$/.test(m));
2715
+ for (const mem of members) {
2716
+ referencedNodes.add(mem);
2717
+ if (declaredNodes.size > 0 && !declaredNodes.has(mem)) {
2718
+ const closest = findClosestMatch(mem, declaredNodes);
2719
+ issues.push({
2720
+ line: lineNo,
2721
+ severity: "warning",
2722
+ code: "UNDEFINED_NODE_REFERENCE",
2723
+ message: `Group references undefined node '${mem}'.${closest ? ` Did you mean '${closest.match}'?` : ""}`,
2724
+ snippet: trimmed,
2725
+ suggestion: closest ? `Replace '${mem}' with declared node '${closest.match}'.` : `Ensure node '${mem}' is declared before adding it to a group.`,
2726
+ didYouMean: closest?.match,
2727
+ fix: closest ? { original: mem, replacement: closest.match } : void 0
2728
+ });
2729
+ }
2730
+ }
2731
+ }
2732
+ }
2733
+ continue;
2734
+ }
2735
+ if (firstWordLower === "annotation") {
2736
+ const targetMatch = trimmed.match(/\btarget=([\w.-]+)/i);
2737
+ if (targetMatch) {
2738
+ const target = targetMatch[1];
2739
+ referencedNodes.add(target);
2740
+ if (declaredNodes.size > 0 && !declaredNodes.has(target)) {
2741
+ const closest = findClosestMatch(target, declaredNodes);
2742
+ issues.push({
2743
+ line: lineNo,
2744
+ severity: "warning",
2745
+ code: "UNDEFINED_NODE_REFERENCE",
2746
+ message: `Annotation targets undefined node '${target}'.${closest ? ` Did you mean '${closest.match}'?` : ""}`,
2747
+ snippet: trimmed,
2748
+ suggestion: closest ? `Replace target='${target}' with target='${closest.match}'.` : void 0,
2749
+ didYouMean: closest?.match,
2750
+ fix: closest ? { original: target, replacement: closest.match } : void 0
2751
+ });
2752
+ }
2753
+ }
2754
+ continue;
2755
+ }
2756
+ if (!insideBeat) {
2757
+ const isCueWord = CUE_KEYWORDS.includes(firstWordLower) || firstWordLower === "shwo" || firstWordLower === "fram" || firstWordLower === "focsu" || firstWordLower === "gloww";
2758
+ const isFlowLine = /(-->|->|<-|<--|~>|--)/.test(trimmed);
2759
+ if (isCueWord || isFlowLine) {
2760
+ issues.push({
2761
+ line: lineNo,
2762
+ severity: "error",
2763
+ code: "CUE_OUTSIDE_BEAT",
2764
+ message: `Action cue or flow line '${trimmed}' found at top-level outside a 'beat' block.`,
2765
+ snippet: trimmed,
2766
+ suggestion: `Wrap this line inside an indented beat block:
2767
+ beat main "System Flow":
2768
+ ${trimmed}`,
2769
+ ruleExplanation: "In MarkdyScript, top-level code is reserved for Directives, Nodes, and Groups. Animated flow transitions and visual cues (show, hide, glow, frame, focus) must reside inside a 'beat <name>:' block."
2770
+ });
2771
+ continue;
2772
+ }
2773
+ }
2774
+ for (const [invalidOp, validOp] of Object.entries(INVALID_FLOW_OP_MAP)) {
2775
+ if (trimmed.includes(invalidOp)) {
2776
+ issues.push({
2777
+ line: lineNo,
2778
+ severity: "error",
2779
+ code: "INVALID_FLOW_OPERATOR",
2780
+ message: `Invalid flow operator '${invalidOp}'. Did you mean '${validOp}'?`,
2781
+ snippet: trimmed,
2782
+ suggestion: `Replace '${invalidOp}' with '${validOp}'.`,
2783
+ didYouMean: validOp,
2784
+ ruleExplanation: "Markdy uses 4 clean flow operators: '->' (request/call), '<-' (response/return), '~>' (asynchronous/event), and '--' (static dependency).",
2785
+ fix: { original: invalidOp, replacement: validOp }
2786
+ });
2787
+ }
2788
+ }
2789
+ if (!insideBeat && !insideGroup && !insidePattern) {
2790
+ const match = trimmed.match(/^(\w[\w.-]*)\s+(\w[\w.-]*)(.*)$/);
2791
+ if (match) {
2792
+ const rawKind = match[1];
2793
+ const rawKindLower = rawKind.toLowerCase();
2794
+ const id = match[2];
2795
+ const rest = match[3].trim();
2796
+ if (!TOP_LEVEL_KEYWORDS.includes(rawKindLower)) {
2797
+ const canonical = canonicalNodeKind(rawKindLower);
2798
+ if (!NODE_KINDS.has(canonical)) {
2799
+ const matchKind = findClosestMatch(rawKindLower, ALL_NODE_KINDS);
2800
+ const resolvedKind = matchKind ? canonicalNodeKind(matchKind.match) : void 0;
2801
+ issues.push({
2802
+ line: lineNo,
2803
+ severity: "error",
2804
+ code: "TYPO_NODE_KIND",
2805
+ message: `Unknown node kind '${rawKind}'.${resolvedKind ? ` Did you mean '${resolvedKind}'?` : ""}`,
2806
+ snippet: trimmed,
2807
+ suggestion: resolvedKind ? `Change '${rawKind}' to '${resolvedKind}': '${resolvedKind} ${id} ...'` : `Use a valid node kind: service, database, cache, gateway, browser, mobile, worker, cloud, queue, storage, cluster, etc.`,
2808
+ didYouMean: resolvedKind,
2809
+ ruleExplanation: "Markdy requires semantic node kinds (e.g. browser, gateway, service, database, cache, queue, worker, cloud, storage, pod, cluster).",
2810
+ fix: resolvedKind ? { original: rawKind, replacement: resolvedKind } : void 0
2811
+ });
2812
+ }
2813
+ if (rest && !rest.startsWith('"') && !rest.startsWith("style=") && !rest.startsWith("icon=")) {
2814
+ const hasMultipleWords = rest.split(/\s+/).length > 1;
2815
+ if (hasMultipleWords && !rest.includes("=")) {
2816
+ issues.push({
2817
+ line: lineNo,
2818
+ severity: "error",
2819
+ code: "UNQUOTED_STRING_LABEL",
2820
+ message: `Unquoted multi-word label '${rest}'. String labels with spaces must be enclosed in double quotes.`,
2821
+ snippet: trimmed,
2822
+ suggestion: `Wrap the label in quotes: '${rawKind} ${id} "${rest}"'`,
2823
+ ruleExplanation: 'String labels containing spaces must be enclosed in double quotes `"..."`.',
2824
+ fix: { original: `${id} ${rest}`, replacement: `${id} "${rest}"` }
2825
+ });
2826
+ }
2827
+ }
2828
+ const quoteCount = (rest.match(/"/g) || []).length;
2829
+ if (quoteCount % 2 !== 0) {
2830
+ issues.push({
2831
+ line: lineNo,
2832
+ severity: "error",
2833
+ code: "UNTERMINATED_STRING",
2834
+ message: `Unterminated string quote in node declaration.`,
2835
+ snippet: trimmed,
2836
+ suggestion: `Add closing quote: '${trimmed}"'`,
2837
+ fix: { original: trimmed, replacement: `${trimmed}"` }
2838
+ });
2839
+ }
2840
+ }
2841
+ }
2842
+ }
2843
+ if (insideBeat) {
2844
+ if (trimmed.includes("->") || trimmed.includes("<-") || trimmed.includes("~>") || trimmed.includes("--")) {
2845
+ const cleanFlow = trimmed.replace(/"[^"]*"/g, " ");
2846
+ const flowTokens = cleanFlow.split(/->|<-|~>|--|&|\s+/).filter(Boolean);
2847
+ for (const token of flowTokens) {
2848
+ if (token.includes("=") || token.startsWith("$") || !/^[a-zA-Z_][\w.-]*$/.test(token) || ["and", "show", "hide", "glow", "focus", "frame", "pulse", "use"].includes(token.toLowerCase())) {
2849
+ continue;
2850
+ }
2851
+ referencedNodes.add(token);
2852
+ if (declaredNodes.size > 0 && !declaredNodes.has(token)) {
2853
+ const closest = findClosestMatch(token, declaredNodes);
2854
+ issues.push({
2855
+ line: lineNo,
2856
+ severity: "error",
2857
+ code: "UNDEFINED_NODE_REFERENCE",
2858
+ message: `Flow references undefined node '${token}'.${closest ? ` Did you mean '${closest.match}'?` : ""}`,
2859
+ snippet: trimmed,
2860
+ suggestion: closest ? `Replace '${token}' with declared node '${closest.match}'.` : `Declare node '${token}' at top of diagram before referencing it.`,
2861
+ didYouMean: closest?.match,
2862
+ ruleExplanation: "All nodes used in flow transitions must be declared beforehand (e.g. `service ${token}`).",
2863
+ fix: closest ? { original: token, replacement: closest.match } : void 0
2864
+ });
2865
+ }
2866
+ }
2867
+ }
2868
+ const cueMatch = trimmed.match(/^(show|hide|glow|focus|frame)\s+(.+)$/i);
2869
+ if (cueMatch) {
2870
+ const cueKind = cueMatch[1].toLowerCase();
2871
+ const targetsRaw = cueMatch[2].replace(/\b\w+=[^\s]+/g, "").trim();
2872
+ const targets = targetsRaw.split(/\s+/).filter((t) => t && !t.startsWith("$") && !t.startsWith('"') && /^[a-zA-Z_][\w.-]*$/.test(t));
2873
+ for (const target of targets) {
2874
+ referencedNodes.add(target);
2875
+ if (declaredNodes.size > 0 && !declaredNodes.has(target)) {
2876
+ const closest = findClosestMatch(target, declaredNodes);
2877
+ issues.push({
2878
+ line: lineNo,
2879
+ severity: "warning",
2880
+ code: "UNDEFINED_NODE_REFERENCE",
2881
+ message: `${cueKind} cue targets undefined node '${target}'.${closest ? ` Did you mean '${closest.match}'?` : ""}`,
2882
+ snippet: trimmed,
2883
+ suggestion: closest ? `Replace '${target}' with '${closest.match}'.` : `Ensure node '${target}' is declared before targeting it.`,
2884
+ didYouMean: closest?.match,
2885
+ fix: closest ? { original: target, replacement: closest.match } : void 0
2886
+ });
2887
+ }
2888
+ }
2889
+ }
2890
+ }
2891
+ }
2081
2892
  try {
2082
- ast = parse(sourceCode);
2893
+ const ast = parse(code);
2894
+ if (checkArchitecture) {
2895
+ const archViolations = validateArchitecture(ast);
2896
+ for (const v of archViolations) {
2897
+ issues.push({
2898
+ line: v.line ?? 1,
2899
+ severity: v.severity === "error" ? "error" : "warning",
2900
+ code: "ARCH_RULE_VIOLATION",
2901
+ message: `[${v.ruleName}] ${v.message}`,
2902
+ suggestion: `Refactor architecture to comply with Well-Architected governance: ${v.ruleName}.`,
2903
+ ruleExplanation: `Architecture Rule Preset: ${v.ruleName}`
2904
+ });
2905
+ }
2906
+ }
2907
+ for (const d of ast.diagnostics) {
2908
+ if (d.message.includes("flow cycle detected")) {
2909
+ issues.push({
2910
+ line: d.line,
2911
+ severity: "warning",
2912
+ code: "FLOW_CYCLE_RETURN_EDGE",
2913
+ message: d.message,
2914
+ suggestion: "Use '<-' for return/response edges instead of '->' or '~>'.",
2915
+ ruleExplanation: "Cycle Safety: Always use '<-' when returning calls to callers to prevent cyclical layout rank collisions."
2916
+ });
2917
+ } else {
2918
+ const isDuplicate = issues.some((iss) => iss.line === d.line && iss.message === d.message);
2919
+ if (!isDuplicate) {
2920
+ issues.push({
2921
+ line: d.line,
2922
+ severity: d.severity === "error" ? "error" : "warning",
2923
+ code: "SYNTAX_ERROR",
2924
+ message: d.message
2925
+ });
2926
+ }
2927
+ }
2928
+ }
2083
2929
  } catch (err) {
2084
- syntaxErrors.push(err instanceof Error ? err.message : String(err));
2930
+ const errMsg = err instanceof Error ? err.message : String(err);
2931
+ const lineMatch = errMsg.match(/line\s+(\d+):\s*(.*)/);
2932
+ const errLine = lineMatch ? Number(lineMatch[1]) : 1;
2933
+ const cleanMsg = lineMatch ? lineMatch[2] : errMsg;
2934
+ const alreadyCaptured = issues.some((iss) => iss.line === errLine && iss.severity === "error");
2935
+ if (!alreadyCaptured) {
2936
+ issues.push({
2937
+ line: errLine,
2938
+ severity: "error",
2939
+ code: "SYNTAX_ERROR",
2940
+ message: cleanMsg,
2941
+ snippet: lines[errLine - 1] ?? "",
2942
+ suggestion: "Review Markdy 4-step structure: Directives -> Semantic Nodes -> Groups -> Beats."
2943
+ });
2944
+ }
2085
2945
  }
2086
- if (!ast) {
2087
- return {
2088
- isValid: false,
2089
- syntaxErrors,
2090
- archViolations: [],
2091
- repairPrompt: [
2092
- "The following MarkdyScript failed to parse with syntax errors:",
2093
- ...syntaxErrors.map((e) => ` - ${e}`),
2094
- "",
2095
- "Please fix the code below to follow valid MarkdyScript syntax:",
2096
- "```markdy",
2097
- sourceCode,
2098
- "```"
2099
- ].join("\n")
2100
- };
2946
+ const autoRepair = repairMarkdyCode(code, {
2947
+ transpileMermaid: options.transpileMermaid,
2948
+ precomputedIssues: issues
2949
+ });
2950
+ const repairPrompt = buildAIHealingPrompt(code, issues, autoRepair.repairedCode);
2951
+ const errorCount = issues.filter((i) => i.severity === "error").length;
2952
+ const warningCount = issues.filter((i) => i.severity === "warning").length;
2953
+ const isValid = errorCount === 0;
2954
+ const summary = isValid ? `\u2705 Markdy code is valid with ${warningCount} warning(s).` : `\u274C Markdy code has ${errorCount} syntax error(s) and ${warningCount} warning(s).`;
2955
+ return {
2956
+ isValid,
2957
+ errorCount,
2958
+ warningCount,
2959
+ issues,
2960
+ repairedCode: autoRepair.repairedCode !== code ? autoRepair.repairedCode : void 0,
2961
+ repairPrompt,
2962
+ summary,
2963
+ declaredNodes: Array.from(declaredNodes),
2964
+ referencedNodes: Array.from(referencedNodes)
2965
+ };
2966
+ }
2967
+ function escapeRegex(s) {
2968
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2969
+ }
2970
+ function repairMarkdyCode(code, options) {
2971
+ const changes = [];
2972
+ if (options?.transpileMermaid && /^\s*(graph\s+(TB|TD|BT|RL|LR)|flowchart\s+(TB|TD|BT|RL|LR))/i.test(code)) {
2973
+ try {
2974
+ const transpiledCode = options.transpileMermaid(code);
2975
+ changes.push("Transpiled Mermaid flowchart to canonical MarkdyScript.");
2976
+ return {
2977
+ repairedCode: transpiledCode,
2978
+ changes,
2979
+ isFixed: true
2980
+ };
2981
+ } catch {
2982
+ }
2983
+ }
2984
+ const lines = code.replace(/\r\n/g, "\n").split("\n");
2985
+ const repairedLines = [];
2986
+ const declaredNodes = /* @__PURE__ */ new Set();
2987
+ for (const line of lines) {
2988
+ const trimmed = line.trim();
2989
+ const nodeMatch = trimmed.match(/^(\w[\w.-]*)\s+(\w[\w.-]*)/);
2990
+ if (nodeMatch && !TOP_LEVEL_KEYWORDS.includes(nodeMatch[1].toLowerCase())) {
2991
+ declaredNodes.add(nodeMatch[2]);
2992
+ }
2101
2993
  }
2102
- const archViolations = validateArchitecture(ast);
2103
- if (ast.diagnostics.length === 0 && archViolations.length === 0) {
2104
- return { isValid: true, syntaxErrors: [], archViolations: [] };
2994
+ let inBeat = false;
2995
+ let beatIndent = 0;
2996
+ let hasBareCues = false;
2997
+ const bareCuesBuffer = [];
2998
+ for (let idx = 0; idx < lines.length; idx++) {
2999
+ let line = lines[idx];
3000
+ const trimmed = line.trim();
3001
+ if (!trimmed || trimmed.startsWith("//") || trimmed.startsWith("#")) {
3002
+ repairedLines.push(line);
3003
+ continue;
3004
+ }
3005
+ const indent = line.match(/^\s*/)?.[0] ?? "";
3006
+ const indentLen = indent.length;
3007
+ let processed = trimmed;
3008
+ if (inBeat && indentLen <= beatIndent && !trimmed.startsWith("&") && !trimmed.startsWith("beat ") && !trimmed.startsWith("bea ")) {
3009
+ inBeat = false;
3010
+ }
3011
+ processed = processed.replace(/^scen\b|^secne\b|^scence\b/i, "scene");
3012
+ processed = processed.replace(/^layput\b|^layour\b|^layuot\b/i, "layout");
3013
+ processed = processed.replace(/^groop\b|^grp\b/i, "group");
3014
+ processed = processed.replace(/^bea\b|^beats\b/i, "beat");
3015
+ processed = processed.replace(/^patern\b/i, "pattern");
3016
+ for (const [inv, valid] of Object.entries(INVALID_FLOW_OP_MAP)) {
3017
+ if (processed.includes(inv)) {
3018
+ processed = processed.replaceAll(inv, valid);
3019
+ changes.push(`Line ${idx + 1}: Fixed invalid flow operator '${inv}' -> '${valid}'.`);
3020
+ }
3021
+ }
3022
+ if (/^beat\s+/i.test(processed)) {
3023
+ inBeat = true;
3024
+ beatIndent = indentLen;
3025
+ if (/^beat\s+[\w.-]+(\s+"[^"]*")?\s*$/i.test(processed)) {
3026
+ processed = `${processed}:`;
3027
+ changes.push(`Line ${idx + 1}: Added missing colon ':' to beat header.`);
3028
+ }
3029
+ }
3030
+ if (/^group\s+/i.test(processed)) {
3031
+ if (!processed.includes(":")) {
3032
+ const groupMatch = processed.match(/^group\s+([\w.-]+)(?:\s+"([^"]*)")?\s+(.+)$/i);
3033
+ if (groupMatch) {
3034
+ const gid = groupMatch[1];
3035
+ const label = groupMatch[2] ? ` "${groupMatch[2]}"` : "";
3036
+ const rest = groupMatch[3];
3037
+ processed = `group ${gid}${label}: ${rest}`;
3038
+ changes.push(`Line ${idx + 1}: Added missing colon ':' to group header.`);
3039
+ }
3040
+ }
3041
+ }
3042
+ if (!inBeat && !processed.startsWith("scene") && !processed.startsWith("layout") && !processed.startsWith("group")) {
3043
+ const match = processed.match(/^(\w[\w.-]*)\s+(\w[\w.-]*)(.*)$/);
3044
+ if (match) {
3045
+ const rawKind = match[1];
3046
+ const rawKindLower = rawKind.toLowerCase();
3047
+ const id = match[2];
3048
+ let rest = match[3].trim();
3049
+ const canonical = canonicalNodeKind(rawKindLower);
3050
+ if (!NODE_KINDS.has(canonical)) {
3051
+ const matchKind = findClosestMatch(rawKindLower, ALL_NODE_KINDS);
3052
+ if (matchKind) {
3053
+ const canonicalFix = canonicalNodeKind(matchKind.match);
3054
+ changes.push(`Line ${idx + 1}: Repaired node kind '${rawKind}' -> '${canonicalFix}'.`);
3055
+ processed = `${canonicalFix} ${id}${rest ? ` ${rest}` : ""}`;
3056
+ }
3057
+ }
3058
+ if (rest && !rest.startsWith('"') && !rest.startsWith("style=") && !rest.startsWith("icon=") && rest.split(/\s+/).length > 1 && !rest.includes("=")) {
3059
+ changes.push(`Line ${idx + 1}: Enclosed multi-word label in quotes: "${rest}".`);
3060
+ processed = `${canonicalNodeKind(rawKindLower)} ${id} "${rest}"`;
3061
+ }
3062
+ }
3063
+ }
3064
+ if ((inBeat || processed.startsWith("group ")) && declaredNodes.size > 0) {
3065
+ if (processed.includes("->") || processed.includes("<-") || processed.includes("~>") || processed.includes("--") || processed.startsWith("group ")) {
3066
+ const cleanFlow = processed.replace(/"[^"]*"/g, '""');
3067
+ const flowTokens = cleanFlow.split(/->|<-|~>|--|&|:|\s+/).filter(Boolean);
3068
+ for (const tok of flowTokens) {
3069
+ if (!tok.startsWith("$") && !tok.includes("=") && /^[a-zA-Z_][\w.-]*$/.test(tok) && tok !== '""' && tok !== "group" && !["and", "show", "hide", "glow", "focus", "frame", "pulse", "use"].includes(tok.toLowerCase()) && !declaredNodes.has(tok)) {
3070
+ const match = findClosestMatch(tok, declaredNodes);
3071
+ if (match) {
3072
+ const regex = new RegExp(`\\b${escapeRegex(tok)}\\b`, "g");
3073
+ processed = processed.replace(regex, match.match);
3074
+ changes.push(`Line ${idx + 1}: Corrected typo in node reference '${tok}' -> '${match.match}'.`);
3075
+ }
3076
+ }
3077
+ }
3078
+ }
3079
+ }
3080
+ if (!inBeat) {
3081
+ const isCue = CUE_KEYWORDS.includes(processed.split(/\s+/)[0].toLowerCase());
3082
+ const isFlow = /(-->|->|<-|<--|~>|--)/.test(processed);
3083
+ if (isCue || isFlow) {
3084
+ hasBareCues = true;
3085
+ bareCuesBuffer.push(` ${processed}`);
3086
+ changes.push(`Line ${idx + 1}: Wrapped bare cue '${processed}' into 'beat main'.`);
3087
+ continue;
3088
+ }
3089
+ }
3090
+ repairedLines.push(`${indent}${processed}`);
2105
3091
  }
3092
+ if (hasBareCues && !lines.some((l) => l.trim().startsWith("beat "))) {
3093
+ repairedLines.push('\nbeat main "Flow":');
3094
+ repairedLines.push(...bareCuesBuffer);
3095
+ }
3096
+ const repairedCode = repairedLines.join("\n");
3097
+ let isFixed = false;
3098
+ try {
3099
+ parse(repairedCode);
3100
+ isFixed = true;
3101
+ } catch {
3102
+ isFixed = false;
3103
+ }
3104
+ return {
3105
+ repairedCode,
3106
+ changes,
3107
+ isFixed
3108
+ };
3109
+ }
3110
+ function buildAIHealingPrompt(sourceCode, issues, suggestedRepair) {
3111
+ if (issues.length === 0) {
3112
+ return "The MarkdyScript diagram is completely valid. No healing required.";
3113
+ }
3114
+ const errorIssues = issues.filter((i) => i.severity === "error" && i.code !== "ARCH_RULE_VIOLATION");
3115
+ const warningIssues = issues.filter((i) => i.severity === "warning" && i.code !== "ARCH_RULE_VIOLATION");
3116
+ const archIssues = issues.filter((i) => i.code === "ARCH_RULE_VIOLATION");
2106
3117
  const promptSections = [
2107
- "The MarkdyScript diagram has the following compiler diagnostics and architectural rule violations:",
2108
- "",
2109
- "### Diagnostics:",
2110
- ...ast.diagnostics.map((d) => ` - Line ${d.line}: ${d.message}`),
2111
- "",
2112
- "### Architectural Violations:",
2113
- ...archViolations.map((v) => ` - [${v.severity.toUpperCase()}] ${v.ruleName}: ${v.message}`),
2114
- "",
2115
- "Please revise the diagram code to resolve all issues while preserving semantic nodes and beats:",
2116
- "```markdy",
2117
- sourceCode,
2118
- "```"
3118
+ "### Markdy Diagram Syntax & Architectural Diagnostics",
3119
+ ""
2119
3120
  ];
3121
+ if (errorIssues.length > 0) {
3122
+ promptSections.push("The following MarkdyScript failed to parse with syntax errors:");
3123
+ promptSections.push("");
3124
+ promptSections.push("### Diagnostics & Critical Errors:");
3125
+ for (const err of errorIssues) {
3126
+ const lineStr = err.line ? ` - Line ${err.line}: ` : " - ";
3127
+ promptSections.push(`${lineStr}${err.message}`);
3128
+ if (err.snippet) {
3129
+ promptSections.push(` *Problem:* \`${err.snippet}\``);
3130
+ }
3131
+ if (err.suggestion) {
3132
+ promptSections.push(` *Recommendation:* ${err.suggestion}`);
3133
+ }
3134
+ if (err.didYouMean) {
3135
+ promptSections.push(` *Did you mean:* \`${err.didYouMean}\`?`);
3136
+ }
3137
+ }
3138
+ promptSections.push("");
3139
+ }
3140
+ if (archIssues.length > 0) {
3141
+ promptSections.push("### Architectural Violations:");
3142
+ for (const v of archIssues) {
3143
+ const lineStr = v.line ? ` (line ${v.line})` : "";
3144
+ promptSections.push(` - [${v.severity.toUpperCase()}] ${v.message}${lineStr}`);
3145
+ if (v.suggestion) {
3146
+ promptSections.push(` *Recommendation:* ${v.suggestion}`);
3147
+ }
3148
+ }
3149
+ promptSections.push("");
3150
+ } else if (warningIssues.length > 0) {
3151
+ promptSections.push("### Diagnostics & Warnings:");
3152
+ for (const w of warningIssues) {
3153
+ const lineStr = w.line ? ` - Line ${w.line}: ` : " - ";
3154
+ promptSections.push(`${lineStr}${w.message}`);
3155
+ if (w.suggestion) {
3156
+ promptSections.push(` *Recommendation:* ${w.suggestion}`);
3157
+ }
3158
+ }
3159
+ promptSections.push("");
3160
+ }
3161
+ promptSections.push("### Canonical Markdy Mental Model Guidelines:");
3162
+ promptSections.push("1. **Directives**: `scene theme=paper width=1280 height=720` and `layout LR` (or TB).");
3163
+ promptSections.push('2. **Nodes**: `<kind> <Id> ["Human Label"]` (e.g. `service Orders "Order Service"`, `database DB "PostgreSQL"`). Labels with spaces MUST be in double quotes.');
3164
+ promptSections.push('3. **Groups**: `group <id> "<Label>": Node1 Node2`');
3165
+ promptSections.push('4. **Beats**: `beat <id> "<Caption>":` with indented flows and cues.');
3166
+ promptSections.push("5. **Cycle Safety**: Always use `<-` for response/return edges to prevent layout rank cycles. Never use `->` to return to a previous node.");
3167
+ promptSections.push("");
3168
+ if (suggestedRepair && suggestedRepair !== sourceCode) {
3169
+ promptSections.push("### Proposed Repaired MarkdyScript:");
3170
+ promptSections.push("```markdy");
3171
+ promptSections.push(suggestedRepair);
3172
+ promptSections.push("```");
3173
+ promptSections.push("");
3174
+ }
3175
+ promptSections.push("Please revise the diagram code to resolve all issues while preserving semantic nodes and beats:");
3176
+ promptSections.push("```markdy");
3177
+ promptSections.push(sourceCode);
3178
+ promptSections.push("```");
3179
+ return promptSections.join("\n");
3180
+ }
3181
+
3182
+ // ../core/src/intellicode.ts
3183
+ var POPULAR_TECHS = [
3184
+ // Databases
3185
+ { name: "PostgreSQL", aliases: ["postgres", "postgresql", "psql"], kind: "database", defaultId: "Postgres", label: "PostgreSQL 16", desc: "ACID relational SQL database", category: "database" },
3186
+ { name: "MySQL", aliases: ["mysql", "mariadb"], kind: "database", defaultId: "MySQL", label: "MySQL 8.0", desc: "Relational database", category: "database" },
3187
+ { name: "MongoDB", aliases: ["mongo", "mongodb"], kind: "database", defaultId: "MongoDB", label: "MongoDB Atlas", desc: "Document NoSQL database", category: "database" },
3188
+ { name: "DynamoDB", aliases: ["dynamo", "dynamodb"], kind: "database", defaultId: "DynamoDB", label: "AWS DynamoDB", desc: "Serverless key-value NoSQL", category: "database" },
3189
+ { name: "ClickHouse", aliases: ["clickhouse"], kind: "database", defaultId: "ClickHouse", label: "ClickHouse OLAP", desc: "Real-time columnar analytics", category: "database" },
3190
+ { name: "Snowflake", aliases: ["snowflake"], kind: "database", defaultId: "Snowflake", label: "Snowflake Data Warehouse", desc: "Cloud data warehouse", category: "database" },
3191
+ { name: "Neo4j", aliases: ["neo4j", "graphdb"], kind: "database", defaultId: "Neo4j", label: "Neo4j Graph Database", desc: "Property graph engine", category: "database" },
3192
+ { name: "Pinecone", aliases: ["pinecone", "weaviate", "qdrant", "chroma"], kind: "database", defaultId: "Pinecone", label: "Pinecone Vector DB", desc: "Vector similarity search for AI/RAG", category: "ai" },
3193
+ // Caching
3194
+ { name: "Redis", aliases: ["redis", "valkey", "dragonfly"], kind: "cache", defaultId: "Redis", label: "Redis Cluster", desc: "In-memory sub-millisecond cache", category: "cache" },
3195
+ { name: "Memcached", aliases: ["memcached"], kind: "cache", defaultId: "Memcached", label: "Memcached", desc: "Distributed memory object cache", category: "cache" },
3196
+ // Messaging & Streams
3197
+ { name: "Kafka", aliases: ["kafka", "redpanda", "confluent"], kind: "queue", defaultId: "Kafka", label: "Kafka Event Stream", desc: "High-throughput event bus", category: "queue" },
3198
+ { name: "RabbitMQ", aliases: ["rabbitmq", "amqp"], kind: "queue", defaultId: "RabbitMQ", label: "RabbitMQ Broker", desc: "AMQP message queue", category: "queue" },
3199
+ { name: "AWS SQS", aliases: ["sqs"], kind: "queue", defaultId: "SQS", label: "Amazon SQS", desc: "Managed queue service", category: "queue" },
3200
+ { name: "NATS", aliases: ["nats"], kind: "queue", defaultId: "NATS", label: "NATS JetStream", desc: "Lightweight cloud native pub/sub", category: "queue" },
3201
+ // Ingress & Gateways
3202
+ { name: "Kong", aliases: ["kong"], kind: "gateway", defaultId: "KongGateway", label: "Kong API Gateway", desc: "Cloud native API gateway", category: "gateway" },
3203
+ { name: "Envoy", aliases: ["envoy"], kind: "gateway", defaultId: "EnvoyProxy", label: "Envoy Service Proxy", desc: "High-performance edge/service proxy", category: "gateway" },
3204
+ { name: "Nginx", aliases: ["nginx"], kind: "gateway", defaultId: "Nginx", label: "Nginx Ingress", desc: "Reverse proxy & load balancer", category: "gateway" },
3205
+ { name: "Traefik", aliases: ["traefik"], kind: "gateway", defaultId: "Traefik", label: "Traefik Proxy", desc: "Dynamic reverse proxy for microservices", category: "gateway" },
3206
+ { name: "Cloudflare", aliases: ["cloudflare"], kind: "cdn", defaultId: "Cloudflare", label: "Cloudflare Edge / WAF", desc: "Edge CDN & security perimeter", category: "security" },
3207
+ // Clients & Frontends
3208
+ { name: "React Web App", aliases: ["react", "next", "nextjs", "frontend", "webapp"], kind: "browser", defaultId: "WebApp", label: "Next.js Web Client", desc: "React / Next.js web application", category: "client" },
3209
+ { name: "Mobile App", aliases: ["ios", "android", "flutter", "reactnative", "mobile"], kind: "mobile", defaultId: "MobileApp", label: "iOS / Android Mobile App", desc: "Native mobile client application", category: "client" },
3210
+ // Compute & Microservices
3211
+ { name: "API Service", aliases: ["service", "api", "backend", "microservice"], kind: "service", defaultId: "ApiService", label: "API Core Service", desc: "Backend REST/gRPC microservice", category: "compute" },
3212
+ { name: "Auth Service", aliases: ["auth", "auth0", "keycloak", "clerk", "cognito"], kind: "service", defaultId: "AuthService", label: "Auth & Identity Provider", desc: "OAuth2 / OIDC authentication service", category: "security" },
3213
+ { name: "Order Service", aliases: ["order", "ordersvc"], kind: "service", defaultId: "OrderService", label: "Order Processing Service", desc: "Transactional order service", category: "compute" },
3214
+ { name: "Payment Gateway", aliases: ["payment", "stripe", "paypal"], kind: "service", defaultId: "PaymentService", label: "Stripe Payment Gateway", desc: "Payment processing integration", category: "compute" },
3215
+ { name: "Async Worker", aliases: ["worker", "celery", "sidekiq"], kind: "worker", defaultId: "AsyncWorker", label: "Background Job Worker", desc: "Asynchronous task consumer worker", category: "compute" },
3216
+ { name: "Serverless Function", aliases: ["lambda", "function", "cloudfunction"], kind: "lambda", defaultId: "LambdaFunc", label: "AWS Lambda / Cloud Function", desc: "Event-driven serverless function", category: "compute" },
3217
+ { name: "Kubernetes Pod", aliases: ["k8s", "pod", "kubernetes"], kind: "pod", defaultId: "AppPod", label: "Kubernetes Pod Cluster", desc: "Containerized workload pod", category: "compute" },
3218
+ // AI & LLMs
3219
+ { name: "Gemini / LLM Engine", aliases: ["gemini", "openai", "gpt", "claude", "llm", "ai"], kind: "service", defaultId: "AIEngine", label: "Gemini 2.5 Flash / AI Core", desc: "Large Language Model inference engine", category: "ai" },
3220
+ // Storage & Backend as a Service
3221
+ { name: "S3 Object Storage", aliases: ["s3", "storage", "gcs", "blob", "r2"], kind: "storage", defaultId: "S3Storage", label: "AWS S3 / Cloud Storage", desc: "Object storage bucket", category: "storage" },
3222
+ { name: "Supabase", aliases: ["supabase"], kind: "database", defaultId: "SupabaseDB", label: "Supabase PostgreSQL", desc: "Open-source Firebase alternative with Postgres", category: "database" },
3223
+ { name: "Firebase Firestore", aliases: ["firebase", "firestore"], kind: "database", defaultId: "Firestore", label: "Firebase Firestore", desc: "Serverless real-time document database", category: "database" },
3224
+ // Observability & Telemetry
3225
+ { name: "Prometheus", aliases: ["prometheus", "metrics"], kind: "metric", defaultId: "Prometheus", label: "Prometheus Metrics", desc: "Time-series metrics monitoring engine", category: "compute" },
3226
+ { name: "Grafana", aliases: ["grafana", "dashboard"], kind: "surface", defaultId: "GrafanaUI", label: "Grafana Dashboard", desc: "Visualization & telemetry analytics dashboard", category: "compute" },
3227
+ { name: "OpenSearch / Elasticsearch", aliases: ["opensearch", "elasticsearch", "elastic"], kind: "database", defaultId: "OpenSearch", label: "OpenSearch Cluster", desc: "Distributed search & log analytics engine", category: "database" },
3228
+ { name: "Sentry", aliases: ["sentry", "errors"], kind: "service", defaultId: "Sentry", label: "Sentry Error Tracker", desc: "Application monitoring and error tracking", category: "compute" }
3229
+ ];
3230
+ function extractDiagramContext(text, cursorLine = 0, cursorCol = 0) {
3231
+ const lines = text.split(/\r?\n/);
3232
+ const declaredNodes = [];
3233
+ const declaredGroups = [];
3234
+ const declaredBeats = [];
3235
+ let theme;
3236
+ let layout;
3237
+ let diagramType;
3238
+ let insideBeat = false;
3239
+ let currentBeatName;
3240
+ let insideGroup = false;
3241
+ for (let i = 0; i < lines.length; i++) {
3242
+ const rawLine = lines[i];
3243
+ const trimmed = rawLine.trim();
3244
+ if (!trimmed || trimmed.startsWith("//")) continue;
3245
+ const themeMatch = /theme\s*=\s*([a-zA-Z0-9_-]+)/.exec(trimmed);
3246
+ if (themeMatch) theme = themeMatch[1];
3247
+ const layoutMatch = /layout\s+([A-Z]{2})/.exec(trimmed);
3248
+ if (layoutMatch) layout = layoutMatch[1];
3249
+ const typeMatch = /type\s*=\s*([a-zA-Z0-9_-]+)/.exec(trimmed);
3250
+ if (typeMatch) diagramType = typeMatch[1];
3251
+ const beatMatch = /^beat\s+([\w.-]+)(?:\s+"([^"]*)")?:/i.exec(trimmed);
3252
+ if (beatMatch) {
3253
+ declaredBeats.push({ id: beatMatch[1], label: beatMatch[2] || beatMatch[1], line: i });
3254
+ if (i <= cursorLine) {
3255
+ insideBeat = true;
3256
+ currentBeatName = beatMatch[1];
3257
+ }
3258
+ continue;
3259
+ }
3260
+ if (insideBeat && i === cursorLine && !rawLine.startsWith(" ") && !rawLine.startsWith(" ") && trimmed) {
3261
+ if (!trimmed.startsWith("beat")) {
3262
+ insideBeat = false;
3263
+ currentBeatName = void 0;
3264
+ }
3265
+ }
3266
+ const groupMatch = /^group\s+([\w.-]+)(?:\s+"([^"]*)")?:\s*(.*)$/i.exec(trimmed);
3267
+ if (groupMatch) {
3268
+ const members = groupMatch[3] ? groupMatch[3].trim().split(/\s+/) : [];
3269
+ declaredGroups.push({ id: groupMatch[1], label: groupMatch[2] || groupMatch[1], members, line: i });
3270
+ continue;
3271
+ }
3272
+ const nodeMatch = /^(\w[\w.-]*)\s+(\w[\w.-]*)(?:\s+"([^"]*)")?/i.exec(trimmed);
3273
+ if (nodeMatch) {
3274
+ const kind = nodeMatch[1].toLowerCase();
3275
+ const id = nodeMatch[2];
3276
+ const label = nodeMatch[3] || humanizeId(id);
3277
+ if (!["scene", "layout", "group", "beat", "style", "pattern", "use", "var", "edge", "theme"].includes(kind) && (NODE_KINDS.has(kind) || NODE_KINDS.has(canonicalNodeKind(kind)) || TECHNICAL_NODE_TYPES.includes(kind))) {
3278
+ declaredNodes.push({ id, kind, label, line: i });
3279
+ }
3280
+ }
3281
+ }
3282
+ const currentLineText = lines[cursorLine] ?? "";
3283
+ const linePrefix = currentLineText.slice(0, cursorCol);
3284
+ const tokenMatch = /[\w$.-><~]*$/.exec(linePrefix);
3285
+ const tokenPrefix = tokenMatch ? tokenMatch[0] : "";
3286
+ let cursorInsideBeat = false;
3287
+ for (let i = cursorLine; i >= 0; i--) {
3288
+ const l = lines[i] ?? "";
3289
+ const trimL = l.trim();
3290
+ if (trimL.startsWith("//") || !trimL) continue;
3291
+ if (/^beat\s+[\w.-]+.*:/i.test(trimL)) {
3292
+ cursorInsideBeat = true;
3293
+ break;
3294
+ }
3295
+ if (/^(scene|layout|group|style|pattern|service|database|cache|queue|gateway|browser|mobile|client|worker|storage|cdn|pod|lambda|user)\b/i.test(trimL)) {
3296
+ cursorInsideBeat = false;
3297
+ break;
3298
+ }
3299
+ }
2120
3300
  return {
2121
- isValid: false,
2122
- syntaxErrors: [],
2123
- archViolations,
2124
- repairPrompt: promptSections.join("\n")
3301
+ declaredNodes,
3302
+ declaredGroups,
3303
+ declaredBeats,
3304
+ theme,
3305
+ layout,
3306
+ diagramType,
3307
+ insideBeat: cursorInsideBeat,
3308
+ currentBeatName,
3309
+ insideGroup,
3310
+ lineNo: cursorLine,
3311
+ lineText: currentLineText,
3312
+ linePrefix,
3313
+ tokenPrefix
2125
3314
  };
2126
3315
  }
3316
+ function isClientRole(kind) {
3317
+ return nodeRole(kind) === "client";
3318
+ }
3319
+ function isGatewayRole(kind) {
3320
+ const c = canonicalNodeKind(kind);
3321
+ return ["gateway", "api_gateway", "proxy", "load_balancer", "cdn", "firewall", "waf"].includes(c);
3322
+ }
3323
+ function isComputeRole(kind) {
3324
+ return nodeRole(kind) === "compute";
3325
+ }
3326
+ function isDatabaseKind(kind) {
3327
+ const c = canonicalNodeKind(kind);
3328
+ return ["database", "db", "sql", "nosql", "warehouse", "table", "lake"].includes(c);
3329
+ }
3330
+ function isCacheKind(kind) {
3331
+ const c = canonicalNodeKind(kind);
3332
+ return ["cache"].includes(c);
3333
+ }
3334
+ function isQueueRole(kind) {
3335
+ return nodeRole(kind) === "messaging";
3336
+ }
3337
+ function getIntelliCodeCompletions(docText, cursorLine, cursorCol) {
3338
+ const ctx = extractDiagramContext(docText, cursorLine, cursorCol);
3339
+ const items = [];
3340
+ const flowMatch = /([\w.-]+)\s*(->|<-|~>|<->|\.\.>)\s*([\w.-]*)$/.exec(ctx.linePrefix);
3341
+ if (flowMatch) {
3342
+ const sourceNodeId = flowMatch[1];
3343
+ const op = flowMatch[2];
3344
+ const filter = flowMatch[3].toLowerCase();
3345
+ const sourceNode = ctx.declaredNodes.find((n) => n.id === sourceNodeId);
3346
+ const sourceKind = sourceNode ? sourceNode.kind : "service";
3347
+ for (const node of ctx.declaredNodes) {
3348
+ if (node.id === sourceNodeId && op !== "<->") continue;
3349
+ let boost = 2;
3350
+ if (isClientRole(sourceKind)) {
3351
+ if (isGatewayRole(node.kind)) boost = 10;
3352
+ else if (isComputeRole(node.kind)) boost = 8;
3353
+ else boost = 2;
3354
+ } else if (isGatewayRole(sourceKind)) {
3355
+ if (isComputeRole(node.kind)) boost = 10;
3356
+ else boost = 3;
3357
+ } else if (isComputeRole(sourceKind)) {
3358
+ if (isDatabaseKind(node.kind) || isCacheKind(node.kind) || isQueueRole(node.kind)) boost = 10;
3359
+ else if (isComputeRole(node.kind)) boost = 7;
3360
+ else boost = 3;
3361
+ } else if (isQueueRole(sourceKind)) {
3362
+ if (isComputeRole(node.kind)) boost = 10;
3363
+ else boost = 2;
3364
+ }
3365
+ if (op === "<-") {
3366
+ boost = isClientRole(node.kind) || isGatewayRole(node.kind) ? 10 : 3;
3367
+ }
3368
+ items.push({
3369
+ label: node.id,
3370
+ insertText: `${node.id} `,
3371
+ kind: "node",
3372
+ detail: `${node.kind} \xB7 "${node.label}"`,
3373
+ documentation: `Connect flow ${sourceNodeId} ${op} ${node.id}`,
3374
+ boost
3375
+ });
3376
+ }
3377
+ if (!filter || filter.startsWith('"')) {
3378
+ const sourceRole = nodeRole(sourceKind);
3379
+ const sampleLabels = getCommonEdgeLabels(sourceRole, op);
3380
+ for (const lbl of sampleLabels) {
3381
+ items.push({
3382
+ label: `"${lbl}"`,
3383
+ insertText: `"${lbl}"`,
3384
+ kind: "snippet",
3385
+ detail: "Flow description label",
3386
+ boost: 1
3387
+ });
3388
+ }
3389
+ }
3390
+ return items;
3391
+ }
3392
+ const nodeStartMatch = /^\s*([\w.-]+)\s+$/.exec(ctx.linePrefix);
3393
+ if (nodeStartMatch) {
3394
+ const candidateId = nodeStartMatch[1];
3395
+ const isDeclared = ctx.declaredNodes.some((n) => n.id === candidateId);
3396
+ if (isDeclared) {
3397
+ items.push(
3398
+ { label: "->", insertText: "-> ", kind: "flowOp", detail: "Sync request / call", documentation: "Synchronous HTTP, gRPC, or RPC call.", boost: 10 },
3399
+ { label: "~>", insertText: "~> ", kind: "flowOp", detail: "Async event / pub-sub", documentation: "Asynchronous event stream or message queue dispatch.", boost: 9 },
3400
+ { label: "<-", insertText: "<- ", kind: "flowOp", detail: "Response / return payload", documentation: "Synchronous return response payload.", boost: 8 },
3401
+ { label: "<->", insertText: "<-> ", kind: "flowOp", detail: "Bidirectional streaming / mTLS", documentation: "Two-way streaming WebSocket or mutual TLS socket.", boost: 7 },
3402
+ { label: "..>", insertText: "..> ", kind: "flowOp", detail: "Weak dependency link", documentation: "Dashed architectural dependency relationship.", boost: 6 }
3403
+ );
3404
+ return items;
3405
+ }
3406
+ }
3407
+ if (/(theme\s*=\s*|theme\s+)\w*$/i.test(ctx.linePrefix)) {
3408
+ const themeEntries = [
3409
+ { name: "paper", desc: "Warm editorial white canvas with crisp modern typography" },
3410
+ { name: "midnight", desc: "Deep OLED dark theme with vibrant neon kinetic pulses" },
3411
+ { name: "blueprint", desc: "Architectural blueprint cyan graph styling" },
3412
+ { name: "nebula", desc: "Futuristic cosmic purple & indigo glow palette" },
3413
+ { name: "editorial", desc: "High-contrast serif luxury publication layout" },
3414
+ { name: "graphite", desc: "Sleek slate monochrome engineering aesthetic" },
3415
+ { name: "terminal", desc: "Retro phosphor CRT hacker terminal green on black" },
3416
+ { name: "sketchy", desc: "Hand-drawn sketchy whiteboard marker design" }
3417
+ ];
3418
+ for (const t of themeEntries) {
3419
+ items.push({
3420
+ label: t.name,
3421
+ insertText: t.name,
3422
+ kind: "theme",
3423
+ detail: `theme=${t.name}`,
3424
+ documentation: t.desc,
3425
+ boost: 10
3426
+ });
3427
+ }
3428
+ return items;
3429
+ }
3430
+ if (/(layout\s+|direction\s*=\s*)\w*$/i.test(ctx.linePrefix)) {
3431
+ items.push(
3432
+ { label: "LR", insertText: "LR", kind: "layout", detail: "Left-to-Right", documentation: "Horizontal left-to-right system flow layout.", boost: 10 },
3433
+ { label: "TB", insertText: "TB", kind: "layout", detail: "Top-to-Bottom", documentation: "Vertical top-to-bottom hierarchy layout.", boost: 9 },
3434
+ { label: "RL", insertText: "RL", kind: "layout", detail: "Right-to-Left", documentation: "Right-to-left reverse topology layout.", boost: 5 },
3435
+ { label: "BT", insertText: "BT", kind: "layout", detail: "Bottom-to-Top", documentation: "Bottom-to-top inverted stack layout.", boost: 5 }
3436
+ );
3437
+ return items;
3438
+ }
3439
+ if (/type\s*=\s*\w*$/i.test(ctx.linePrefix)) {
3440
+ for (const t of DIAGRAM_TYPES) {
3441
+ items.push({
3442
+ label: t,
3443
+ insertText: t,
3444
+ kind: "diagramType",
3445
+ detail: `type=${t}`,
3446
+ documentation: `Specialized layout engine: ${t}`,
3447
+ boost: 8
3448
+ });
3449
+ }
3450
+ return items;
3451
+ }
3452
+ const cueMatch = /^\s*(show|glow|pulse|focus|frame|hide)\s+([\w$]*)$/i.exec(ctx.linePrefix);
3453
+ if (cueMatch) {
3454
+ const cueName = cueMatch[1].toLowerCase();
3455
+ items.push(
3456
+ { label: "$nodes", insertText: "$nodes stagger=60ms", kind: "selector", detail: "All diagram nodes", documentation: "Selects all semantic nodes in the scene.", boost: 10 },
3457
+ { label: "$title", insertText: "$title", kind: "selector", detail: "Diagram title banner", documentation: "Selects the title node.", boost: 8 },
3458
+ { label: "$edges", insertText: "$edges", kind: "selector", detail: "All edge connectors", documentation: "Selects all flow connectors.", boost: 7 }
3459
+ );
3460
+ for (const node of ctx.declaredNodes) {
3461
+ items.push({
3462
+ label: node.id,
3463
+ insertText: `${node.id} `,
3464
+ kind: "node",
3465
+ detail: `${node.kind} \xB7 "${node.label}"`,
3466
+ boost: 9
3467
+ });
3468
+ }
3469
+ for (const group of ctx.declaredGroups) {
3470
+ items.push({
3471
+ label: group.id,
3472
+ insertText: cueName === "frame" ? `${group.id} zoom=1.2 dur=600ms` : `${group.id} `,
3473
+ kind: "group",
3474
+ detail: `group "${group.label}"`,
3475
+ documentation: `Group boundary with ${group.members.length} nodes.`,
3476
+ boost: 8
3477
+ });
3478
+ }
3479
+ if (cueName === "show" || cueName === "hide") {
3480
+ items.push({ label: "stagger=60ms", insertText: "stagger=60ms", kind: "attribute", detail: "Stagger delay", boost: 5 });
3481
+ } else if (cueName === "glow" || cueName === "pulse") {
3482
+ items.push(
3483
+ { label: "color=#38bdf8", insertText: "color=#38bdf8", kind: "attribute", detail: "Sky blue kinetic glow", boost: 5 },
3484
+ { label: "color=#e11d48", insertText: "color=#e11d48", kind: "attribute", detail: "Alert crimson pulse", boost: 5 },
3485
+ { label: "color=#10b981", insertText: "color=#10b981", kind: "attribute", detail: "Success emerald glow", boost: 5 }
3486
+ );
3487
+ } else if (cueName === "frame") {
3488
+ items.push(
3489
+ { label: "zoom=1.2 dur=600ms", insertText: "zoom=1.2 dur=600ms", kind: "attribute", detail: "Camera focus & zoom", boost: 5 }
3490
+ );
3491
+ }
3492
+ return items;
3493
+ }
3494
+ if (ctx.insideBeat) {
3495
+ for (const cue of BEAT_CUE_KEYWORDS) {
3496
+ const aliasTarget = cue === "pulse" ? "focus" : cue === "highlight" ? "glow" : cue;
3497
+ items.push({
3498
+ label: cue,
3499
+ insertText: cue === "show" ? "show $nodes stagger=60ms" : `${cue} `,
3500
+ kind: "cue",
3501
+ detail: `Choreography cue (${aliasTarget})`,
3502
+ documentation: `Execute kinetic choreography action: ${cue}`,
3503
+ boost: 8
3504
+ });
3505
+ }
3506
+ for (const node of ctx.declaredNodes) {
3507
+ items.push({
3508
+ label: node.id,
3509
+ insertText: `${node.id} -> `,
3510
+ kind: "node",
3511
+ detail: `Start flow from ${node.kind} "${node.label}"`,
3512
+ boost: 9
3513
+ });
3514
+ }
3515
+ items.push(
3516
+ { label: "$nodes", insertText: "$nodes", kind: "selector", detail: "Selector for all nodes", boost: 6 },
3517
+ { label: "$title", insertText: "$title", kind: "selector", detail: "Diagram title", boost: 5 }
3518
+ );
3519
+ return items;
3520
+ }
3521
+ items.push(
3522
+ { label: "scene", insertText: "scene theme=paper layout=LR\n", kind: "keyword", detail: "Scene directive", documentation: "Declare scene configuration, theme, and layout direction.", boost: 10 },
3523
+ { label: "layout LR", insertText: "layout LR\n", kind: "directive", detail: "Horizontal layout", documentation: "Left-to-right topology layout.", boost: 10 },
3524
+ { label: "layout TB", insertText: "layout TB\n", kind: "directive", detail: "Vertical hierarchy layout", documentation: "Top-to-bottom hierarchy layout.", boost: 9 },
3525
+ { label: "theme midnight", insertText: "theme=midnight\n", kind: "directive", detail: "Dark theme", documentation: "High-contrast dark mode with neon accents.", boost: 8 },
3526
+ { label: "theme paper", insertText: "theme=paper\n", kind: "directive", detail: "Editorial light theme", documentation: "Clean light mode with high-legibility typography.", boost: 8 },
3527
+ { label: "group", insertText: 'group ${1:subsystem} "${2:Subsystem Perimeter}": ${3:Node1} ${4:Node2}\n', kind: "snippet", isSnippet: true, detail: "Group boundary", documentation: "Perimeter enclosing related subsystems.", boost: 8 },
3528
+ { label: "beat", insertText: 'beat ${1:main} "${2:System Flow}":\n show $nodes stagger=60ms\n ${0}\n', kind: "snippet", isSnippet: true, detail: "Kinetic narrative beat", documentation: "Defines an animated step in the diagram choreography.", boost: 9 },
3529
+ { label: "pattern", insertText: 'pattern ${1:retry_flow} "${2:Retry Pattern}":\n ${3:Service} -> ${4:Queue} "retry"\n', kind: "snippet", isSnippet: true, detail: "Reusable flow pattern", documentation: "Defines a reusable architectural interaction pattern.", boost: 6 },
3530
+ { label: "use pattern", insertText: "use pattern=${1:retry_flow}\n", kind: "directive", detail: "Instantiate pattern", documentation: "Reuses a declared flow pattern in the current scope.", boost: 6 },
3531
+ { label: "style", insertText: "style ${1:NodeId} fill=${2:#38bdf8} stroke=${3:#0284c7}\n", kind: "snippet", isSnippet: true, detail: "Custom node styling", boost: 5 },
3532
+ { label: "var", insertText: "var ${1:key} = ${2:value}\n", kind: "snippet", isSnippet: true, detail: "Declare variable", boost: 4 }
3533
+ );
3534
+ const nodeKindsList = [
3535
+ { kind: "service", id: "ApiService", label: "API Service", desc: "Microservice / REST / gRPC backend" },
3536
+ { kind: "database", id: "Database", label: "PostgreSQL Database", desc: "Relational or NoSQL database store" },
3537
+ { kind: "cache", id: "RedisCache", label: "In-Memory Cache", desc: "High-speed in-memory cache" },
3538
+ { kind: "queue", id: "MessageQueue", label: "Kafka Event Stream", desc: "Pub/Sub message broker or event stream" },
3539
+ { kind: "gateway", id: "ApiGateway", label: "API Gateway", desc: "Ingress proxy and perimeter router" },
3540
+ { kind: "browser", id: "WebApp", label: "Web Client", desc: "End-user web browser application" },
3541
+ { kind: "mobile", id: "MobileApp", label: "Mobile Application", desc: "iOS / Android native mobile app" },
3542
+ { kind: "worker", id: "BackgroundWorker", label: "Async Task Worker", desc: "Background job execution worker" },
3543
+ { kind: "storage", id: "ObjectStorage", label: "S3 Object Store", desc: "Blob / document cloud storage" },
3544
+ { kind: "cdn", id: "EdgeCDN", label: "Cloudflare Edge", desc: "Global content delivery network" },
3545
+ { kind: "firewall", id: "WAF", label: "Security Perimeter", desc: "Web application firewall & DDoS shield" },
3546
+ { kind: "lambda", id: "ServerlessFunc", label: "Serverless Function", desc: "Event-driven serverless function" },
3547
+ { kind: "pod", id: "AppPod", label: "Kubernetes Pod", desc: "Containerized workload pod" },
3548
+ { kind: "user", id: "Customer", label: "End User", desc: "Human user / customer actor" }
3549
+ ];
3550
+ for (const n of nodeKindsList) {
3551
+ items.push({
3552
+ label: n.kind,
3553
+ insertText: `${n.kind} \${1:${n.id}} "\${2:${n.label}}"`,
3554
+ kind: "nodeKind",
3555
+ isSnippet: true,
3556
+ detail: `Node kind \u2192 ${n.kind}`,
3557
+ documentation: n.desc,
3558
+ boost: 7
3559
+ });
3560
+ }
3561
+ for (const vp of VISUAL_PRIMITIVE_TYPES) {
3562
+ items.push({
3563
+ label: vp,
3564
+ insertText: `${vp} \${1:${humanizeId(vp).replace(/\s+/g, "")}} "\${2:${humanizeId(vp)}}"`,
3565
+ kind: "nodeKind",
3566
+ isSnippet: true,
3567
+ detail: `Visual primitive \u2192 ${vp}`,
3568
+ boost: 4
3569
+ });
3570
+ }
3571
+ for (const tech of POPULAR_TECHS) {
3572
+ items.push({
3573
+ label: tech.name,
3574
+ insertText: `${tech.kind} ${tech.defaultId} "${tech.label}"
3575
+ `,
3576
+ kind: "tech",
3577
+ detail: `\u26A1 ${tech.kind} \xB7 ${tech.name}`,
3578
+ documentation: `${tech.desc}
3579
+
3580
+ Inserts semantic ${tech.kind} node definition.`,
3581
+ filterText: `${tech.name} ${tech.aliases.join(" ")} ${tech.kind}`,
3582
+ boost: 6
3583
+ });
3584
+ }
3585
+ for (const node of ctx.declaredNodes) {
3586
+ items.push({
3587
+ label: node.id,
3588
+ insertText: node.id,
3589
+ kind: "node",
3590
+ detail: `${node.kind} \xB7 "${node.label}"`,
3591
+ boost: 5
3592
+ });
3593
+ }
3594
+ return items;
3595
+ }
3596
+ function getCommonEdgeLabels(sourceKind, op) {
3597
+ if (op === "<-") {
3598
+ return ["200 OK", "201 Created", "Cached Response", "Result Payload", "JWT Token"];
3599
+ }
3600
+ if (op === "~>") {
3601
+ return ["order.created", "user.signup", "event.published", "telemetry.metric", "task.enqueue"];
3602
+ }
3603
+ if (op === "<->") {
3604
+ return ["WebSocket / Streaming", "mTLS Bidirectional", "gRPC Bidirectional Stream"];
3605
+ }
3606
+ if (op === "..>") {
3607
+ return ["depends on", "reads schema", "replicates to", "monitors"];
3608
+ }
3609
+ if (isClientRole(sourceKind)) {
3610
+ return ["POST /api/v1/checkout", "GET /api/v1/user", "POST /auth/login", "GraphQL Query"];
3611
+ }
3612
+ if (isGatewayRole(sourceKind)) {
3613
+ return ["Route /v1/orders", "Authorize & Forward", "Proxy Request"];
3614
+ }
3615
+ if (isComputeRole(sourceKind)) {
3616
+ return ["SELECT * FROM orders", "SET cache:key", "GET cache:key", "Publish Event", "Verify Token"];
3617
+ }
3618
+ if (isQueueRole(sourceKind)) {
3619
+ return ["Consume Message", "Process Batch", "Deliver Payload"];
3620
+ }
3621
+ return ["call", "request", "query", "sync"];
3622
+ }
3623
+ function predictNextLineSuggestion(docText, cursorLine) {
3624
+ const ctx = extractDiagramContext(docText, cursorLine, 0);
3625
+ const totalNodes = ctx.declaredNodes.length;
3626
+ if (totalNodes === 0) {
3627
+ return {
3628
+ text: 'browser WebApp "Web Application"',
3629
+ insertText: 'browser WebApp "Web Application"\nservice ApiGw "API Gateway Service"\n\nbeat main "System Entrance":\n show $nodes stagger=60ms\n WebApp -> ApiGw "POST /api"\n',
3630
+ description: "Start with an entry client and API Gateway with entrance choreography",
3631
+ type: "next-node"
3632
+ };
3633
+ }
3634
+ if (totalNodes >= 2 && ctx.declaredBeats.length === 0 && !ctx.insideBeat) {
3635
+ const n1 = ctx.declaredNodes[0];
3636
+ const n2 = ctx.declaredNodes[1];
3637
+ return {
3638
+ text: `beat main "System Flow":
3639
+ show $nodes stagger=60ms
3640
+ ${n1.id} -> ${n2.id} "call"`,
3641
+ insertText: `beat main "System Flow":
3642
+ show $nodes stagger=60ms
3643
+ ${n1.id} -> ${n2.id} "call"
3644
+ `,
3645
+ description: "Initialize choreography with entrance animation and primary flow",
3646
+ type: "init-beat"
3647
+ };
3648
+ }
3649
+ if (ctx.insideBeat) {
3650
+ const lines = docText.split(/\r?\n/);
3651
+ const prevLine = lines[cursorLine - 1]?.trim() ?? "";
3652
+ const reqMatch = /^([\w.-]+)\s*->\s*([\w.-]+)(?:\s+"([^"]*)")?/i.exec(prevLine);
3653
+ if (reqMatch) {
3654
+ const src = reqMatch[1];
3655
+ const tgt = reqMatch[2];
3656
+ return {
3657
+ text: ` ${src} <- ${tgt} "200 OK"`,
3658
+ insertText: ` ${src} <- ${tgt} "200 OK"
3659
+ `,
3660
+ description: `Add response flow returning from ${tgt} to ${src}`,
3661
+ type: "next-flow"
3662
+ };
3663
+ }
3664
+ const eventMatch = /^([\w.-]+)\s*~>\s*([\w.-]+)/i.exec(prevLine);
3665
+ if (eventMatch) {
3666
+ const queueId = eventMatch[2];
3667
+ const worker = ctx.declaredNodes.find((n) => nodeRole(n.kind) === "compute" && n.id !== eventMatch[1]);
3668
+ if (worker) {
3669
+ return {
3670
+ text: ` ${queueId} ~> ${worker.id} "process"`,
3671
+ insertText: ` ${queueId} ~> ${worker.id} "process"
3672
+ `,
3673
+ description: `Deliver queued event from ${queueId} to worker ${worker.id}`,
3674
+ type: "next-flow"
3675
+ };
3676
+ }
3677
+ }
3678
+ const keyNode = ctx.declaredNodes.find((n) => nodeRole(n.kind) === "compute" || nodeRole(n.kind) === "database") || ctx.declaredNodes[0];
3679
+ if (keyNode) {
3680
+ return {
3681
+ text: ` pulse ${keyNode.id} color=#38bdf8`,
3682
+ insertText: ` pulse ${keyNode.id} color=#38bdf8
3683
+ `,
3684
+ description: `Add kinetic highlight pulse to ${keyNode.id}`,
3685
+ type: "beat-cue"
3686
+ };
3687
+ }
3688
+ }
3689
+ const hasClient = ctx.declaredNodes.some((n) => isClientRole(n.kind));
3690
+ const hasCompute = ctx.declaredNodes.some((n) => isComputeRole(n.kind) || isGatewayRole(n.kind));
3691
+ const hasDB = ctx.declaredNodes.some((n) => isDatabaseKind(n.kind));
3692
+ const hasCache = ctx.declaredNodes.some((n) => isCacheKind(n.kind));
3693
+ const hasQueue = ctx.declaredNodes.some((n) => isQueueRole(n.kind));
3694
+ if (hasCompute && !hasDB) {
3695
+ return {
3696
+ text: 'database Postgres "PostgreSQL 16"',
3697
+ insertText: 'database Postgres "PostgreSQL 16"\n',
3698
+ description: "Add a database tier to persist state for your services",
3699
+ type: "next-node"
3700
+ };
3701
+ }
3702
+ if (hasCompute && hasDB && !hasCache) {
3703
+ return {
3704
+ text: 'cache Redis "Redis Cluster"',
3705
+ insertText: 'cache Redis "Redis Cluster"\n',
3706
+ description: "Add an in-memory caching tier to accelerate query latency",
3707
+ type: "next-node"
3708
+ };
3709
+ }
3710
+ if (hasCompute && hasDB && !hasQueue) {
3711
+ return {
3712
+ text: 'queue Kafka "Kafka Event Stream"',
3713
+ insertText: 'queue Kafka "Kafka Event Stream"\nworker AsyncWorker "Background Worker"\n',
3714
+ description: "Add an asynchronous event queue and worker for decoupled processing",
3715
+ type: "next-node"
3716
+ };
3717
+ }
3718
+ return null;
3719
+ }
3720
+ function getArchitectureSuggestions(docText) {
3721
+ const ctx = extractDiagramContext(docText, 0, 0);
3722
+ const recommendations = [];
3723
+ const nodeCount = ctx.declaredNodes.length;
3724
+ if (nodeCount === 0) return recommendations;
3725
+ const hasClient = ctx.declaredNodes.some((n) => isClientRole(n.kind));
3726
+ const hasGateway = ctx.declaredNodes.some((n) => isGatewayRole(n.kind));
3727
+ const hasServices = ctx.declaredNodes.filter((n) => isComputeRole(n.kind));
3728
+ const hasDatabases = ctx.declaredNodes.filter((n) => isDatabaseKind(n.kind));
3729
+ const hasCache = ctx.declaredNodes.some((n) => isCacheKind(n.kind));
3730
+ const hasQueue = ctx.declaredNodes.some((n) => isQueueRole(n.kind));
3731
+ const hasGroups = ctx.declaredGroups.length > 0;
3732
+ const hasBeats = ctx.declaredBeats.length > 0;
3733
+ if (hasClient && (hasServices.length > 0 || hasDatabases.length > 0) && !hasGateway) {
3734
+ recommendations.push({
3735
+ id: "add-gateway",
3736
+ title: "Add API Gateway Perimeter",
3737
+ desc: "Direct client-to-service connections bypass centralized rate-limiting and auth. Insert an API Gateway.",
3738
+ snippet: 'gateway ApiGateway "Kong / Envoy Gateway"\n\nbeat route:\n WebApp -> ApiGateway "HTTPS"\n',
3739
+ category: "security",
3740
+ actionLabel: "Insert Gateway"
3741
+ });
3742
+ }
3743
+ if (hasDatabases.length > 0 && !hasCache) {
3744
+ recommendations.push({
3745
+ id: "add-cache",
3746
+ title: "Add In-Memory Cache (Redis)",
3747
+ desc: "Offload frequent read queries from your database with a sub-millisecond Redis cluster.",
3748
+ snippet: 'cache Redis "Redis Cluster"\n\nbeat cache_layer:\n ApiService -> Redis "cache.get"\n',
3749
+ category: "performance",
3750
+ actionLabel: "Insert Redis Cache"
3751
+ });
3752
+ }
3753
+ if (hasServices.length >= 2 && !hasGroups) {
3754
+ const memberIds = hasServices.map((s) => s.id).join(" ");
3755
+ recommendations.push({
3756
+ id: "add-group-boundary",
3757
+ title: "Enclose Microservices in Group Boundary",
3758
+ desc: "Group related backend microservices inside a secure VPC boundary perimeter.",
3759
+ snippet: `group backend "Backend Microservices VPC": ${memberIds}
3760
+ `,
3761
+ category: "structure",
3762
+ actionLabel: "Add Group Boundary"
3763
+ });
3764
+ }
3765
+ if (!hasBeats && nodeCount >= 2) {
3766
+ const firstTwo = ctx.declaredNodes.slice(0, 2);
3767
+ recommendations.push({
3768
+ id: "add-entrance-beat",
3769
+ title: "Add Animated Entrance Choreography",
3770
+ desc: "Bring your architecture to life with staggered node reveals and motion flows.",
3771
+ snippet: `beat reveal "System Entrance":
3772
+ show $nodes stagger=60ms
3773
+ ${firstTwo[0].id} -> ${firstTwo[1].id} "GET /api"
3774
+ `,
3775
+ category: "choreography",
3776
+ actionLabel: "Add Narrative Beat"
3777
+ });
3778
+ }
3779
+ if (hasServices.length >= 2 && !hasQueue) {
3780
+ recommendations.push({
3781
+ id: "add-event-queue",
3782
+ title: "Decouple Services with Event Queue",
3783
+ desc: "Use an asynchronous event stream (Kafka/SQS) for resilient pub/sub event handling.",
3784
+ snippet: 'queue Kafka "Kafka Event Bus"\nworker Worker "Event Consumer"\n\nbeat async_event:\n ApiService ~> Kafka "event.published"\n Kafka ~> Worker "process"\n',
3785
+ category: "reliability",
3786
+ actionLabel: "Add Kafka Queue"
3787
+ });
3788
+ }
3789
+ return recommendations;
3790
+ }
2127
3791
 
2128
3792
  // ../compat/src/mermaid/mermaid-transpiler.ts
2129
3793
  function sanitizeId(id) {
@@ -3075,7 +4739,10 @@ function handleValidateMarkdy(code, checkArchitecture = true) {
3075
4739
  content: [{ type: "text", text: lines.join("\n") }]
3076
4740
  };
3077
4741
  } catch (error) {
3078
- const repairBundle = analyzeAndBuildRepairPrompt(code);
4742
+ const report = diagnoseMarkdyCode(code, {
4743
+ checkArchitecture,
4744
+ transpileMermaid: (src) => transpileMermaidToMarkdy(src, "Imported Scene").code
4745
+ });
3079
4746
  return {
3080
4747
  isError: true,
3081
4748
  content: [
@@ -3084,12 +4751,87 @@ function handleValidateMarkdy(code, checkArchitecture = true) {
3084
4751
  text: `\u274C Parse Error: ${error.message}
3085
4752
 
3086
4753
  Suggested AI Healing Prompt:
3087
- ${repairBundle.repairPrompt}`
4754
+ ${report.repairPrompt}`
3088
4755
  }
3089
4756
  ]
3090
4757
  };
3091
4758
  }
3092
4759
  }
4760
+ function handleDiagnoseMarkdy(code, checkArchitecture = true) {
4761
+ const report = diagnoseMarkdyCode(code, {
4762
+ checkArchitecture,
4763
+ transpileMermaid: (src) => transpileMermaidToMarkdy(src, "Imported Scene").code
4764
+ });
4765
+ const lines = [];
4766
+ lines.push(report.summary);
4767
+ if (report.issues.length > 0) {
4768
+ const errors = report.issues.filter((i) => i.severity === "error");
4769
+ const warnings = report.issues.filter((i) => i.severity === "warning");
4770
+ if (errors.length > 0) {
4771
+ lines.push("\n### \u274C Syntax Errors & Typos:");
4772
+ for (const e of errors) {
4773
+ lines.push(`- **[${e.code}]** Line ${e.line}: ${e.message}`);
4774
+ if (e.snippet) lines.push(` - *Problem:* \`${e.snippet}\``);
4775
+ if (e.suggestion) lines.push(` - *Recommendation:* ${e.suggestion}`);
4776
+ if (e.didYouMean) lines.push(` - *Did you mean:* \`${e.didYouMean}\`?`);
4777
+ }
4778
+ }
4779
+ if (warnings.length > 0) {
4780
+ lines.push("\n### \u26A0\uFE0F Warnings & Governance:");
4781
+ for (const w of warnings) {
4782
+ lines.push(`- **[${w.code}]** Line ${w.line}: ${w.message}`);
4783
+ if (w.suggestion) lines.push(` - *Recommendation:* ${w.suggestion}`);
4784
+ }
4785
+ }
4786
+ }
4787
+ if (report.repairedCode) {
4788
+ lines.push("\n### \u{1F6E0}\uFE0F Proposed Auto-Repaired Code:");
4789
+ lines.push("```markdy");
4790
+ lines.push(report.repairedCode);
4791
+ lines.push("```");
4792
+ }
4793
+ lines.push("\n### \u{1F4A1} AI Self-Healing Prompt for LLMs / Agents:");
4794
+ lines.push(report.repairPrompt);
4795
+ return {
4796
+ isError: !report.isValid,
4797
+ content: [{ type: "text", text: lines.join("\n") }]
4798
+ };
4799
+ }
4800
+ function handleFixMarkdy(code) {
4801
+ const result = repairMarkdyCode(code, {
4802
+ transpileMermaid: (src) => transpileMermaidToMarkdy(src, "Imported Scene").code
4803
+ });
4804
+ const lines = [];
4805
+ if (result.isFixed) {
4806
+ lines.push(`\u2705 Markdy Code Repaired Successfully (${result.changes.length} change(s) applied):
4807
+ `);
4808
+ for (const c of result.changes) {
4809
+ lines.push(`- ${c}`);
4810
+ }
4811
+ lines.push("\n```markdy");
4812
+ lines.push(result.repairedCode);
4813
+ lines.push("```");
4814
+ } else {
4815
+ lines.push(`\u26A0\uFE0F Applied ${result.changes.length} automated repair(s), but additional manual corrections may be needed:
4816
+ `);
4817
+ for (const c of result.changes) {
4818
+ lines.push(`- ${c}`);
4819
+ }
4820
+ lines.push("\n```markdy");
4821
+ lines.push(result.repairedCode);
4822
+ lines.push("```");
4823
+ const diag = diagnoseMarkdyCode(result.repairedCode);
4824
+ if (!diag.isValid) {
4825
+ lines.push("\nRemaining issues to resolve:");
4826
+ for (const iss of diag.issues) {
4827
+ lines.push(`- Line ${iss.line}: ${iss.message}`);
4828
+ }
4829
+ }
4830
+ }
4831
+ return {
4832
+ content: [{ type: "text", text: lines.join("\n") }]
4833
+ };
4834
+ }
3093
4835
  async function handleTranspileToMarkdy(source, format, title = "Imported Scene") {
3094
4836
  try {
3095
4837
  let markdyCode = "";
@@ -3211,6 +4953,45 @@ function handleGetArchitectureCatalog(filterCategory) {
3211
4953
  content: [{ type: "text", text }]
3212
4954
  };
3213
4955
  }
4956
+ function handleIntelliCode(code, line, column) {
4957
+ const lines = code.split(/\r?\n/);
4958
+ const targetLine = line !== void 0 ? line : Math.max(0, lines.length - 1);
4959
+ const targetCol = column !== void 0 ? column : lines[targetLine]?.length ?? 0;
4960
+ const completions = getIntelliCodeCompletions(code, targetLine, targetCol);
4961
+ const nextLinePrediction = predictNextLineSuggestion(code, targetLine);
4962
+ const archRecommendations = getArchitectureSuggestions(code);
4963
+ const output = {
4964
+ cursor: { line: targetLine, column: targetCol },
4965
+ completionsCount: completions.length,
4966
+ topCompletions: completions.slice(0, 20).map((c) => ({
4967
+ label: c.label,
4968
+ insertText: c.insertText,
4969
+ kind: c.kind,
4970
+ detail: c.detail,
4971
+ documentation: c.documentation
4972
+ })),
4973
+ nextLinePrediction: nextLinePrediction ? {
4974
+ suggestedText: nextLinePrediction.text,
4975
+ description: nextLinePrediction.description,
4976
+ type: nextLinePrediction.type
4977
+ } : null,
4978
+ architectureRecommendations: archRecommendations.map((r) => ({
4979
+ id: r.id,
4980
+ title: r.title,
4981
+ description: r.desc,
4982
+ category: r.category,
4983
+ actionSnippet: r.snippet
4984
+ }))
4985
+ };
4986
+ return {
4987
+ content: [
4988
+ {
4989
+ type: "text",
4990
+ text: JSON.stringify(output, null, 2)
4991
+ }
4992
+ ]
4993
+ };
4994
+ }
3214
4995
  function handleReadResource(uri) {
3215
4996
  switch (uri) {
3216
4997
  case "markdy://spec/agent-reference":
@@ -3248,6 +5029,30 @@ Cycle Safety: Always use <- for responses back to callers to prevent layout rank
3248
5029
  }
3249
5030
  ]
3250
5031
  };
5032
+ case "markdy://spec/grammar-rules":
5033
+ return {
5034
+ contents: [
5035
+ {
5036
+ uri,
5037
+ mimeType: "text/markdown",
5038
+ text: `# MarkdyScript Grammar & Diagnostic Rules
5039
+
5040
+ ### 1. The 4-Step Mental Model
5041
+ 1. **Directives**: \`scene theme=paper width=1280 height=720\` and \`layout LR\` (or TB).
5042
+ 2. **Nodes**: \`<kind> <Id> ["Human Label"]\` (e.g. \`browser Client "Shopper"\`, \`gateway GW "API Gateway"\`, \`service Orders\`, \`database DB\`).
5043
+ 3. **Groups**: \`group <id> "<Label>": Node1 Node2\`
5044
+ 4. **Beats**: \`beat <id> "<Caption>":\` containing indented flow lines and cues.
5045
+
5046
+ ### 2. Common Syntax Errors & Corrections
5047
+ - **Keyword typos**: \`scen\` -> \`scene\`, \`layput\` -> \`layout\`, \`groop\` -> \`group\`, \`bea\` -> \`beat\`.
5048
+ - **Node kind typos**: \`servce\` -> \`service\`, \`databse\` -> \`database\`, \`gateawy\` -> \`gateway\`, \`cach\` -> \`cache\`.
5049
+ - **Missing colons**: Always terminate \`beat <id> ["Caption"]:\` and \`group <id> ["Label"]:\` with a colon.
5050
+ - **Unquoted strings**: Labels containing spaces MUST be wrapped in double quotes (\`"...\`\`).
5051
+ - **Cycle safety**: NEVER use \`->\` for response or return paths. Use \`<-\` for return calls to prevent cycle overlap rank collapse.
5052
+ - **Cue placement**: Visual cues (\`show $nodes\`, \`glow\`, \`frame\`, \`focus\`) and flows must reside INSIDE an indented beat block.`
5053
+ }
5054
+ ]
5055
+ };
3251
5056
  default:
3252
5057
  throw new Error(`Resource not found: ${uri}`);
3253
5058
  }
@@ -3290,6 +5095,38 @@ function createMarkdyMcpServer() {
3290
5095
  required: ["code"]
3291
5096
  }
3292
5097
  },
5098
+ {
5099
+ name: "diagnose_markdy_syntax",
5100
+ description: "Deep diagnostic tool that analyzes MarkdyScript diagram syntax, detects typos in keywords/node kinds/operators/nodes, identifies flow cycle overlaps, unquoted strings, and provides 'Did you mean?' suggestions with auto-repaired code.",
5101
+ inputSchema: {
5102
+ type: "object",
5103
+ properties: {
5104
+ code: {
5105
+ type: "string",
5106
+ description: "The MarkdyScript (.markdy) diagram code to inspect."
5107
+ },
5108
+ checkArchitecture: {
5109
+ type: "boolean",
5110
+ description: "Whether to include Well-Architected governance rules in the report. Default: true."
5111
+ }
5112
+ },
5113
+ required: ["code"]
5114
+ }
5115
+ },
5116
+ {
5117
+ name: "fix_markdy_code",
5118
+ description: "Automatically repairs common MarkdyScript syntax errors, typos, missing colons, invalid flow operators, unquoted strings, cycle return edges, and wraps top-level bare cues into a valid scene.",
5119
+ inputSchema: {
5120
+ type: "object",
5121
+ properties: {
5122
+ code: {
5123
+ type: "string",
5124
+ description: "The broken or draft MarkdyScript diagram code to automatically repair."
5125
+ }
5126
+ },
5127
+ required: ["code"]
5128
+ }
5129
+ },
3293
5130
  {
3294
5131
  name: "transpile_to_markdy",
3295
5132
  description: "Converts external infrastructure or diagram code (Mermaid, Docker Compose, Kubernetes manifests, Terraform state, or Draw.io XML) into animated MarkdyScript scenes.",
@@ -3353,6 +5190,28 @@ function createMarkdyMcpServer() {
3353
5190
  }
3354
5191
  }
3355
5192
  }
5193
+ },
5194
+ {
5195
+ name: "get_markdy_intellicode",
5196
+ description: "Provides zero-dependency intelligent code completion, predictive ghost-text next-line suggestions, and Well-Architected proactive recommendations for MarkdyScript diagrams.",
5197
+ inputSchema: {
5198
+ type: "object",
5199
+ properties: {
5200
+ code: {
5201
+ type: "string",
5202
+ description: "The MarkdyScript diagram code."
5203
+ },
5204
+ line: {
5205
+ type: "number",
5206
+ description: "Optional 0-indexed line number where the cursor is positioned."
5207
+ },
5208
+ column: {
5209
+ type: "number",
5210
+ description: "Optional 0-indexed column character where the cursor is positioned."
5211
+ }
5212
+ },
5213
+ required: ["code"]
5214
+ }
3356
5215
  }
3357
5216
  ]
3358
5217
  };
@@ -3366,6 +5225,13 @@ function createMarkdyMcpServer() {
3366
5225
  String(safeArgs.code ?? ""),
3367
5226
  safeArgs.checkArchitecture !== false
3368
5227
  );
5228
+ case "diagnose_markdy_syntax":
5229
+ return handleDiagnoseMarkdy(
5230
+ String(safeArgs.code ?? ""),
5231
+ safeArgs.checkArchitecture !== false
5232
+ );
5233
+ case "fix_markdy_code":
5234
+ return handleFixMarkdy(String(safeArgs.code ?? ""));
3369
5235
  case "transpile_to_markdy":
3370
5236
  return await handleTranspileToMarkdy(
3371
5237
  String(safeArgs.source ?? ""),
@@ -3378,6 +5244,12 @@ function createMarkdyMcpServer() {
3378
5244
  return handleGenerateMarkdyPrompt(String(safeArgs.userGoal ?? ""));
3379
5245
  case "get_architecture_catalog":
3380
5246
  return handleGetArchitectureCatalog(safeArgs.filterCategory ? String(safeArgs.filterCategory) : void 0);
5247
+ case "get_markdy_intellicode":
5248
+ return handleIntelliCode(
5249
+ String(safeArgs.code ?? ""),
5250
+ typeof safeArgs.line === "number" ? safeArgs.line : void 0,
5251
+ typeof safeArgs.column === "number" ? safeArgs.column : void 0
5252
+ );
3381
5253
  default:
3382
5254
  throw new Error(`Unknown tool: ${name}`);
3383
5255
  }
@@ -3391,6 +5263,12 @@ function createMarkdyMcpServer() {
3391
5263
  description: "Canonical reference for MarkdyScript syntax, closed node vocabularies, and cycle-safety rules.",
3392
5264
  mimeType: "text/markdown"
3393
5265
  },
5266
+ {
5267
+ uri: "markdy://spec/grammar-rules",
5268
+ name: "MarkdyScript Grammar & Diagnostic Rules",
5269
+ description: "Grammar conventions, typo resolution guides, and cycle-safe routing rules.",
5270
+ mimeType: "text/markdown"
5271
+ },
3394
5272
  {
3395
5273
  uri: "markdy://templates/catalog",
3396
5274
  name: "Markdy Architecture Templates Catalog",
@@ -3434,6 +5312,17 @@ function createMarkdyMcpServer() {
3434
5312
  }
3435
5313
  ]
3436
5314
  },
5315
+ {
5316
+ name: "debug_markdy_syntax",
5317
+ description: "Diagnose, troubleshoot, and heal broken MarkdyScript syntax, typos, undefined node references, or flow cycles.",
5318
+ arguments: [
5319
+ {
5320
+ name: "code",
5321
+ description: "The broken or erroneous MarkdyScript code to diagnose and repair.",
5322
+ required: true
5323
+ }
5324
+ ]
5325
+ },
3437
5326
  {
3438
5327
  name: "audit_architecture",
3439
5328
  description: "Review a MarkdyScript diagram for Well-Architected governance, cycle overlap, and layer violations.",
@@ -3494,6 +5383,24 @@ Follow the canonical 4-step Markdy mental model:
3494
5383
  ]
3495
5384
  };
3496
5385
  }
5386
+ case "debug_markdy_syntax": {
5387
+ return {
5388
+ description: "Diagnose and heal MarkdyScript syntax errors and typos",
5389
+ messages: [
5390
+ {
5391
+ role: "user",
5392
+ content: {
5393
+ type: "text",
5394
+ text: `Please diagnose and repair the following MarkdyScript code. Check for keyword typos, invalid node kinds, unquoted strings, missing colons, undefined node references, and flow cycles:
5395
+
5396
+ \`\`\`markdy
5397
+ ${safeArgs.code}
5398
+ \`\`\``
5399
+ }
5400
+ }
5401
+ ]
5402
+ };
5403
+ }
3497
5404
  case "audit_architecture": {
3498
5405
  return {
3499
5406
  description: "Audit Markdy diagram against Well-Architected rules",
@@ -3551,9 +5458,12 @@ if (isDirectExecution) {
3551
5458
  export {
3552
5459
  MCP_SERVER_VERSION,
3553
5460
  createMarkdyMcpServer,
5461
+ handleDiagnoseMarkdy,
3554
5462
  handleExplainArchitecture,
5463
+ handleFixMarkdy,
3555
5464
  handleGenerateMarkdyPrompt,
3556
5465
  handleGetArchitectureCatalog,
5466
+ handleIntelliCode,
3557
5467
  handleReadResource,
3558
5468
  handleTranspileToMarkdy,
3559
5469
  handleValidateMarkdy,