@markdy/mcp-server 1.0.24 → 1.0.26

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
@@ -5,22 +5,3053 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
5
5
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
6
  import {
7
7
  CallToolRequestSchema,
8
- ListToolsRequestSchema
8
+ ListToolsRequestSchema,
9
+ ListResourcesRequestSchema,
10
+ ReadResourceRequestSchema,
11
+ ListPromptsRequestSchema,
12
+ GetPromptRequestSchema
9
13
  } from "@modelcontextprotocol/sdk/types.js";
10
14
 
15
+ // ../core/src/system-vocabulary.ts
16
+ var TECHNICAL_NODE_TYPES = [
17
+ "service",
18
+ "api",
19
+ "microservice",
20
+ "backend",
21
+ "server",
22
+ "worker",
23
+ "job",
24
+ "scheduler",
25
+ "cron",
26
+ "batch",
27
+ "function",
28
+ "lambda",
29
+ "edge",
30
+ "controller",
31
+ "handler",
32
+ "repository",
33
+ "module",
34
+ "package",
35
+ "library",
36
+ "sdk",
37
+ "cli",
38
+ "runtime",
39
+ "process",
40
+ "client",
41
+ "user",
42
+ "browser",
43
+ "web",
44
+ "mobile",
45
+ "desktop",
46
+ "frontend",
47
+ "app",
48
+ "page",
49
+ "view",
50
+ "component",
51
+ "store",
52
+ "db",
53
+ "database",
54
+ "sql",
55
+ "nosql",
56
+ "table",
57
+ "index",
58
+ "warehouse",
59
+ "lake",
60
+ "object_store",
61
+ "storage",
62
+ "bucket",
63
+ "blob",
64
+ "volume",
65
+ "disk",
66
+ "search",
67
+ "cache",
68
+ "queue",
69
+ "topic",
70
+ "stream",
71
+ "event",
72
+ "event_bus",
73
+ "bus",
74
+ "broker",
75
+ "pubsub",
76
+ "kafka",
77
+ "producer",
78
+ "consumer",
79
+ "dead_letter",
80
+ "dlq",
81
+ "webhook",
82
+ "cloud",
83
+ "region",
84
+ "vpc",
85
+ "subnet",
86
+ "network",
87
+ "internet",
88
+ "dns",
89
+ "cdn",
90
+ "proxy",
91
+ "gateway",
92
+ "api_gateway",
93
+ "load_balancer",
94
+ "reverse_proxy",
95
+ "router",
96
+ "switch",
97
+ "nat",
98
+ "firewall",
99
+ "waf",
100
+ "vpn",
101
+ "bastion",
102
+ "container",
103
+ "cluster",
104
+ "pod",
105
+ "node",
106
+ "deployment",
107
+ "replicaset",
108
+ "statefulset",
109
+ "daemonset",
110
+ "namespace",
111
+ "ingress",
112
+ "service_mesh",
113
+ "sidecar",
114
+ "image",
115
+ "registry",
116
+ "docker",
117
+ "compose",
118
+ "helm",
119
+ "chart",
120
+ "configmap",
121
+ "pvc",
122
+ "auth",
123
+ "identity",
124
+ "oauth",
125
+ "oidc",
126
+ "jwt",
127
+ "session",
128
+ "policy",
129
+ "role",
130
+ "permission",
131
+ "vault",
132
+ "secret",
133
+ "key",
134
+ "certificate",
135
+ "security",
136
+ "repo",
137
+ "branch",
138
+ "commit",
139
+ "pipeline",
140
+ "workflow",
141
+ "runner",
142
+ "build",
143
+ "test",
144
+ "artifact",
145
+ "deploy",
146
+ "release",
147
+ "environment",
148
+ "preview",
149
+ "monitor",
150
+ "metrics",
151
+ "logs",
152
+ "trace",
153
+ "alert",
154
+ "dashboard",
155
+ "probe",
156
+ "slo",
157
+ "start",
158
+ "end",
159
+ "state",
160
+ "decision",
161
+ "condition",
162
+ "step",
163
+ "loop",
164
+ "sequence",
165
+ "participant",
166
+ "hub",
167
+ "station",
168
+ "bronze",
169
+ "silver",
170
+ "gold",
171
+ "lane",
172
+ "replica",
173
+ "shard",
174
+ "leader",
175
+ "follower",
176
+ "quorum",
177
+ "consensus",
178
+ "lock",
179
+ "class",
180
+ "interface",
181
+ "method",
182
+ "object",
183
+ "enum",
184
+ "type"
185
+ ];
186
+ var VISUAL_PRIMITIVE_TYPES = [
187
+ "panel",
188
+ "surface",
189
+ "terminal",
190
+ "metric",
191
+ "stat",
192
+ "grid",
193
+ "matrix",
194
+ "lane",
195
+ "track",
196
+ "marker",
197
+ "dot",
198
+ "token_strip",
199
+ "chips",
200
+ "glyph_card",
201
+ "glyph",
202
+ "external",
203
+ "optional"
204
+ ];
205
+ var VISUAL_PRIMITIVE_KINDS = {
206
+ panel: "flow",
207
+ surface: "flow",
208
+ terminal: "flow",
209
+ metric: "observability",
210
+ stat: "observability",
211
+ grid: "flow",
212
+ matrix: "flow",
213
+ lane: "flow",
214
+ track: "flow",
215
+ marker: "flow",
216
+ dot: "flow",
217
+ token_strip: "flow",
218
+ chips: "flow",
219
+ glyph_card: "flow",
220
+ glyph: "flow",
221
+ external: "network",
222
+ optional: "flow"
223
+ };
224
+ var TECHNICAL_NODE_KINDS = {
225
+ service: "compute",
226
+ api: "compute",
227
+ microservice: "compute",
228
+ backend: "compute",
229
+ server: "compute",
230
+ worker: "compute",
231
+ job: "compute",
232
+ scheduler: "compute",
233
+ cron: "compute",
234
+ batch: "compute",
235
+ function: "compute",
236
+ lambda: "compute",
237
+ edge: "compute",
238
+ controller: "compute",
239
+ handler: "compute",
240
+ repository: "compute",
241
+ runtime: "compute",
242
+ process: "compute",
243
+ module: "code",
244
+ package: "code",
245
+ library: "code",
246
+ sdk: "code",
247
+ cli: "code",
248
+ class: "code",
249
+ interface: "code",
250
+ method: "code",
251
+ object: "code",
252
+ enum: "code",
253
+ type: "code",
254
+ client: "client",
255
+ user: "client",
256
+ browser: "client",
257
+ web: "client",
258
+ mobile: "client",
259
+ desktop: "client",
260
+ frontend: "client",
261
+ app: "client",
262
+ page: "client",
263
+ view: "client",
264
+ component: "client",
265
+ store: "client",
266
+ db: "data",
267
+ database: "data",
268
+ sql: "data",
269
+ nosql: "data",
270
+ table: "data",
271
+ index: "data",
272
+ warehouse: "data",
273
+ lake: "data",
274
+ object_store: "data",
275
+ storage: "data",
276
+ bucket: "data",
277
+ blob: "data",
278
+ volume: "data",
279
+ disk: "data",
280
+ search: "data",
281
+ cache: "data",
282
+ queue: "messaging",
283
+ topic: "messaging",
284
+ stream: "messaging",
285
+ event: "messaging",
286
+ event_bus: "messaging",
287
+ bus: "messaging",
288
+ broker: "messaging",
289
+ pubsub: "messaging",
290
+ kafka: "messaging",
291
+ producer: "messaging",
292
+ consumer: "messaging",
293
+ dead_letter: "messaging",
294
+ dlq: "messaging",
295
+ webhook: "messaging",
296
+ cloud: "network",
297
+ region: "network",
298
+ vpc: "network",
299
+ subnet: "network",
300
+ network: "network",
301
+ internet: "network",
302
+ dns: "network",
303
+ cdn: "network",
304
+ proxy: "network",
305
+ gateway: "network",
306
+ api_gateway: "network",
307
+ load_balancer: "network",
308
+ reverse_proxy: "network",
309
+ router: "network",
310
+ switch: "network",
311
+ nat: "network",
312
+ firewall: "network",
313
+ waf: "network",
314
+ vpn: "network",
315
+ bastion: "network",
316
+ container: "platform",
317
+ cluster: "platform",
318
+ pod: "platform",
319
+ node: "platform",
320
+ deployment: "platform",
321
+ replicaset: "platform",
322
+ statefulset: "platform",
323
+ daemonset: "platform",
324
+ namespace: "platform",
325
+ ingress: "platform",
326
+ service_mesh: "platform",
327
+ sidecar: "platform",
328
+ image: "platform",
329
+ registry: "platform",
330
+ docker: "platform",
331
+ compose: "platform",
332
+ helm: "platform",
333
+ chart: "platform",
334
+ configmap: "platform",
335
+ pvc: "platform",
336
+ auth: "security",
337
+ identity: "security",
338
+ oauth: "security",
339
+ oidc: "security",
340
+ jwt: "security",
341
+ session: "security",
342
+ policy: "security",
343
+ role: "security",
344
+ permission: "security",
345
+ vault: "security",
346
+ secret: "security",
347
+ key: "security",
348
+ certificate: "security",
349
+ security: "security",
350
+ repo: "delivery",
351
+ branch: "delivery",
352
+ commit: "delivery",
353
+ pipeline: "delivery",
354
+ workflow: "delivery",
355
+ runner: "delivery",
356
+ build: "delivery",
357
+ test: "delivery",
358
+ artifact: "delivery",
359
+ deploy: "delivery",
360
+ release: "delivery",
361
+ environment: "delivery",
362
+ preview: "delivery",
363
+ monitor: "observability",
364
+ metrics: "observability",
365
+ logs: "observability",
366
+ trace: "observability",
367
+ alert: "observability",
368
+ dashboard: "observability",
369
+ probe: "observability",
370
+ slo: "observability",
371
+ start: "flow",
372
+ end: "flow",
373
+ state: "flow",
374
+ decision: "flow",
375
+ condition: "flow",
376
+ step: "flow",
377
+ loop: "flow",
378
+ sequence: "flow",
379
+ participant: "flow",
380
+ hub: "data",
381
+ station: "compute",
382
+ bronze: "data",
383
+ silver: "data",
384
+ gold: "data",
385
+ lane: "flow",
386
+ replica: "distributed",
387
+ shard: "distributed",
388
+ leader: "distributed",
389
+ follower: "distributed",
390
+ quorum: "distributed",
391
+ consensus: "distributed",
392
+ lock: "distributed"
393
+ };
394
+
395
+ // ../core/src/player.ts
396
+ function parseBooleanToken(raw) {
397
+ if (typeof raw === "boolean") return raw;
398
+ const s = String(raw).trim().toLowerCase();
399
+ if (["true", "on", "yes", "1"].includes(s)) return true;
400
+ if (["false", "off", "no", "0"].includes(s)) return false;
401
+ return void 0;
402
+ }
403
+ var CONTROL_KEYS = [
404
+ "play",
405
+ "restart",
406
+ "prevBeat",
407
+ "nextBeat",
408
+ "seek",
409
+ "speed",
410
+ "fit",
411
+ "resetView",
412
+ "fullscreen",
413
+ "svg",
414
+ "share",
415
+ "code",
416
+ "theme"
417
+ ];
418
+ var INTERACTION_KEYS = ["zoom", "pan", "doubleClickToReset"];
419
+ var PLAYBACK = {
420
+ autoplay: { group: "playback", key: "autoplay", type: "boolean" },
421
+ loop: { group: "playback", key: "loop", type: "boolean" },
422
+ rate: { group: "playback", key: "rate", type: "rate" },
423
+ speed: { group: "playback", key: "rate", type: "rate" },
424
+ playbackRate: { group: "playback", key: "rate", type: "rate" },
425
+ playback_rate: { group: "playback", key: "rate", type: "rate" }
426
+ };
427
+ var CONTROLS = {
428
+ controls: { group: "controls", key: "*", type: "group" },
429
+ play: { group: "controls", key: "play", type: "boolean" },
430
+ playButton: { group: "controls", key: "play", type: "boolean" },
431
+ play_button: { group: "controls", key: "play", type: "boolean" },
432
+ restart: { group: "controls", key: "restart", type: "boolean" },
433
+ restartButton: { group: "controls", key: "restart", type: "boolean" },
434
+ restart_button: { group: "controls", key: "restart", type: "boolean" },
435
+ prevBeat: { group: "controls", key: "prevBeat", type: "boolean" },
436
+ prev_beat: { group: "controls", key: "prevBeat", type: "boolean" },
437
+ nextBeat: { group: "controls", key: "nextBeat", type: "boolean" },
438
+ next_beat: { group: "controls", key: "nextBeat", type: "boolean" },
439
+ speeds: { group: "controls", key: "speeds", type: "rates" },
440
+ speedOptions: { group: "controls", key: "speeds", type: "rates" },
441
+ speed_options: { group: "controls", key: "speeds", type: "rates" },
442
+ seek: { group: "controls", key: "seek", type: "boolean" },
443
+ seekBar: { group: "controls", key: "seek", type: "boolean" },
444
+ seek_bar: { group: "controls", key: "seek", type: "boolean" },
445
+ speed: { group: "controls", key: "speed", type: "boolean" },
446
+ speedControls: { group: "controls", key: "speed", type: "boolean" },
447
+ speed_controls: { group: "controls", key: "speed", type: "boolean" },
448
+ fit: { group: "controls", key: "fit", type: "boolean" },
449
+ fitView: { group: "controls", key: "fit", type: "boolean" },
450
+ fit_view: { group: "controls", key: "fit", type: "boolean" },
451
+ fitViewButton: { group: "controls", key: "fit", type: "boolean" },
452
+ fit_view_button: { group: "controls", key: "fit", type: "boolean" },
453
+ resetView: { group: "controls", key: "resetView", type: "boolean" },
454
+ reset_view: { group: "controls", key: "resetView", type: "boolean" },
455
+ resetViewButton: { group: "controls", key: "resetView", type: "boolean" },
456
+ reset_view_button: { group: "controls", key: "resetView", type: "boolean" },
457
+ fullscreen: { group: "controls", key: "fullscreen", type: "boolean" },
458
+ fullScreen: { group: "controls", key: "fullscreen", type: "boolean" },
459
+ full_screen: { group: "controls", key: "fullscreen", type: "boolean" },
460
+ fullscreenButton: { group: "controls", key: "fullscreen", type: "boolean" },
461
+ fullscreen_button: { group: "controls", key: "fullscreen", type: "boolean" },
462
+ svg: { group: "controls", key: "svg", type: "boolean" },
463
+ exportSvg: { group: "controls", key: "svg", type: "boolean" },
464
+ export_svg: { group: "controls", key: "svg", type: "boolean" },
465
+ share: { group: "controls", key: "share", type: "boolean" },
466
+ shareLink: { group: "controls", key: "share", type: "boolean" },
467
+ share_link: { group: "controls", key: "share", type: "boolean" },
468
+ code: { group: "controls", key: "code", type: "boolean" },
469
+ codeButton: { group: "controls", key: "code", type: "boolean" },
470
+ code_button: { group: "controls", key: "code", type: "boolean" },
471
+ exposeCode: { group: "controls", key: "code", type: "boolean" },
472
+ expose_code: { group: "controls", key: "code", type: "boolean" },
473
+ viewSource: { group: "controls", key: "code", type: "boolean" },
474
+ view_source: { group: "controls", key: "code", type: "boolean" },
475
+ theme: { group: "controls", key: "theme", type: "boolean" },
476
+ themeButton: { group: "controls", key: "theme", type: "boolean" },
477
+ theme_button: { group: "controls", key: "theme", type: "boolean" },
478
+ switchTheme: { group: "controls", key: "theme", type: "boolean" },
479
+ switch_theme: { group: "controls", key: "theme", type: "boolean" }
480
+ };
481
+ var INTERACTION = {
482
+ interactive: { group: "interaction", key: "*", type: "group" },
483
+ interactiveViewport: { group: "interaction", key: "*", type: "group" },
484
+ interactive_viewport: { group: "interaction", key: "*", type: "group" },
485
+ zoom: { group: "interaction", key: "zoom", type: "boolean" },
486
+ allowZoom: { group: "interaction", key: "zoom", type: "boolean" },
487
+ allow_zoom: { group: "interaction", key: "zoom", type: "boolean" },
488
+ pan: { group: "interaction", key: "pan", type: "boolean" },
489
+ allowPan: { group: "interaction", key: "pan", type: "boolean" },
490
+ allow_pan: { group: "interaction", key: "pan", type: "boolean" },
491
+ clickToPlay: { group: "interaction", key: "clickToPlay", type: "boolean" },
492
+ click_to_play: { group: "interaction", key: "clickToPlay", type: "boolean" },
493
+ keyboard: { group: "interaction", key: "keyboard", type: "boolean" },
494
+ shortcuts: { group: "interaction", key: "keyboard", type: "boolean" },
495
+ doubleClickToReset: { group: "interaction", key: "doubleClickToReset", type: "boolean" },
496
+ double_click_to_reset: { group: "interaction", key: "doubleClickToReset", type: "boolean" }
497
+ };
498
+ var CHROME = {
499
+ badge: { group: "chrome", key: "badge", type: "boolean" },
500
+ copyright: { group: "chrome", key: "badge", type: "boolean" },
501
+ progress: { group: "chrome", key: "progress", type: "progress" },
502
+ progressBar: { group: "chrome", key: "progress", type: "progress" },
503
+ progress_bar: { group: "chrome", key: "progress", type: "progress" },
504
+ sceneBoundaryProgress: { group: "chrome", key: "progress", type: "progress" },
505
+ progressColor: { group: "chrome", key: "progressColor", type: "color" },
506
+ progress_color: { group: "chrome", key: "progressColor", type: "color" },
507
+ progressBarColor: { group: "chrome", key: "progressColor", type: "color" },
508
+ progress_bar_color: { group: "chrome", key: "progressColor", type: "color" }
509
+ };
510
+ var FLAT = {
511
+ ...PLAYBACK,
512
+ ...CHROME,
513
+ controls: CONTROLS.controls,
514
+ playButton: CONTROLS.playButton,
515
+ play_button: CONTROLS.play_button,
516
+ restartButton: CONTROLS.restartButton,
517
+ restart_button: CONTROLS.restart_button,
518
+ seek: CONTROLS.seek,
519
+ seekBar: CONTROLS.seekBar,
520
+ seek_bar: CONTROLS.seek_bar,
521
+ speedControls: CONTROLS.speedControls,
522
+ speed_controls: CONTROLS.speed_controls,
523
+ speeds: CONTROLS.speeds,
524
+ speedOptions: CONTROLS.speedOptions,
525
+ speed_options: CONTROLS.speed_options,
526
+ prevBeat: CONTROLS.prevBeat,
527
+ prev_beat: CONTROLS.prev_beat,
528
+ nextBeat: CONTROLS.nextBeat,
529
+ next_beat: CONTROLS.next_beat,
530
+ keyboard: INTERACTION.keyboard,
531
+ shortcuts: INTERACTION.shortcuts,
532
+ fitView: CONTROLS.fitView,
533
+ fit_view: CONTROLS.fit_view,
534
+ fitViewButton: CONTROLS.fitViewButton,
535
+ fit_view_button: CONTROLS.fit_view_button,
536
+ resetViewButton: CONTROLS.resetViewButton,
537
+ reset_view_button: CONTROLS.reset_view_button,
538
+ fullscreen: CONTROLS.fullscreen,
539
+ fullScreen: CONTROLS.fullScreen,
540
+ full_screen: CONTROLS.full_screen,
541
+ fullscreenButton: CONTROLS.fullscreenButton,
542
+ fullscreen_button: CONTROLS.fullscreen_button,
543
+ exportSvg: CONTROLS.exportSvg,
544
+ export_svg: CONTROLS.export_svg,
545
+ shareLink: CONTROLS.shareLink,
546
+ share_link: CONTROLS.share_link,
547
+ code: CONTROLS.code,
548
+ codeButton: CONTROLS.codeButton,
549
+ code_button: CONTROLS.code_button,
550
+ exposeCode: CONTROLS.exposeCode,
551
+ expose_code: CONTROLS.expose_code,
552
+ viewSource: CONTROLS.viewSource,
553
+ view_source: CONTROLS.view_source,
554
+ interactive: INTERACTION.interactive,
555
+ interactiveViewport: INTERACTION.interactiveViewport,
556
+ interactive_viewport: INTERACTION.interactive_viewport,
557
+ allowZoom: INTERACTION.allowZoom,
558
+ allow_zoom: INTERACTION.allow_zoom,
559
+ allowPan: INTERACTION.allowPan,
560
+ allow_pan: INTERACTION.allow_pan,
561
+ clickToPlay: INTERACTION.clickToPlay,
562
+ click_to_play: INTERACTION.click_to_play,
563
+ doubleClickToReset: INTERACTION.doubleClickToReset,
564
+ double_click_to_reset: INTERACTION.double_click_to_reset
565
+ };
566
+ var SCOPES = {
567
+ player: FLAT,
568
+ playback: PLAYBACK,
569
+ controls: CONTROLS,
570
+ interaction: INTERACTION,
571
+ // `color` is unambiguous inside the block, but too generic at the root.
572
+ chrome: { ...CHROME, color: CHROME.progressColor }
573
+ };
574
+ var PLAYER_FLAT_KEYS = Object.keys(FLAT);
575
+ function applyPlayerSetting(config, scope, key, rawValue) {
576
+ const setting = SCOPES[scope][key];
577
+ if (!setting) return `unknown player property '${key}'`;
578
+ const value = unquote(rawValue).trim();
579
+ const group = config[setting.group] ??= {};
580
+ if (setting.type === "boolean" || setting.type === "group") {
581
+ const parsed = value ? parseBooleanToken(value) : true;
582
+ if (parsed === void 0) return `player property '${key}' expects true or false`;
583
+ if (setting.type === "boolean") group[setting.key] = parsed;
584
+ else for (const child of setting.group === "controls" ? CONTROL_KEYS : INTERACTION_KEYS) group[child] = parsed;
585
+ return void 0;
586
+ }
587
+ if (setting.type === "rate") {
588
+ const rate = Number(value);
589
+ if (!Number.isFinite(rate) || rate <= 0) return `player property '${key}' expects a positive number`;
590
+ group[setting.key] = rate;
591
+ return void 0;
592
+ }
593
+ if (setting.type === "rates") {
594
+ const rates = value.split(/[\s,]+/).filter(Boolean).map(Number);
595
+ if (!rates.length || rates.some((rate) => !Number.isFinite(rate) || rate <= 0)) {
596
+ return `player property '${key}' expects a list of positive numbers`;
597
+ }
598
+ group[setting.key] = rates;
599
+ return void 0;
600
+ }
601
+ if (setting.type === "color") {
602
+ if (value) group[setting.key] = value;
603
+ return void 0;
604
+ }
605
+ const mode = toProgressMode(value);
606
+ if (mode) group.progress = mode;
607
+ else if (value) group.progressColor = value;
608
+ return void 0;
609
+ }
610
+ function toProgressMode(value) {
611
+ const lower = value.toLowerCase();
612
+ if (lower === "none" || lower === "bar" || lower === "boundary") return lower;
613
+ const bool = parseBooleanToken(lower);
614
+ if (bool === true) return "boundary";
615
+ if (bool === false) return "none";
616
+ return void 0;
617
+ }
618
+ function unquote(raw) {
619
+ const value = raw.trim();
620
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
621
+ return value.slice(1, -1);
622
+ }
623
+ return value;
624
+ }
625
+
626
+ // ../core/src/registry.ts
627
+ var NODE_KINDS = /* @__PURE__ */ new Set([
628
+ ...TECHNICAL_NODE_TYPES,
629
+ ...VISUAL_PRIMITIVE_TYPES
630
+ ]);
631
+ var DIAGRAM_TYPES = /* @__PURE__ */ new Set([
632
+ "architecture",
633
+ "flowchart",
634
+ "tree",
635
+ "state",
636
+ "sequence",
637
+ "constellation",
638
+ "loop",
639
+ "flywheel",
640
+ "medallion",
641
+ "quadrant",
642
+ "swimlane",
643
+ "pyramid",
644
+ "radar",
645
+ "timeline",
646
+ "gantt",
647
+ "venn",
648
+ "layers",
649
+ "nested"
650
+ ]);
651
+ var EDGE_OPERATORS = {
652
+ "->": "request",
653
+ "<-": "response",
654
+ "~>": "event",
655
+ "--": "dependency"
656
+ };
657
+ var RESERVED_SELECTORS = /* @__PURE__ */ new Set(["$title", "$nodes", "$edges"]);
658
+ var CUE_ALIASES = {
659
+ pulse: "focus",
660
+ highlight: "glow",
661
+ emphasize: "glow"
662
+ };
663
+ var BEAT_CUE_KEYWORDS = /* @__PURE__ */ new Set([
664
+ "show",
665
+ "hide",
666
+ "glow",
667
+ "focus",
668
+ "frame",
669
+ "use",
670
+ ...Object.keys(CUE_ALIASES)
671
+ ]);
672
+ var SCENE_KEYS = /* @__PURE__ */ new Set([
673
+ "width",
674
+ "height",
675
+ "fps",
676
+ "theme",
677
+ "duration",
678
+ "direction",
679
+ "layout",
680
+ "type",
681
+ ...PLAYER_FLAT_KEYS
682
+ ]);
683
+ function nodeRole(kind) {
684
+ const canonical = canonicalNodeKind(kind);
685
+ return TECHNICAL_NODE_KINDS[canonical] ?? VISUAL_PRIMITIVE_KINDS[canonical] ?? "compute";
686
+ }
687
+ function humanizeId(id) {
688
+ const acronyms = /* @__PURE__ */ new Set(["api", "cdn", "db", "dns", "http", "https", "id", "jwt", "oidc", "sdk", "tls", "ui", "url"]);
689
+ const exactCase = /* @__PURE__ */ new Map([
690
+ ["etcd", "etcd"],
691
+ ["kubectl", "kubectl"]
692
+ ]);
693
+ return id.replace(/[_-]+/g, " ").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/([a-z\d])([A-Z])/g, "$1 $2").split(/\s+/).filter(Boolean).map((word) => {
694
+ const lower = word.toLowerCase();
695
+ const exact = exactCase.get(lower);
696
+ if (exact) return exact;
697
+ if (acronyms.has(lower)) return lower.toUpperCase();
698
+ return word.charAt(0).toUpperCase() + word.slice(1);
699
+ }).join(" ");
700
+ }
701
+ var NODE_ALIASES = {
702
+ db: "database",
703
+ api: "service",
704
+ gateway: "api_gateway",
705
+ mq: "queue",
706
+ k8s: "cluster",
707
+ lb: "load_balancer",
708
+ panel: "surface",
709
+ metric: "stat",
710
+ grid: "matrix",
711
+ lane: "track",
712
+ marker: "dot",
713
+ chips: "token_strip",
714
+ glyph: "glyph_card"
715
+ };
716
+ function canonicalNodeKind(kind) {
717
+ return NODE_ALIASES[kind] ?? kind;
718
+ }
719
+
720
+ // ../core/src/parser.ts
721
+ var ParseError = class extends Error {
722
+ line;
723
+ column;
724
+ constructor(message, line, column) {
725
+ super(`line ${line}: ${message}`);
726
+ this.name = "ParseError";
727
+ this.line = line;
728
+ this.column = column;
729
+ }
730
+ };
731
+ var FLOW_OP_RE = /(->|<-|~>|--)/;
732
+ function stripComment(line) {
733
+ let inString = false;
734
+ let escaped = false;
735
+ for (let i = 0; i < line.length; i++) {
736
+ const ch = line[i];
737
+ if (escaped) {
738
+ escaped = false;
739
+ continue;
740
+ }
741
+ if (inString && ch === "\\") {
742
+ escaped = true;
743
+ continue;
744
+ }
745
+ if (ch === '"') {
746
+ inString = !inString;
747
+ continue;
748
+ }
749
+ if (inString) continue;
750
+ if (ch === "/" && line[i + 1] === "/") return line.slice(0, i);
751
+ if (ch === "#" && (i === 0 || /\s/.test(line[i - 1])) && (i + 1 >= line.length || /\s/.test(line[i + 1]))) {
752
+ return line.slice(0, i);
753
+ }
754
+ }
755
+ return line;
756
+ }
757
+ function parsePropValue(raw) {
758
+ let val = raw;
759
+ if (raw.startsWith('"')) {
760
+ const parsed = parseStringToken(raw);
761
+ if (parsed && parsed.rest === "") val = parsed.value;
762
+ }
763
+ if (typeof val === "string") {
764
+ if (/^\d+(\.\d+)?$/.test(val)) val = Number(val);
765
+ else if (val === "true") val = true;
766
+ else if (val === "false") val = false;
767
+ else if (/^\d+ms$/.test(val)) val = Number(val.slice(0, -2)) / 1e3;
768
+ else if (/^\d+(\.\d+)?s$/.test(val)) val = Number(val.slice(0, -1));
769
+ }
770
+ return val;
771
+ }
772
+ function parseProps(raw) {
773
+ const props = {};
774
+ let i = 0;
775
+ while (i < raw.length) {
776
+ const ch = raw[i];
777
+ if (ch === '"') {
778
+ const parsed = parseStringToken(raw.slice(i));
779
+ if (!parsed) break;
780
+ i = raw.length - parsed.rest.length;
781
+ continue;
782
+ }
783
+ const keyMatch = raw.slice(i).match(/^(\w[\w.-]*)=/);
784
+ if (!keyMatch) {
785
+ i++;
786
+ continue;
787
+ }
788
+ const key = keyMatch[1];
789
+ i += key.length + 1;
790
+ let value = "";
791
+ if (raw[i] === '"') {
792
+ const start = i;
793
+ const parsed = parseStringToken(raw.slice(i));
794
+ if (!parsed) {
795
+ value = raw.slice(i);
796
+ i = raw.length;
797
+ } else {
798
+ i = raw.length - parsed.rest.length;
799
+ value = raw.slice(start, i).trim();
800
+ }
801
+ } else {
802
+ const start = i;
803
+ while (i < raw.length && !/\s/.test(raw[i])) i++;
804
+ value = raw.slice(start, i);
805
+ }
806
+ props[key] = parsePropValue(value);
807
+ }
808
+ return props;
809
+ }
810
+ function parseStringToken(raw) {
811
+ const trimmed = raw.trimStart();
812
+ if (!trimmed.startsWith('"')) return null;
813
+ let i = 1;
814
+ let value = "";
815
+ while (i < trimmed.length) {
816
+ const ch = trimmed[i];
817
+ if (ch === '"') return { value, rest: trimmed.slice(i + 1).trim() };
818
+ if (ch === "\\" && i + 1 < trimmed.length) {
819
+ value += trimmed[i + 1];
820
+ i += 2;
821
+ continue;
822
+ }
823
+ value += ch;
824
+ i++;
825
+ }
826
+ return null;
827
+ }
828
+ function splitTargets(raw) {
829
+ const tokens = raw.match(/"(?:[^"\\]|\\.)*"|[^\s,]+/g) ?? [];
830
+ return tokens.map((token) => token.trim()).filter((token) => token.length > 0 && !token.startsWith('"'));
831
+ }
832
+ function splitOutsideQuotes(raw, separator) {
833
+ const parts = [];
834
+ let start = 0;
835
+ let inString = false;
836
+ let escaped = false;
837
+ for (let i = 0; i < raw.length; i++) {
838
+ const ch = raw[i];
839
+ if (escaped) {
840
+ escaped = false;
841
+ continue;
842
+ }
843
+ if (inString && ch === "\\") {
844
+ escaped = true;
845
+ continue;
846
+ }
847
+ if (ch === '"') {
848
+ inString = !inString;
849
+ continue;
850
+ }
851
+ if (!inString && ch === separator && /\s/.test(raw[i - 1] ?? "") && /\s/.test(raw[i + 1] ?? "")) {
852
+ parts.push(raw.slice(start, i).trim());
853
+ start = i + 1;
854
+ }
855
+ }
856
+ parts.push(raw.slice(start).trim());
857
+ return parts.filter(Boolean);
858
+ }
859
+ function stripCueProps(raw, keys) {
860
+ let inString = false;
861
+ let escaped = false;
862
+ for (let i = 0; i < raw.length; i++) {
863
+ const ch = raw[i];
864
+ if (escaped) {
865
+ escaped = false;
866
+ continue;
867
+ }
868
+ if (inString && ch === "\\") {
869
+ escaped = true;
870
+ continue;
871
+ }
872
+ if (ch === '"') {
873
+ inString = !inString;
874
+ continue;
875
+ }
876
+ if (inString || !/\s/.test(ch)) continue;
877
+ const rest = raw.slice(i + 1);
878
+ if (keys.some((key) => rest.startsWith(`${key}=`))) return raw.slice(0, i).trim();
879
+ }
880
+ return raw.trim();
881
+ }
882
+ function tokenizeFlowChain(line) {
883
+ const parts = [];
884
+ let current = "";
885
+ let inString = false;
886
+ let escaped = false;
887
+ for (let i = 0; i < line.length; i++) {
888
+ const ch = line[i];
889
+ if (inString) {
890
+ current += ch;
891
+ if (escaped) escaped = false;
892
+ else if (ch === "\\") escaped = true;
893
+ else if (ch === '"') inString = false;
894
+ continue;
895
+ }
896
+ if (ch === '"') {
897
+ inString = true;
898
+ current += ch;
899
+ continue;
900
+ }
901
+ const op = line.slice(i, i + 2);
902
+ if (op === "->" || op === "<-" || op === "~>" || op === "--") {
903
+ if (current.trim()) parts.push(current.trim());
904
+ parts.push(op);
905
+ current = "";
906
+ i += 1;
907
+ continue;
908
+ }
909
+ current += ch;
910
+ }
911
+ if (current.trim()) parts.push(current.trim());
912
+ return parts;
913
+ }
914
+ function parseFlowChain(line, lineNo) {
915
+ const segments = [];
916
+ const parts = tokenizeFlowChain(line);
917
+ if (parts.length < 3) {
918
+ throw new ParseError(`expected flow chain like A -> B "label"`, lineNo);
919
+ }
920
+ let i = 0;
921
+ let from = splitTargetLabel(parts[i++], lineNo).node;
922
+ while (i < parts.length) {
923
+ const opToken = parts[i++];
924
+ const op = EDGE_OPERATORS[opToken];
925
+ if (!op) throw new ParseError(`unknown flow operator '${opToken}'`, lineNo);
926
+ if (i >= parts.length) throw new ParseError(`expected target after '${opToken}'`, lineNo);
927
+ const { node: to, label } = splitTargetLabel(parts[i++], lineNo);
928
+ if (!to) throw new ParseError(`expected target node after '${opToken}'`, lineNo);
929
+ segments.push({
930
+ from: op === "response" ? to : from,
931
+ op,
932
+ to: op === "response" ? from : to,
933
+ label
934
+ });
935
+ from = op === "response" ? from : to;
936
+ }
937
+ return segments;
938
+ }
939
+ function splitTargetLabel(token, lineNo) {
940
+ const quoteIdx = token.indexOf('"');
941
+ if (quoteIdx < 0) return { node: token.trim() };
942
+ const parsed = parseStringToken(token.slice(quoteIdx));
943
+ if (!parsed) throw new ParseError("unterminated string in flow label", lineNo);
944
+ const before = token.slice(0, quoteIdx).trim();
945
+ const node = before || parsed.rest.trim();
946
+ return { node, label: parsed.value };
947
+ }
948
+ function parseCueLine(line, lineNo) {
949
+ const trimmed = line.trim();
950
+ const props = parseProps(trimmed);
951
+ if (/^@\+?\d/.test(trimmed) || /^\w[\w.-]*\.\w+\(/.test(trimmed) || /^camera\./.test(trimmed)) {
952
+ throw new ParseError(
953
+ "unsupported timeline command; use beat cues like show, frame, focus, glow, and flow lines",
954
+ lineNo
955
+ );
956
+ }
957
+ const parallelParts = splitOutsideQuotes(trimmed, "&");
958
+ if (parallelParts.length > 1) {
959
+ return {
960
+ kind: "parallel",
961
+ cues: parallelParts.map((p, idx) => parseCueLine(p, lineNo + idx * 1e-3)),
962
+ line: lineNo
963
+ };
964
+ }
965
+ if (FLOW_OP_RE.test(trimmed)) {
966
+ const chainPart = stripCueProps(trimmed, ["dur", "stagger", "color", "strength", "zoom", "after"]);
967
+ return {
968
+ kind: "flow",
969
+ segments: parseFlowChain(chainPart, lineNo),
970
+ dur: typeof props.dur === "number" ? props.dur : void 0,
971
+ line: lineNo
972
+ };
973
+ }
974
+ const [head, ...rest] = trimmed.split(/\s+/);
975
+ const rawKeyword = head.toLowerCase();
976
+ const keyword = CUE_ALIASES[rawKeyword] ?? rawKeyword;
977
+ if (keyword === "show" || keyword === "hide") {
978
+ const targetRaw = stripCueProps(rest.join(" "), ["dur", "stagger"]);
979
+ return {
980
+ kind: keyword,
981
+ targets: splitTargets(targetRaw),
982
+ stagger: typeof props.stagger === "number" ? props.stagger : void 0,
983
+ dur: typeof props.dur === "number" ? props.dur : void 0,
984
+ line: lineNo
985
+ };
986
+ }
987
+ if (keyword === "glow") {
988
+ const targetRaw = stripCueProps(rest.join(" "), ["color", "strength", "dur"]);
989
+ return {
990
+ kind: "glow",
991
+ targets: splitTargets(targetRaw),
992
+ color: typeof props.color === "string" ? props.color : void 0,
993
+ strength: typeof props.strength === "number" ? props.strength : void 0,
994
+ dur: typeof props.dur === "number" ? props.dur : void 0,
995
+ line: lineNo
996
+ };
997
+ }
998
+ if (keyword === "focus") {
999
+ const targetRaw = stripCueProps(rest.join(" "), ["zoom", "dur"]);
1000
+ return {
1001
+ kind: "focus",
1002
+ targets: splitTargets(targetRaw),
1003
+ zoom: typeof props.zoom === "number" ? props.zoom : void 0,
1004
+ dur: typeof props.dur === "number" ? props.dur : void 0,
1005
+ line: lineNo
1006
+ };
1007
+ }
1008
+ if (keyword === "frame") {
1009
+ const targetRaw = stripCueProps(rest.join(" "), ["zoom", "dur"]);
1010
+ const targets = splitTargets(targetRaw);
1011
+ if (targets.length === 0) throw new ParseError(`expected frame target`, lineNo);
1012
+ return {
1013
+ kind: "frame",
1014
+ targets,
1015
+ zoom: typeof props.zoom === "number" ? props.zoom : void 0,
1016
+ dur: typeof props.dur === "number" ? props.dur : void 0,
1017
+ line: lineNo
1018
+ };
1019
+ }
1020
+ if (keyword === "use") {
1021
+ const call = rest.join(" ");
1022
+ const m = call.match(/^(\w+)\s*\((.*)\)\s*$/);
1023
+ if (!m) throw new ParseError(`expected use pattern(args)`, lineNo);
1024
+ const args = {};
1025
+ if (m[2].trim()) {
1026
+ for (const part of m[2].split(",")) {
1027
+ const trimmed2 = part.trim();
1028
+ if (trimmed2.includes("=")) {
1029
+ const [k, v] = trimmed2.split("=").map((s) => s.trim());
1030
+ if (!k || !v) throw new ParseError(`invalid use argument '${part}'`, lineNo);
1031
+ args[k] = v;
1032
+ } else {
1033
+ args[`__pos_${Object.keys(args).length}`] = trimmed2;
1034
+ }
1035
+ }
1036
+ }
1037
+ return { kind: "use", pattern: m[1], args, line: lineNo };
1038
+ }
1039
+ throw new ParseError(`unknown cue '${head}'; use show, hide, glow, focus, frame, or use`, lineNo);
1040
+ }
1041
+ function expandPatternCues(cues, patterns, line) {
1042
+ const out = [];
1043
+ for (const cue of cues) {
1044
+ if (cue.kind === "use") {
1045
+ const pat = patterns[cue.pattern];
1046
+ if (!pat) throw new ParseError(`unknown pattern '${cue.pattern}'`, line);
1047
+ const substituted = pat.body.map((c) => substitutePatternCue(c, pat.params, cue.args));
1048
+ out.push(...substituted);
1049
+ continue;
1050
+ }
1051
+ if (cue.kind === "parallel") {
1052
+ out.push({ ...cue, cues: expandPatternCues(cue.cues, patterns, line) });
1053
+ continue;
1054
+ }
1055
+ out.push(cue);
1056
+ }
1057
+ return out;
1058
+ }
1059
+ function substitutePatternCue(cue, params, args) {
1060
+ const positional = Object.keys(args).filter((k) => k.startsWith("__pos_")).sort().map((k) => args[k]);
1061
+ const resolvedArgs = { ...args };
1062
+ params.forEach((p, i) => {
1063
+ if (resolvedArgs[p] === void 0 && positional[i]) resolvedArgs[p] = positional[i];
1064
+ });
1065
+ for (const key of Object.keys(resolvedArgs)) {
1066
+ if (key.startsWith("__pos_")) delete resolvedArgs[key];
1067
+ }
1068
+ const sub = (s) => {
1069
+ for (const p of params) {
1070
+ if (resolvedArgs[p]) s = s.replaceAll(`$${p}`, resolvedArgs[p]);
1071
+ }
1072
+ return s;
1073
+ };
1074
+ if (cue.kind === "flow") {
1075
+ return {
1076
+ ...cue,
1077
+ segments: cue.segments.map((seg) => ({
1078
+ ...seg,
1079
+ from: sub(seg.from),
1080
+ to: sub(seg.to),
1081
+ label: seg.label ? sub(seg.label) : void 0
1082
+ }))
1083
+ };
1084
+ }
1085
+ if (cue.kind === "show" || cue.kind === "hide" || cue.kind === "glow" || cue.kind === "focus" || cue.kind === "frame") {
1086
+ return { ...cue, targets: cue.targets.map(sub) };
1087
+ }
1088
+ if (cue.kind === "parallel") {
1089
+ return { ...cue, cues: cue.cues.map((c) => substitutePatternCue(c, params, args)) };
1090
+ }
1091
+ return cue;
1092
+ }
1093
+ function readBlocks(lines) {
1094
+ const blocks = [];
1095
+ for (let i = 0; i < lines.length; i++) {
1096
+ const raw = lines[i];
1097
+ if (!raw.trim() || raw.trim().startsWith("//")) continue;
1098
+ const indent = raw.match(/^\s*/)?.[0].length ?? 0;
1099
+ blocks.push({ line: i + 1, indent, text: raw.trim() });
1100
+ }
1101
+ return blocks;
1102
+ }
1103
+ function readIndentedBody(blocks, startIdx, parentIndent) {
1104
+ const body = [];
1105
+ let i = startIdx;
1106
+ while (i < blocks.length) {
1107
+ if (blocks[i].indent <= parentIndent) break;
1108
+ body.push(blocks[i]);
1109
+ i++;
1110
+ }
1111
+ return { body, nextIdx: i };
1112
+ }
1113
+ var PLAYER_KEYWORDS = [...PLAYER_FLAT_KEYS].sort((a, b) => b.length - a.length).join("|");
1114
+ var TOP_LEVEL_KEYWORDS_RE = new RegExp(
1115
+ `^(scene|player|layout|pattern|group|annotation|edge|beat|var|style|${PLAYER_KEYWORDS})\\b`
1116
+ );
1117
+ var PLAYER_DIRECTIVE_RE = new RegExp(`^(${PLAYER_KEYWORDS})\\b(?:\\s*[:=]?\\s*(.+))?$`);
1118
+ var PLAYER_GROUP_RE = /^(playback|controls|interaction|chrome)\s*:\s*$/;
1119
+ var PLAYER_SETTING_RE = /^(\w+)(?:\s*[:=]\s*|\s+)?(.*)$/;
1120
+ var PLAYER_FLAT_KEY_SET = new Set(PLAYER_FLAT_KEYS);
1121
+ function mirrorLegacySceneMeta(meta) {
1122
+ const player = meta.player;
1123
+ if (!player) return meta;
1124
+ const { playback, controls, interaction, chrome } = player;
1125
+ if (playback?.autoplay !== void 0) meta.autoplay = playback.autoplay;
1126
+ if (playback?.loop !== void 0) meta.loop = playback.loop;
1127
+ if (playback?.rate !== void 0) meta.playbackRate = playback.rate;
1128
+ if (controls) meta.controls = Object.values(controls).some(Boolean);
1129
+ if (interaction) {
1130
+ meta.interactiveViewport = Boolean(interaction.zoom ?? true) || Boolean(interaction.pan ?? true);
1131
+ }
1132
+ if (chrome?.badge !== void 0) meta.copyright = chrome.badge;
1133
+ if (chrome?.progress === "none") meta.progressColor = "none";
1134
+ else if (chrome?.progressColor !== void 0) meta.progressColor = chrome.progressColor;
1135
+ return meta;
1136
+ }
1137
+ function isTopLevelStatement(line) {
1138
+ if (line === "}") return true;
1139
+ if (TOP_LEVEL_KEYWORDS_RE.test(line)) return true;
1140
+ const nodeMatch = line.match(/^(\w[\w.-]*)\s+(\w[\w.-]*)/);
1141
+ if (!nodeMatch) return false;
1142
+ const kind = canonicalNodeKind(nodeMatch[1].toLowerCase());
1143
+ return NODE_KINDS.has(kind);
1144
+ }
1145
+ function readColonBody(blocks, startIdx, parentIndent, diagnostics, context, headerLine) {
1146
+ const indented = readIndentedBody(blocks, startIdx, parentIndent);
1147
+ if (indented.body.length > 0 || startIdx >= blocks.length) {
1148
+ return indented;
1149
+ }
1150
+ if (isTopLevelStatement(blocks[startIdx].text)) {
1151
+ return indented;
1152
+ }
1153
+ const body = [];
1154
+ let i = startIdx;
1155
+ while (i < blocks.length) {
1156
+ const block = blocks[i];
1157
+ if (block.indent < parentIndent) break;
1158
+ if (block.indent === parentIndent && isTopLevelStatement(block.text)) break;
1159
+ body.push(block);
1160
+ i++;
1161
+ }
1162
+ return { body, nextIdx: i };
1163
+ }
1164
+ function readBody(blocks, startIdx, parentIndent, braceDelimited, diagnostics = [], context = "block", headerLine = 1) {
1165
+ if (!braceDelimited) {
1166
+ return readColonBody(blocks, startIdx, parentIndent, diagnostics, context, headerLine);
1167
+ }
1168
+ const body = [];
1169
+ let i = startIdx;
1170
+ while (i < blocks.length) {
1171
+ if (blocks[i].text === "}") return { body, nextIdx: i + 1 };
1172
+ body.push(blocks[i]);
1173
+ i++;
1174
+ }
1175
+ return { body, nextIdx: i };
1176
+ }
1177
+ function normalizeCueBlocks(blocks) {
1178
+ const normalized = [];
1179
+ for (const block of blocks) {
1180
+ if (block.text.startsWith("& ")) {
1181
+ const prev = normalized[normalized.length - 1];
1182
+ if (!prev) throw new ParseError(`parallel continuation must follow a cue`, block.line);
1183
+ prev.text = `${prev.text} ${block.text}`;
1184
+ continue;
1185
+ }
1186
+ normalized.push({ ...block });
1187
+ }
1188
+ return normalized;
1189
+ }
1190
+ function unsupportedSyntaxMessage(line) {
1191
+ if (/^actor\s+/.test(line) || /\bfigure\s*\(/.test(line) || /\bbox\s*\(/.test(line) || /\bat\s*\(/.test(line)) {
1192
+ return "unsupported manual drawing syntax; declare architecture nodes like service API, cache Redis, and database DB";
1193
+ }
1194
+ if (/^@\+?\d/.test(line)) {
1195
+ return "unsupported timeline command; put flow and cue lines inside beat blocks";
1196
+ }
1197
+ if (/^camera\b/.test(line)) {
1198
+ return "unsupported camera command; use frame NodeOrGroup zoom=... inside a beat";
1199
+ }
1200
+ return null;
1201
+ }
1202
+ var RESERVED_VAR_NAMES = /* @__PURE__ */ new Set(["nodes", "title", "edges"]);
1203
+ function extractVars(blocks, diagnostics) {
1204
+ const vars = /* @__PURE__ */ new Map();
1205
+ const rest = [];
1206
+ for (const block of blocks) {
1207
+ if (!/^var\b/.test(block.text)) {
1208
+ rest.push(block);
1209
+ continue;
1210
+ }
1211
+ const m = block.text.match(/^var\s+([A-Za-z_]\w*)\s*=\s*(.+)$/);
1212
+ if (!m) throw new ParseError("expected var name = value", block.line);
1213
+ const name = m[1];
1214
+ if (RESERVED_VAR_NAMES.has(name)) {
1215
+ diagnostics.push({ severity: "warning", message: `var '${name}' shadows a reserved selector and was ignored`, line: block.line });
1216
+ continue;
1217
+ }
1218
+ const raw = m[2].trim();
1219
+ const str = parseStringToken(raw);
1220
+ vars.set(name, str && str.rest === "" ? str.value : raw);
1221
+ }
1222
+ return { vars, rest };
1223
+ }
1224
+ function applyVars(blocks, vars) {
1225
+ if (vars.size === 0) return blocks;
1226
+ return blocks.map((block) => {
1227
+ let text = block.text;
1228
+ for (const [name, value] of vars) {
1229
+ text = text.replace(new RegExp(`\\$${name}(?![\\w])`, "g"), value);
1230
+ }
1231
+ return { ...block, text };
1232
+ });
1233
+ }
1234
+ function pushWarning(diagnostics, seen, line, message) {
1235
+ const key = `${line}:${message}`;
1236
+ if (seen.has(key)) return;
1237
+ seen.add(key);
1238
+ diagnostics.push({ severity: "warning", message, line });
1239
+ }
1240
+ function visitCues(cues, visit) {
1241
+ for (const cue of cues) {
1242
+ visit(cue);
1243
+ if (cue.kind === "parallel") visitCues(cue.cues, visit);
1244
+ }
1245
+ }
1246
+ function validateReferences(ast) {
1247
+ const seen = /* @__PURE__ */ new Set();
1248
+ const hasNode = (id) => Boolean(ast.nodes[id]);
1249
+ const hasGroup = (id) => Boolean(ast.groups[id]);
1250
+ const isKnownTarget = (target) => {
1251
+ if (RESERVED_SELECTORS.has(target)) return true;
1252
+ if (target.startsWith("$")) return hasGroup(target.slice(1));
1253
+ return hasNode(target) || hasGroup(target);
1254
+ };
1255
+ for (const group of Object.values(ast.groups)) {
1256
+ for (const member of group.members) {
1257
+ if (!hasNode(member)) {
1258
+ pushWarning(ast.diagnostics, seen, group.line, `group '${group.id}' references unknown node '${member}'`);
1259
+ }
1260
+ }
1261
+ }
1262
+ for (const node of Object.values(ast.nodes)) {
1263
+ if (node.style && !ast.styles[node.style]) {
1264
+ pushWarning(ast.diagnostics, seen, node.line, `node '${node.id}' references unknown style '${node.style}'`);
1265
+ }
1266
+ }
1267
+ const validateFlowEndpoint = (line, endpoint) => {
1268
+ if (!hasNode(endpoint)) {
1269
+ pushWarning(ast.diagnostics, seen, line, `flow references unknown node '${endpoint}'`);
1270
+ }
1271
+ };
1272
+ for (const edge of ast.edges) {
1273
+ validateFlowEndpoint(edge.line, edge.from);
1274
+ validateFlowEndpoint(edge.line, edge.to);
1275
+ }
1276
+ for (const ann of ast.annotations) {
1277
+ if (ann.target && !hasNode(ann.target)) {
1278
+ pushWarning(ast.diagnostics, seen, ann.line, `annotation references unknown target '${ann.target}'`);
1279
+ }
1280
+ }
1281
+ if (ast.annotations.length > 2) {
1282
+ pushWarning(ast.diagnostics, seen, ast.annotations[2].line, "more than 2 annotation callouts; editorial diagrams should use \u22642");
1283
+ }
1284
+ for (const beat of ast.beats) {
1285
+ visitCues(beat.cues, (cue) => {
1286
+ if (cue.kind === "flow") {
1287
+ for (const segment of cue.segments) {
1288
+ validateFlowEndpoint(cue.line, segment.from);
1289
+ validateFlowEndpoint(cue.line, segment.to);
1290
+ }
1291
+ return;
1292
+ }
1293
+ if (cue.kind === "show" || cue.kind === "hide" || cue.kind === "glow" || cue.kind === "focus" || cue.kind === "frame") {
1294
+ for (const target of cue.targets) {
1295
+ if (!isKnownTarget(target)) {
1296
+ pushWarning(ast.diagnostics, seen, cue.line, `${cue.kind} references unknown target '${target}'`);
1297
+ }
1298
+ }
1299
+ }
1300
+ });
1301
+ }
1302
+ }
1303
+ function detectFlowCycles(ast) {
1304
+ const dtype = ast.meta.type ?? "architecture";
1305
+ if (dtype !== "architecture" && dtype !== "flowchart") return;
1306
+ const seen = /* @__PURE__ */ new Set();
1307
+ const adj = /* @__PURE__ */ new Map();
1308
+ const ensure = (id) => {
1309
+ if (!adj.has(id)) adj.set(id, []);
1310
+ };
1311
+ for (const id of Object.keys(ast.nodes)) ensure(id);
1312
+ const addEdge = (from, to, line) => {
1313
+ if (from === to) return;
1314
+ if (!ast.nodes[from] || !ast.nodes[to]) return;
1315
+ ensure(from);
1316
+ adj.get(from).push({ to, line });
1317
+ };
1318
+ for (const edge of ast.edges) {
1319
+ if (edge.kind !== "response") addEdge(edge.from, edge.to, edge.line);
1320
+ }
1321
+ for (const beat of ast.beats) {
1322
+ visitCues(beat.cues, (cue) => {
1323
+ if (cue.kind !== "flow") return;
1324
+ for (const segment of cue.segments) {
1325
+ if (segment.op !== "response") addEdge(segment.from, segment.to, cue.line);
1326
+ }
1327
+ });
1328
+ }
1329
+ const WHITE = 0;
1330
+ const GRAY = 1;
1331
+ const BLACK = 2;
1332
+ const color = /* @__PURE__ */ new Map();
1333
+ for (const id of adj.keys()) color.set(id, WHITE);
1334
+ const stack = [];
1335
+ const dfs = (node) => {
1336
+ color.set(node, GRAY);
1337
+ stack.push(node);
1338
+ for (const { to, line } of adj.get(node) ?? []) {
1339
+ if (color.get(to) === GRAY) {
1340
+ const idx = stack.indexOf(to);
1341
+ const cyclePath = [...stack.slice(idx), to].join(" -> ");
1342
+ pushWarning(
1343
+ ast.diagnostics,
1344
+ seen,
1345
+ line,
1346
+ `flow cycle detected: ${cyclePath} \u2014 if '${to}' is receiving a reply/return value here, use '<-' for this edge instead of '->'/'~>'/'--' (an unmarked cycle can crush the ranked layout and overlap nodes)`
1347
+ );
1348
+ } else if (color.get(to) === WHITE) {
1349
+ dfs(to);
1350
+ }
1351
+ }
1352
+ stack.pop();
1353
+ color.set(node, BLACK);
1354
+ };
1355
+ for (const id of adj.keys()) {
1356
+ if (color.get(id) === WHITE) dfs(id);
1357
+ }
1358
+ }
1359
+ function parse(source, opts = {}) {
1360
+ const lines = source.replace(/\r\n/g, "\n").split("\n");
1361
+ const diagnostics = [];
1362
+ const meta = {
1363
+ width: 1280,
1364
+ height: 720,
1365
+ fps: 60,
1366
+ theme: "paper",
1367
+ direction: "LR"
1368
+ };
1369
+ const styles = {};
1370
+ const nodes = {};
1371
+ const edges = [];
1372
+ const groups = {};
1373
+ const patterns = {};
1374
+ const beats = [];
1375
+ const annotations = [];
1376
+ let title = "";
1377
+ let edgeCounter = 0;
1378
+ let annotationCounter = 0;
1379
+ const rawBlocks = readBlocks(lines.map(stripComment));
1380
+ const { vars, rest } = extractVars(rawBlocks, diagnostics);
1381
+ const blocks = applyVars(rest, vars);
1382
+ let i = 0;
1383
+ while (i < blocks.length) {
1384
+ const block = blocks[i];
1385
+ const line = block.text;
1386
+ const lineNo = block.line;
1387
+ const unsupportedMessage = unsupportedSyntaxMessage(line);
1388
+ if (unsupportedMessage) throw new ParseError(unsupportedMessage, lineNo);
1389
+ if (/^player\s*:\s*$/.test(line)) {
1390
+ const { body, nextIdx } = readIndentedBody(blocks, i + 1, block.indent);
1391
+ if (body.length === 0) throw new ParseError("player block requires at least one setting", lineNo);
1392
+ const player = meta.player ??= {};
1393
+ const applySetting = (scope, setting) => {
1394
+ const match = setting.text.match(PLAYER_SETTING_RE);
1395
+ const error = applyPlayerSetting(player, scope, match?.[1] ?? "", match?.[2] ?? "");
1396
+ if (error) diagnostics.push({ severity: "warning", message: error, line: setting.line });
1397
+ };
1398
+ let settingIdx = 0;
1399
+ while (settingIdx < body.length) {
1400
+ const setting = body[settingIdx];
1401
+ const groupMatch = setting.text.match(PLAYER_GROUP_RE);
1402
+ if (groupMatch) {
1403
+ const scope = groupMatch[1];
1404
+ const { body: childBody, nextIdx: childNextIdx } = readIndentedBody(body, settingIdx + 1, setting.indent);
1405
+ if (childBody.length === 0) {
1406
+ throw new ParseError(`player ${scope} block requires at least one setting`, setting.line);
1407
+ }
1408
+ for (const child of childBody) applySetting(scope, child);
1409
+ settingIdx = childNextIdx;
1410
+ continue;
1411
+ }
1412
+ applySetting("player", setting);
1413
+ settingIdx++;
1414
+ }
1415
+ i = nextIdx;
1416
+ continue;
1417
+ }
1418
+ if (line.startsWith("scene")) {
1419
+ const rest2 = line.slice(5).trim();
1420
+ if (line.endsWith("{")) {
1421
+ throw new ParseError(`nested scene blocks are not supported; use one scene with multiple beat blocks`, lineNo);
1422
+ }
1423
+ let remainder = rest2;
1424
+ const str = parseStringToken(rest2);
1425
+ if (str) {
1426
+ title = str.value;
1427
+ remainder = str.rest;
1428
+ }
1429
+ const inlineLayout = remainder.match(/\blayout\s+(LR|RL|TB|BT)\b/i);
1430
+ if (inlineLayout) {
1431
+ meta.direction = inlineLayout[1].toUpperCase();
1432
+ remainder = remainder.replace(/\blayout\s+(LR|RL|TB|BT)\b/i, " ");
1433
+ }
1434
+ const props = parseProps(remainder);
1435
+ for (const [k, v] of Object.entries(props)) {
1436
+ if (!SCENE_KEYS.has(k)) {
1437
+ diagnostics.push({ severity: "warning", message: `unknown scene property '${k}'`, line: lineNo });
1438
+ continue;
1439
+ }
1440
+ if (k === "width") {
1441
+ meta.width = Number(v);
1442
+ meta.explicitWidth = true;
1443
+ } else if (k === "height") {
1444
+ meta.height = Number(v);
1445
+ meta.explicitHeight = true;
1446
+ } else if (k === "fps") {
1447
+ meta.fps = Number(v);
1448
+ } else if (k === "duration") meta.duration = Number(v);
1449
+ else if (k === "theme") meta.theme = String(v);
1450
+ else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
1451
+ else if (PLAYER_FLAT_KEY_SET.has(k)) {
1452
+ const error = applyPlayerSetting(meta.player ??= {}, "player", k, String(v));
1453
+ if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
1454
+ } else if (k === "type") {
1455
+ const t = String(v).toLowerCase();
1456
+ if (!DIAGRAM_TYPES.has(t)) {
1457
+ diagnostics.push({ severity: "warning", message: `unknown diagram type '${v}'`, line: lineNo });
1458
+ } else {
1459
+ meta.type = t;
1460
+ if (t === "flowchart" && !props.direction && !props.layout && !inlineLayout) {
1461
+ meta.direction = "TB";
1462
+ }
1463
+ }
1464
+ }
1465
+ }
1466
+ i++;
1467
+ continue;
1468
+ }
1469
+ const directiveMatch = line.match(PLAYER_DIRECTIVE_RE);
1470
+ if (directiveMatch) {
1471
+ const error = applyPlayerSetting(meta.player ??= {}, "player", directiveMatch[1], directiveMatch[2] ?? "");
1472
+ if (error) diagnostics.push({ severity: "warning", message: error, line: lineNo });
1473
+ i++;
1474
+ continue;
1475
+ }
1476
+ if (/^layout\s+(LR|RL|TB|BT)\b/i.test(line)) {
1477
+ meta.direction = line.split(/\s+/)[1].toUpperCase();
1478
+ i++;
1479
+ continue;
1480
+ }
1481
+ if (line.startsWith("style ")) {
1482
+ const m = line.match(/^style\s+(\w+)\s*=\s*(.+)$/);
1483
+ if (!m) throw new ParseError(`expected style name = props`, lineNo);
1484
+ styles[m[1]] = { name: m[1], props: parseProps(m[2]), line: lineNo };
1485
+ i++;
1486
+ continue;
1487
+ }
1488
+ if (line.startsWith("pattern ")) {
1489
+ const m = line.match(/^pattern\s+(\w+)\s*\(([^)]*)\)\s*(?::|\{)\s*$/);
1490
+ if (!m) throw new ParseError(`expected pattern name(params):`, lineNo);
1491
+ const params = m[2].trim() ? m[2].split(",").map((p) => p.trim()) : [];
1492
+ const { body, nextIdx } = readBody(
1493
+ blocks,
1494
+ i + 1,
1495
+ block.indent,
1496
+ line.endsWith("{"),
1497
+ diagnostics,
1498
+ `pattern '${m[1]}'`,
1499
+ lineNo
1500
+ );
1501
+ const cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
1502
+ patterns[m[1]] = { name: m[1], params, body: cues, line: lineNo };
1503
+ i = nextIdx;
1504
+ continue;
1505
+ }
1506
+ if (line.startsWith("group ")) {
1507
+ const inline = line.match(/^group\s+(\w+)(?:\s+"([^"]*)")?\s*:\s*(.+)$/);
1508
+ if (inline) {
1509
+ groups[inline[1]] = {
1510
+ id: inline[1],
1511
+ label: inline[2],
1512
+ members: splitTargets(inline[3]),
1513
+ props: {},
1514
+ line: lineNo
1515
+ };
1516
+ i++;
1517
+ continue;
1518
+ }
1519
+ const header = line.match(/^group\s+(\w+)(?:\s+"([^"]*)")?\s*:\s*$/);
1520
+ if (!header) throw new ParseError(`expected group name: A B C`, lineNo);
1521
+ const { body, nextIdx } = readColonBody(
1522
+ blocks,
1523
+ i + 1,
1524
+ block.indent,
1525
+ diagnostics,
1526
+ `group '${header[1]}'`,
1527
+ lineNo
1528
+ );
1529
+ const members = body.flatMap((b) => splitTargets(b.text));
1530
+ if (members.length === 0) throw new ParseError(`group '${header[1]}' has no members`, lineNo);
1531
+ groups[header[1]] = {
1532
+ id: header[1],
1533
+ label: header[2],
1534
+ members,
1535
+ props: {},
1536
+ line: lineNo
1537
+ };
1538
+ i = nextIdx;
1539
+ continue;
1540
+ }
1541
+ if (line.startsWith("annotation ")) {
1542
+ const str = parseStringToken(line.slice("annotation ".length).trim());
1543
+ if (!str) throw new ParseError(`expected annotation "text" with optional target= position=`, lineNo);
1544
+ const props = parseProps(str.rest);
1545
+ const target = typeof props.target === "string" ? props.target : void 0;
1546
+ const position = typeof props.position === "string" ? props.position : void 0;
1547
+ annotations.push({
1548
+ id: `ann_${++annotationCounter}`,
1549
+ text: str.value,
1550
+ target,
1551
+ position,
1552
+ props,
1553
+ line: lineNo
1554
+ });
1555
+ i++;
1556
+ continue;
1557
+ }
1558
+ if (line.startsWith("edge ")) {
1559
+ const m = line.match(/^edge\s+(\w+)\s*:\s*(.+)$/);
1560
+ if (!m) throw new ParseError(`expected edge id: A -> B`, lineNo);
1561
+ const segments = parseFlowChain(m[2].split(/\s+\w+=/)[0], lineNo);
1562
+ for (const seg of segments) {
1563
+ edges.push({
1564
+ id: `edge_${++edgeCounter}`,
1565
+ kind: seg.op,
1566
+ from: seg.from,
1567
+ to: seg.to,
1568
+ label: seg.label,
1569
+ props: parseProps(m[2]),
1570
+ line: lineNo
1571
+ });
1572
+ }
1573
+ i++;
1574
+ continue;
1575
+ }
1576
+ if (line.startsWith("beat ")) {
1577
+ const m = line.match(/^beat\s+([\w.-]+)(?:\s+"([^"]*)")?\s*(?::|\{)\s*$/);
1578
+ if (!m) throw new ParseError(`expected beat name:`, lineNo);
1579
+ const { body, nextIdx } = readBody(
1580
+ blocks,
1581
+ i + 1,
1582
+ block.indent,
1583
+ line.endsWith("{"),
1584
+ diagnostics,
1585
+ `beat '${m[1]}'`,
1586
+ lineNo
1587
+ );
1588
+ let cues = normalizeCueBlocks(body).map((b) => parseCueLine(b.text, b.line));
1589
+ cues = expandPatternCues(cues, patterns, lineNo);
1590
+ beats.push({ name: m[1], label: m[2], cues, line: lineNo });
1591
+ i = nextIdx;
1592
+ continue;
1593
+ }
1594
+ const nodeMatch = line.match(/^(\w[\w.-]*)\s+(\w[\w.-]*)(.*)$/);
1595
+ if (nodeMatch) {
1596
+ const rawKind = nodeMatch[1].toLowerCase();
1597
+ const kind = canonicalNodeKind(rawKind);
1598
+ const id = nodeMatch[2];
1599
+ let remainder = nodeMatch[3].trim();
1600
+ if (!NODE_KINDS.has(kind)) {
1601
+ throw new ParseError(`unknown node kind '${rawKind}'`, lineNo);
1602
+ }
1603
+ let label = humanizeId(id);
1604
+ const str = parseStringToken(remainder);
1605
+ if (str) {
1606
+ label = str.value;
1607
+ remainder = str.rest;
1608
+ }
1609
+ const styleMatch = remainder.match(/\bstyle=(\w+)/);
1610
+ const props = parseProps(remainder);
1611
+ nodes[id] = {
1612
+ kind,
1613
+ id,
1614
+ label,
1615
+ style: styleMatch?.[1],
1616
+ props,
1617
+ line: lineNo
1618
+ };
1619
+ i++;
1620
+ continue;
1621
+ }
1622
+ if (BEAT_CUE_KEYWORDS.has(line.split(/\s+/)[0].toLowerCase()) || FLOW_OP_RE.test(line)) {
1623
+ throw new ParseError(`top-level cues must be inside a beat block`, lineNo);
1624
+ }
1625
+ throw new ParseError(`unexpected statement`, lineNo);
1626
+ }
1627
+ const ast = {
1628
+ meta: { ...mirrorLegacySceneMeta(meta), title: title || void 0 },
1629
+ styles,
1630
+ nodes,
1631
+ edges,
1632
+ groups,
1633
+ annotations,
1634
+ patterns,
1635
+ beats,
1636
+ diagnostics
1637
+ };
1638
+ validateReferences(ast);
1639
+ detectFlowCycles(ast);
1640
+ const errors = ast.diagnostics.filter((d) => d.severity === "error");
1641
+ if (errors.length) {
1642
+ throw new ParseError(errors[0].message, errors[0].line, errors[0].column);
1643
+ }
1644
+ if (!opts.parseOnly && Object.keys(nodes).length === 0 && beats.length === 0) {
1645
+ }
1646
+ return ast;
1647
+ }
1648
+
1649
+ // ../core/src/arch-lint.ts
1650
+ function matchNode(node, selector) {
1651
+ if (!selector) return true;
1652
+ if (selector.id && node.id !== selector.id) return false;
1653
+ if (selector.kindEquals && node.kind.toLowerCase() !== selector.kindEquals.toLowerCase()) return false;
1654
+ if (selector.roleEquals) {
1655
+ const role = nodeRole(node.kind);
1656
+ if (role.toLowerCase() !== selector.roleEquals.toLowerCase()) return false;
1657
+ }
1658
+ if (selector.labelContains) {
1659
+ const target = (node.label || node.id).toLowerCase();
1660
+ if (!target.includes(selector.labelContains.toLowerCase())) return false;
1661
+ }
1662
+ return true;
1663
+ }
1664
+ function matchEdge(edge, selector) {
1665
+ if (!selector) return true;
1666
+ if (selector.kind && edge.kind !== selector.kind) return false;
1667
+ if (selector.labelContains) {
1668
+ const label = (edge.label || "").toLowerCase();
1669
+ if (!label.includes(selector.labelContains.toLowerCase())) return false;
1670
+ }
1671
+ return true;
1672
+ }
1673
+ function extractAllEdges(ast) {
1674
+ const edges = [];
1675
+ const seen = /* @__PURE__ */ new Set();
1676
+ for (const edge of ast.edges) {
1677
+ const key = `${edge.from}->${edge.to}:${edge.kind}:${edge.label ?? ""}`;
1678
+ if (!seen.has(key)) {
1679
+ seen.add(key);
1680
+ edges.push({
1681
+ from: edge.from,
1682
+ to: edge.to,
1683
+ kind: edge.kind,
1684
+ label: edge.label,
1685
+ line: edge.line
1686
+ });
1687
+ }
1688
+ }
1689
+ for (const beat of ast.beats) {
1690
+ for (const cue of beat.cues) {
1691
+ if (cue.kind === "flow") {
1692
+ for (const seg of cue.segments) {
1693
+ const key = `${seg.from}->${seg.to}:${seg.op}:${seg.label ?? ""}`;
1694
+ if (!seen.has(key)) {
1695
+ seen.add(key);
1696
+ edges.push({
1697
+ from: seg.from,
1698
+ to: seg.to,
1699
+ kind: seg.op,
1700
+ label: seg.label,
1701
+ line: cue.line
1702
+ });
1703
+ }
1704
+ }
1705
+ }
1706
+ }
1707
+ }
1708
+ return edges;
1709
+ }
1710
+ function detectCycleInGraph(nodes, edges, edgeFilter) {
1711
+ const candidateEdges = edges.filter((e) => matchEdge(e, edgeFilter));
1712
+ const adj = /* @__PURE__ */ new Map();
1713
+ for (const e of candidateEdges) {
1714
+ if (!adj.has(e.from)) adj.set(e.from, []);
1715
+ adj.get(e.from).push({ to: e.to, line: e.line });
1716
+ }
1717
+ const visited = /* @__PURE__ */ new Set();
1718
+ const onStack = /* @__PURE__ */ new Set();
1719
+ const parentMap = /* @__PURE__ */ new Map();
1720
+ let foundCycle = null;
1721
+ const dfs = (curr) => {
1722
+ visited.add(curr);
1723
+ onStack.add(curr);
1724
+ for (const next of adj.get(curr) ?? []) {
1725
+ if (foundCycle) return;
1726
+ if (!visited.has(next.to)) {
1727
+ parentMap.set(next.to, curr);
1728
+ dfs(next.to);
1729
+ } else if (onStack.has(next.to)) {
1730
+ const cycle = [next.to, curr];
1731
+ let p = curr;
1732
+ while (p !== next.to && parentMap.has(p)) {
1733
+ p = parentMap.get(p);
1734
+ cycle.push(p);
1735
+ }
1736
+ foundCycle = {
1737
+ path: cycle.reverse(),
1738
+ line: next.line
1739
+ };
1740
+ return;
1741
+ }
1742
+ }
1743
+ onStack.delete(curr);
1744
+ };
1745
+ for (const node of nodes) {
1746
+ if (!visited.has(node.id)) {
1747
+ dfs(node.id);
1748
+ if (foundCycle) return foundCycle;
1749
+ }
1750
+ }
1751
+ return null;
1752
+ }
1753
+ var ARCH_RULE_PRESETS = {
1754
+ cleanArchitecture: {
1755
+ id: "clean-architecture",
1756
+ name: "Clean / Layered Architecture",
1757
+ description: "Enforce strict dependency rules: Presentation cannot directly access Data Storage",
1758
+ rules: [
1759
+ {
1760
+ id: "no-presentation-to-database",
1761
+ name: "No Direct Client DB Access",
1762
+ description: "Presentation/Client components must communicate via backend services, never directly with databases.",
1763
+ severity: "error",
1764
+ type: "cannot-connect",
1765
+ from: { roleEquals: "client" },
1766
+ to: { roleEquals: "data" }
1767
+ },
1768
+ {
1769
+ id: "no-browser-to-internal-storage",
1770
+ name: "No Direct Browser Storage Access",
1771
+ description: "Browser nodes must not connect directly to private storage buckets.",
1772
+ severity: "error",
1773
+ type: "cannot-connect",
1774
+ from: { kindEquals: "browser" },
1775
+ to: { kindEquals: "storage" }
1776
+ }
1777
+ ]
1778
+ },
1779
+ microservicesGovernance: {
1780
+ id: "microservices-governance",
1781
+ name: "Microservices Governance",
1782
+ description: "Prevent synchronous request cycles and shared database anti-patterns",
1783
+ rules: [
1784
+ {
1785
+ id: "no-sync-request-cycles",
1786
+ name: "Forbidden Request Cycle",
1787
+ description: "Synchronous request flows (->) must not form cyclic dependencies between services.",
1788
+ severity: "error",
1789
+ type: "forbidden-cycle",
1790
+ edge: { kind: "request" }
1791
+ },
1792
+ {
1793
+ id: "gateway-enforcement",
1794
+ name: "API Gateway Required",
1795
+ description: "Architecture diagrams with 3 or more services should declare an API gateway.",
1796
+ severity: "warning",
1797
+ type: "must-have-role",
1798
+ from: { roleEquals: "gateway" },
1799
+ min: 1
1800
+ }
1801
+ ]
1802
+ },
1803
+ securityBoundaries: {
1804
+ id: "security-boundaries",
1805
+ name: "Zero-Trust Security Boundaries",
1806
+ description: "Ensure external clients pass through auth and edge gateway tiers",
1807
+ rules: [
1808
+ {
1809
+ id: "auth-service-presence",
1810
+ name: "Authentication Component Presence",
1811
+ description: "Public-facing architectures should explicitly define an Auth or Identity provider.",
1812
+ severity: "info",
1813
+ type: "must-have-role",
1814
+ from: { roleEquals: "security" }
1815
+ }
1816
+ ]
1817
+ }
1818
+ };
1819
+ function validateArchitecture(ast, rules = [
1820
+ ...ARCH_RULE_PRESETS.cleanArchitecture.rules,
1821
+ ...ARCH_RULE_PRESETS.microservicesGovernance.rules
1822
+ ]) {
1823
+ const violations = [];
1824
+ const nodes = Object.values(ast.nodes);
1825
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
1826
+ const edges = extractAllEdges(ast);
1827
+ for (const rule of rules) {
1828
+ switch (rule.type) {
1829
+ case "cannot-connect": {
1830
+ for (const edge of edges) {
1831
+ const sourceNode = nodeMap.get(edge.from);
1832
+ const targetNode = nodeMap.get(edge.to);
1833
+ if (!sourceNode || !targetNode) continue;
1834
+ if (matchNode(sourceNode, rule.from) && matchNode(targetNode, rule.to) && matchEdge(edge, rule.edge)) {
1835
+ violations.push({
1836
+ ruleId: rule.id,
1837
+ ruleName: rule.name,
1838
+ message: rule.description || `Node "${sourceNode.id}" is forbidden from connecting to "${targetNode.id}"`,
1839
+ severity: rule.severity,
1840
+ nodeIds: [sourceNode.id, targetNode.id],
1841
+ edgeKeys: [`${edge.from}->${edge.to}`],
1842
+ line: edge.line
1843
+ });
1844
+ }
1845
+ }
1846
+ break;
1847
+ }
1848
+ case "must-connect": {
1849
+ const sourceNodes = nodes.filter((n) => matchNode(n, rule.from));
1850
+ for (const src of sourceNodes) {
1851
+ const hasMatchingConnection = edges.some((edge) => {
1852
+ if (edge.from !== src.id) return false;
1853
+ const targetNode = nodeMap.get(edge.to);
1854
+ return targetNode ? matchNode(targetNode, rule.to) && matchEdge(edge, rule.edge) : false;
1855
+ });
1856
+ if (!hasMatchingConnection) {
1857
+ violations.push({
1858
+ ruleId: rule.id,
1859
+ ruleName: rule.name,
1860
+ message: rule.description || `Node "${src.id}" must connect to a matching downstream component`,
1861
+ severity: rule.severity,
1862
+ nodeIds: [src.id],
1863
+ edgeKeys: [],
1864
+ line: src.line
1865
+ });
1866
+ }
1867
+ }
1868
+ break;
1869
+ }
1870
+ case "forbidden-cycle": {
1871
+ const cycleInfo = detectCycleInGraph(nodes, edges, rule.edge);
1872
+ if (cycleInfo) {
1873
+ violations.push({
1874
+ ruleId: rule.id,
1875
+ ruleName: rule.name,
1876
+ message: `${rule.description} (Cycle path: ${cycleInfo.path.join(" -> ")})`,
1877
+ severity: rule.severity,
1878
+ nodeIds: Array.from(new Set(cycleInfo.path)),
1879
+ edgeKeys: [],
1880
+ line: cycleInfo.line
1881
+ });
1882
+ }
1883
+ break;
1884
+ }
1885
+ case "must-have-role": {
1886
+ const matching = nodes.filter((n) => matchNode(n, rule.from));
1887
+ const min = rule.min ?? 1;
1888
+ if (matching.length < min) {
1889
+ violations.push({
1890
+ ruleId: rule.id,
1891
+ ruleName: rule.name,
1892
+ message: rule.description,
1893
+ severity: rule.severity,
1894
+ nodeIds: [],
1895
+ edgeKeys: [],
1896
+ line: 1
1897
+ });
1898
+ }
1899
+ break;
1900
+ }
1901
+ case "role-count-limit": {
1902
+ const matching = nodes.filter((n) => matchNode(n, rule.from));
1903
+ if (rule.max !== void 0 && matching.length > rule.max) {
1904
+ violations.push({
1905
+ ruleId: rule.id,
1906
+ ruleName: rule.name,
1907
+ message: `${rule.description} (Found ${matching.length}, maximum allowed is ${rule.max})`,
1908
+ severity: rule.severity,
1909
+ nodeIds: matching.map((n) => n.id),
1910
+ edgeKeys: [],
1911
+ line: matching[0]?.line ?? 1
1912
+ });
1913
+ }
1914
+ break;
1915
+ }
1916
+ }
1917
+ }
1918
+ return violations;
1919
+ }
1920
+
1921
+ // ../core/src/classifier.ts
1922
+ var TECH_CATALOG = [
1923
+ // ── DATABASES ───────────────────────────────────────────────────────
1924
+ {
1925
+ patterns: [/\b(postgres(ql)?|psql|cockroach(db)?|timescale)\b/i],
1926
+ kind: "database",
1927
+ role: "database",
1928
+ badge: "SQL"
1929
+ },
1930
+ {
1931
+ patterns: [/\b(mysql|mariadb|aurora|planetscale)\b/i],
1932
+ kind: "database",
1933
+ role: "database",
1934
+ badge: "MySQL"
1935
+ },
1936
+ {
1937
+ patterns: [/\b(mongodb|mongo|documentdb|couchdb)\b/i],
1938
+ kind: "database",
1939
+ role: "database",
1940
+ badge: "Document"
1941
+ },
1942
+ {
1943
+ patterns: [/\b(dynamodb|cassandra|scylla|hbase)\b/i],
1944
+ kind: "database",
1945
+ role: "database",
1946
+ badge: "NoSQL"
1947
+ },
1948
+ {
1949
+ patterns: [/\b(redis|memcached?|elasticache|dragonfly|valkey)\b/i],
1950
+ kind: "cache",
1951
+ role: "cache",
1952
+ badge: "Cache"
1953
+ },
1954
+ {
1955
+ patterns: [/\b(neo4j|memgraph|dgraph|graphdb)\b/i],
1956
+ kind: "database",
1957
+ role: "database",
1958
+ badge: "Graph"
1959
+ },
1960
+ {
1961
+ patterns: [/\b(clickhouse|snowflake|bigquery|redshift|duckdb)\b/i],
1962
+ kind: "database",
1963
+ role: "database",
1964
+ badge: "Analytics"
1965
+ },
1966
+ // ── MESSAGING & EVENT STREAMING ────────────────────────────────────
1967
+ {
1968
+ patterns: [/\b(kafka|confluent|redpanda)\b/i],
1969
+ kind: "queue",
1970
+ role: "event_stream",
1971
+ badge: "EventStream"
1972
+ },
1973
+ {
1974
+ patterns: [/\b(rabbitmq|sqs|activemq|pulsar|nats|eventgrid|servicebus)\b/i],
1975
+ kind: "queue",
1976
+ role: "queue",
1977
+ badge: "Queue"
1978
+ },
1979
+ // ── GATEWAYS & INGRESS ──────────────────────────────────────────────
1980
+ {
1981
+ patterns: [/\b(kong|envoy|nginx|traefik|caddy|haproxy|emissary)\b/i],
1982
+ kind: "api_gateway",
1983
+ role: "gateway",
1984
+ badge: "Gateway"
1985
+ },
1986
+ {
1987
+ patterns: [/\b(cloudfront|cloudflare|fastly|akamai|cdn)\b/i],
1988
+ kind: "cdn",
1989
+ role: "network",
1990
+ badge: "CDN"
1991
+ },
1992
+ {
1993
+ patterns: [/\b(alb|elb|nlb|load_?balancer|ingress)\b/i],
1994
+ kind: "load_balancer",
1995
+ role: "network",
1996
+ badge: "LB"
1997
+ },
1998
+ // ── CLIENT & PRESENTATION ──────────────────────────────────────────
1999
+ {
2000
+ patterns: [/\b(react|vue|angular|svelte|next(\.?js)?|nuxt|solid)\b/i],
2001
+ kind: "browser",
2002
+ role: "client",
2003
+ badge: "Web"
2004
+ },
2005
+ {
2006
+ patterns: [/\b(flutter|react_?native|ios|android|swift|kotlin|mobile)\b/i],
2007
+ kind: "browser",
2008
+ role: "client",
2009
+ badge: "Mobile"
2010
+ },
2011
+ // ── AI & LLM ────────────────────────────────────────────────────────
2012
+ {
2013
+ patterns: [/\b(gemini|gpt(-?[a-z0-9.]+)?|claude|openai|anthropic|bedrock|vertexai|mistral|ollama|deepseek)\b/i],
2014
+ kind: "service",
2015
+ role: "ai_model",
2016
+ badge: "AI/LLM"
2017
+ },
2018
+ {
2019
+ patterns: [/\b(pinecone|weaviate|qdrant|chroma|milvus)\b/i],
2020
+ kind: "database",
2021
+ role: "database",
2022
+ badge: "VectorDB"
2023
+ },
2024
+ // ── CLOUD & CONTAINER RUNTIMES ─────────────────────────────────────
2025
+ {
2026
+ patterns: [/\b(k8s|kubernetes|eks|gke|aks|helm|argocd)\b/i],
2027
+ kind: "cluster",
2028
+ role: "platform",
2029
+ badge: "K8s"
2030
+ },
2031
+ {
2032
+ patterns: [/\b(lambda|cloud_?functions?|azure_?functions?|serverless)\b/i],
2033
+ kind: "worker",
2034
+ role: "compute",
2035
+ badge: "Function"
2036
+ },
2037
+ {
2038
+ patterns: [/\b(docker|ecs|container|fargate|cloud_?run)\b/i],
2039
+ kind: "service",
2040
+ role: "compute",
2041
+ badge: "Container"
2042
+ },
2043
+ // ── STORAGE & BLOB ──────────────────────────────────────────────────
2044
+ {
2045
+ patterns: [/\b(s3|gcs|azure_?blob|minio|r2|ceph)\b/i],
2046
+ kind: "storage",
2047
+ role: "storage",
2048
+ badge: "ObjectStorage"
2049
+ },
2050
+ // ── SECURITY & AUTH ─────────────────────────────────────────────────
2051
+ {
2052
+ patterns: [/\b(auth0|clerk|keycloak|cognito|okta|vault|jwt|oauth|oidc)\b/i],
2053
+ kind: "service",
2054
+ role: "security",
2055
+ badge: "Security"
2056
+ }
2057
+ ];
2058
+ function classifyTechnology(id, label = "") {
2059
+ const combined = `${id} ${label}`.trim();
2060
+ for (const entry of TECH_CATALOG) {
2061
+ if (entry.patterns.some((p) => p.test(combined))) {
2062
+ return {
2063
+ kind: entry.kind,
2064
+ role: entry.role,
2065
+ suggestedTheme: "paper",
2066
+ badge: entry.badge
2067
+ };
2068
+ }
2069
+ }
2070
+ return {
2071
+ kind: "service",
2072
+ role: "compute",
2073
+ suggestedTheme: "paper"
2074
+ };
2075
+ }
2076
+
2077
+ // ../core/src/ai-healing.ts
2078
+ function analyzeAndBuildRepairPrompt(sourceCode) {
2079
+ const syntaxErrors = [];
2080
+ let ast = null;
2081
+ try {
2082
+ ast = parse(sourceCode);
2083
+ } catch (err) {
2084
+ syntaxErrors.push(err instanceof Error ? err.message : String(err));
2085
+ }
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
+ };
2101
+ }
2102
+ const archViolations = validateArchitecture(ast);
2103
+ if (ast.diagnostics.length === 0 && archViolations.length === 0) {
2104
+ return { isValid: true, syntaxErrors: [], archViolations: [] };
2105
+ }
2106
+ 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
+ "```"
2119
+ ];
2120
+ return {
2121
+ isValid: false,
2122
+ syntaxErrors: [],
2123
+ archViolations,
2124
+ repairPrompt: promptSections.join("\n")
2125
+ };
2126
+ }
2127
+
2128
+ // ../compat/src/mermaid/mermaid-transpiler.ts
2129
+ function sanitizeId(id) {
2130
+ return id.trim().replace(/[^a-zA-Z0-9_]/g, "_");
2131
+ }
2132
+ function cleanLabel(raw) {
2133
+ return raw.trim().replace(/^["'\[\(\{]+/, "").replace(/["'\]\)\}]+$/, "").replace(/<br\s*\/?>/gi, " ");
2134
+ }
2135
+ function inferKindFromMermaid(id, label, shapeBracket) {
2136
+ const text = `${id} ${label}`.toLowerCase();
2137
+ if (shapeBracket === "[(" || shapeBracket === ")]" || /(db|database|sql|postgres|mongo|dynamo|redis)/.test(text)) {
2138
+ return "database";
2139
+ }
2140
+ if (shapeBracket === "{{" || shapeBracket === "}}" || /(queue|kafka|rabbitmq|sqs|event|bus)/.test(text)) {
2141
+ return "queue";
2142
+ }
2143
+ if (shapeBracket === "{" || shapeBracket === "}" || /(decision|check|valid|auth|gate)/.test(text)) {
2144
+ return "gateway";
2145
+ }
2146
+ if (shapeBracket === "([" || shapeBracket === "])" || /(client|user|browser|ui|app|web|frontend)/.test(text)) {
2147
+ return "browser";
2148
+ }
2149
+ if (/(cache|memcached|varnish)/.test(text)) return "cache";
2150
+ if (/(storage|s3|bucket|blob)/.test(text)) return "storage";
2151
+ if (/(worker|cron|job|lambda|function)/.test(text)) return "worker";
2152
+ return "service";
2153
+ }
2154
+ function transpileMermaidToMarkdy(mermaidSource, sceneTitle = "Imported Diagram") {
2155
+ const lines = mermaidSource.split(/\r?\n/).map((l) => l.trim()).filter((l) => l && !l.startsWith("%%"));
2156
+ if (lines.length === 0) {
2157
+ return {
2158
+ code: `scene theme=paper
2159
+ layout LR
2160
+ `,
2161
+ diagramType: "architecture",
2162
+ nodeCount: 0,
2163
+ edgeCount: 0
2164
+ };
2165
+ }
2166
+ const firstLine = lines[0].toLowerCase();
2167
+ if (firstLine.startsWith("sequencediagram")) {
2168
+ return transpileSequenceDiagram(lines.slice(1), sceneTitle);
2169
+ }
2170
+ return transpileFlowchart(lines, sceneTitle);
2171
+ }
2172
+ function transpileSequenceDiagram(lines, title) {
2173
+ const participants = /* @__PURE__ */ new Map();
2174
+ const messages = [];
2175
+ for (const line of lines) {
2176
+ const partMatch = /^(?:participant|actor)\s+([^\s]+)(?:\s+as\s+(.+))?$/i.exec(line);
2177
+ if (partMatch) {
2178
+ const id = sanitizeId(partMatch[1]);
2179
+ const label = partMatch[2] ? cleanLabel(partMatch[2]) : id;
2180
+ const kind = inferKindFromMermaid(id, label);
2181
+ participants.set(id, { id, label, kind });
2182
+ continue;
2183
+ }
2184
+ const msgMatch = /^([a-zA-Z0-9_]+)\s*(-->>|->>|->|-->|-\)|~>)\s*([a-zA-Z0-9_]+)\s*:\s*(.+)$/.exec(line);
2185
+ if (msgMatch) {
2186
+ const from = sanitizeId(msgMatch[1]);
2187
+ const arrow = msgMatch[2];
2188
+ const to = sanitizeId(msgMatch[3]);
2189
+ const label = cleanLabel(msgMatch[4]);
2190
+ if (!participants.has(from)) participants.set(from, { id: from, label: from, kind: inferKindFromMermaid(from, from) });
2191
+ if (!participants.has(to)) participants.set(to, { id: to, label: to, kind: inferKindFromMermaid(to, to) });
2192
+ let kind = "->";
2193
+ if (arrow === "-->>" || arrow === "-->" || arrow === "-.->") kind = "~>";
2194
+ messages.push({ from, to, label, kind });
2195
+ }
2196
+ }
2197
+ const out = [];
2198
+ out.push(title ? `scene "${title}" type=sequence theme=paper` : `scene type=sequence theme=paper`);
2199
+ out.push("");
2200
+ for (const p of participants.values()) {
2201
+ out.push(`${p.kind} ${p.id} "${p.label}"`);
2202
+ }
2203
+ out.push("");
2204
+ out.push('beat main "Sequence Flow":');
2205
+ out.push(" show $nodes");
2206
+ for (const msg of messages) {
2207
+ out.push(` ${msg.from} ${msg.kind} ${msg.to} "${msg.label}"`);
2208
+ }
2209
+ return {
2210
+ code: out.join("\n"),
2211
+ diagramType: "sequence",
2212
+ nodeCount: participants.size,
2213
+ edgeCount: messages.length
2214
+ };
2215
+ }
2216
+ function transpileFlowchart(lines, title) {
2217
+ let direction = "LR";
2218
+ const first = lines[0].toLowerCase();
2219
+ if (first.startsWith("graph") || first.startsWith("flowchart")) {
2220
+ const dirMatch = /\b(lr|rl|tb|td|bt)\b/i.exec(first);
2221
+ if (dirMatch) {
2222
+ direction = dirMatch[1].toUpperCase();
2223
+ if (direction === "TD") direction = "TB";
2224
+ }
2225
+ lines = lines.slice(1);
2226
+ }
2227
+ const nodes = /* @__PURE__ */ new Map();
2228
+ const groups = /* @__PURE__ */ new Map();
2229
+ const flows = [];
2230
+ let currentSubgraph = null;
2231
+ const explicitNodeRe = /([a-zA-Z0-9_-]+)\s*(\[\([^\n]*?\)\]|\[\[[^\n]*?\]\]|\(\[[^\n]*?\]\)|\(\([^\n]*?\)\)|\[[^\n]*?\]|\{[^\n]*?\}|\([^\n]*?\))/g;
2232
+ const flowPattern = /([a-zA-Z0-9_-]+)\s*(?:\[[^\]]*\]|\([^)]*\)|\{[^}]*\})?\s*(-->|->|==>|-.->|--\s*([^-]+)\s*-->)\s*(?:\|([^|]+)\|)?\s*([a-zA-Z0-9_-]+)/;
2233
+ for (const line of lines) {
2234
+ const subMatch = /^subgraph\s+([a-zA-Z0-9_]+)?\s*(\[.*\]|".*")?/i.exec(line);
2235
+ if (subMatch) {
2236
+ const rawId = subMatch[1] || `group_${groups.size + 1}`;
2237
+ const id = sanitizeId(rawId);
2238
+ const label = subMatch[2] ? cleanLabel(subMatch[2]) : id;
2239
+ currentSubgraph = { id, label, members: [] };
2240
+ groups.set(id, currentSubgraph);
2241
+ continue;
2242
+ }
2243
+ if (line === "end" && currentSubgraph) {
2244
+ currentSubgraph = null;
2245
+ continue;
2246
+ }
2247
+ let match;
2248
+ while ((match = explicitNodeRe.exec(line)) !== null) {
2249
+ const rawId = match[1];
2250
+ const rawBody = match[2];
2251
+ const id = sanitizeId(rawId);
2252
+ const bracket = rawBody.slice(0, 2);
2253
+ const label = cleanLabel(rawBody) || id;
2254
+ const kind = inferKindFromMermaid(id, label, bracket);
2255
+ nodes.set(id, { id, label, kind });
2256
+ if (currentSubgraph && !currentSubgraph.members.includes(id)) {
2257
+ currentSubgraph.members.push(id);
2258
+ }
2259
+ }
2260
+ let remainingLine = line;
2261
+ while (true) {
2262
+ const fMatch = flowPattern.exec(remainingLine);
2263
+ if (!fMatch) break;
2264
+ const from = sanitizeId(fMatch[1]);
2265
+ const arrow = fMatch[2];
2266
+ const inlineLabel = fMatch[3];
2267
+ const pipeLabel = fMatch[4];
2268
+ const to = sanitizeId(fMatch[5]);
2269
+ const label = pipeLabel ? cleanLabel(pipeLabel) : inlineLabel ? cleanLabel(inlineLabel) : void 0;
2270
+ if (!nodes.has(from)) nodes.set(from, { id: from, label: from, kind: inferKindFromMermaid(from, from) });
2271
+ if (!nodes.has(to)) nodes.set(to, { id: to, label: to, kind: inferKindFromMermaid(to, to) });
2272
+ let op = "->";
2273
+ if (arrow.includes("-.->") || arrow.includes("~")) op = "~>";
2274
+ flows.push({ from, to, op, label });
2275
+ const toIndex = fMatch.index + fMatch[0].lastIndexOf(fMatch[5]);
2276
+ if (toIndex === 0) break;
2277
+ remainingLine = remainingLine.substring(toIndex);
2278
+ }
2279
+ }
2280
+ const out = [];
2281
+ out.push(title ? `scene "${title}" theme=paper` : `scene theme=paper`);
2282
+ out.push(`layout ${direction}`);
2283
+ out.push("");
2284
+ for (const g of groups.values()) {
2285
+ if (g.members.length > 0) {
2286
+ out.push(`group ${g.id} "${g.label}": ${g.members.join(" ")}`);
2287
+ }
2288
+ }
2289
+ for (const n of nodes.values()) {
2290
+ out.push(`${n.kind} ${n.id} "${n.label}"`);
2291
+ }
2292
+ out.push("");
2293
+ out.push('beat main "Render Diagram":');
2294
+ out.push(" show $nodes stagger=60ms");
2295
+ for (const flow of flows) {
2296
+ if (flow.label) {
2297
+ out.push(` ${flow.from} ${flow.op} ${flow.to} "${flow.label}"`);
2298
+ } else {
2299
+ out.push(` ${flow.from} ${flow.op} ${flow.to}`);
2300
+ }
2301
+ }
2302
+ return {
2303
+ code: out.join("\n"),
2304
+ diagramType: "architecture",
2305
+ nodeCount: nodes.size,
2306
+ edgeCount: flows.length
2307
+ };
2308
+ }
2309
+
2310
+ // ../compat/src/infra/docker-compose-transpiler.ts
2311
+ function sanitizeIdentifier(name) {
2312
+ return name.replace(/[^a-zA-Z0-9_]/g, "_");
2313
+ }
2314
+ function inferSemanticKind(serviceName, image) {
2315
+ const combined = `${serviceName} ${image ?? ""}`.toLowerCase();
2316
+ if (/(postgres|mysql|mongo|mariadb|sqlite|cockroach|db)/.test(combined)) return "database";
2317
+ if (/(redis|memcache)/.test(combined)) return "cache";
2318
+ if (/(kafka|rabbitmq|sqs|pulsar|nats|queue)/.test(combined)) return "queue";
2319
+ if (/(nginx|envoy|traefik|caddy|gateway|haproxy)/.test(combined)) return "gateway";
2320
+ if (/(web|frontend|client|ui|react|vue|next)/.test(combined)) return "browser";
2321
+ if (/(worker|job|cron|consumer)/.test(combined)) return "worker";
2322
+ if (/(minio|s3|storage|blob)/.test(combined)) return "storage";
2323
+ return "service";
2324
+ }
2325
+ function parseSimpleYaml(content) {
2326
+ const result = {};
2327
+ const lines = content.split(/\r?\n/);
2328
+ const stack = [
2329
+ { indent: -1, obj: result }
2330
+ ];
2331
+ for (let i = 0; i < lines.length; i++) {
2332
+ const rawLine = lines[i];
2333
+ if (!rawLine.trim() || rawLine.trim().startsWith("#")) continue;
2334
+ const indent = rawLine.length - rawLine.trimStart().length;
2335
+ const trimmed = rawLine.trim();
2336
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
2337
+ stack.pop();
2338
+ }
2339
+ if (trimmed.startsWith("- ")) {
2340
+ const val = trimmed.slice(2).trim().replace(/^['"]|['"]$/g, "");
2341
+ const parent2 = stack[stack.length - 1].obj;
2342
+ if (Array.isArray(parent2)) {
2343
+ parent2.push(val);
2344
+ }
2345
+ continue;
2346
+ }
2347
+ const colonIdx = trimmed.indexOf(":");
2348
+ if (colonIdx === -1) continue;
2349
+ const key = trimmed.slice(0, colonIdx).trim().replace(/^['"]|['"]$/g, "");
2350
+ const valRaw = trimmed.slice(colonIdx + 1).trim();
2351
+ const parent = stack[stack.length - 1].obj;
2352
+ if (valRaw === "" || valRaw === "|" || valRaw === ">") {
2353
+ let isArray = false;
2354
+ for (let j = i + 1; j < lines.length; j++) {
2355
+ const nextLine = lines[j];
2356
+ if (!nextLine.trim() || nextLine.trim().startsWith("#")) continue;
2357
+ const nextIndent = nextLine.length - nextLine.trimStart().length;
2358
+ if (nextIndent > indent && nextLine.trim().startsWith("- ")) {
2359
+ isArray = true;
2360
+ }
2361
+ break;
2362
+ }
2363
+ if (isArray) {
2364
+ const child = [];
2365
+ parent[key] = child;
2366
+ stack.push({ indent, obj: child });
2367
+ } else {
2368
+ const child = {};
2369
+ parent[key] = child;
2370
+ stack.push({ indent, obj: child });
2371
+ }
2372
+ } else if (valRaw.startsWith("[") && valRaw.endsWith("]")) {
2373
+ const inner = valRaw.slice(1, -1).trim();
2374
+ const items = inner ? inner.split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean) : [];
2375
+ parent[key] = items;
2376
+ } else {
2377
+ parent[key] = valRaw.replace(/^['"]|['"]$/g, "");
2378
+ }
2379
+ }
2380
+ return result;
2381
+ }
2382
+ function transpileDockerComposeToMarkdy(yamlContent, title = "Container Topology") {
2383
+ const parsed = parseSimpleYaml(yamlContent);
2384
+ const rawServices = parsed["services"] ?? {};
2385
+ const serviceList = [];
2386
+ for (const [svcName, svcConfig] of Object.entries(rawServices)) {
2387
+ if (typeof svcConfig !== "object" || svcConfig === null) continue;
2388
+ const cfg = svcConfig;
2389
+ let ports = [];
2390
+ if (Array.isArray(cfg["ports"])) {
2391
+ ports = cfg["ports"].map((p) => String(p).replace(/^['"]|['"]$/g, ""));
2392
+ } else if (typeof cfg["ports"] === "string") {
2393
+ const pStr = cfg["ports"].trim();
2394
+ if (pStr.startsWith("[") && pStr.endsWith("]")) {
2395
+ ports = pStr.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
2396
+ } else {
2397
+ ports = [pStr.replace(/^['"]|['"]$/g, "")];
2398
+ }
2399
+ }
2400
+ let dependsOn = [];
2401
+ if (Array.isArray(cfg["depends_on"])) {
2402
+ dependsOn = cfg["depends_on"].map((d) => String(d).replace(/^['"]|['"]$/g, ""));
2403
+ } else if (typeof cfg["depends_on"] === "object" && cfg["depends_on"] !== null) {
2404
+ dependsOn = Object.keys(cfg["depends_on"]);
2405
+ } else if (typeof cfg["depends_on"] === "string") {
2406
+ const dStr = cfg["depends_on"].trim();
2407
+ if (dStr.startsWith("[") && dStr.endsWith("]")) {
2408
+ dependsOn = dStr.slice(1, -1).split(",").map((s) => s.trim().replace(/^['"]|['"]$/g, "")).filter(Boolean);
2409
+ } else {
2410
+ dependsOn = [dStr.replace(/^['"]|['"]$/g, "")];
2411
+ }
2412
+ }
2413
+ serviceList.push({
2414
+ name: svcName,
2415
+ image: typeof cfg["image"] === "string" ? cfg["image"] : void 0,
2416
+ ports,
2417
+ dependsOn
2418
+ });
2419
+ }
2420
+ const out = [];
2421
+ out.push(title ? `scene "${title}" theme=paper` : `scene theme=paper`);
2422
+ out.push("layout LR");
2423
+ out.push("");
2424
+ for (const svc of serviceList) {
2425
+ const id = sanitizeIdentifier(svc.name);
2426
+ const kind = inferSemanticKind(svc.name, svc.image);
2427
+ const ports = svc.ports ?? [];
2428
+ const label = ports.length > 0 ? `${svc.name} :${ports[0]}` : svc.name;
2429
+ out.push(`${kind} ${id} "${label}"`);
2430
+ }
2431
+ out.push("");
2432
+ out.push('beat main "Initialize and connect services":');
2433
+ out.push(" show $nodes stagger=60ms");
2434
+ for (const svc of serviceList) {
2435
+ const sourceId = sanitizeIdentifier(svc.name);
2436
+ for (const dep of svc.dependsOn ?? []) {
2437
+ const targetId = sanitizeIdentifier(dep);
2438
+ out.push(` ${sourceId} -> ${targetId} "depends on"`);
2439
+ }
2440
+ }
2441
+ return out.join("\n");
2442
+ }
2443
+
2444
+ // ../compat/src/infra/k8s-transpiler.ts
2445
+ function sanitize(id) {
2446
+ return id.replace(/[^a-zA-Z0-9_]/g, "_");
2447
+ }
2448
+ function transpileKubernetesManifestsToMarkdy(manifestContent, title = "Kubernetes Cluster Topology") {
2449
+ const docs = manifestContent.split(/^---/m).map((d) => d.trim()).filter((d) => d.length > 0);
2450
+ const manifests = [];
2451
+ for (const doc of docs) {
2452
+ const raw = parseSimpleYaml(doc);
2453
+ const kind = typeof raw["kind"] === "string" ? raw["kind"] : "";
2454
+ const meta = raw["metadata"] || {};
2455
+ const name = typeof meta["name"] === "string" ? meta["name"] : "";
2456
+ const namespace = typeof meta["namespace"] === "string" ? meta["namespace"] : "default";
2457
+ if (!kind || !name) continue;
2458
+ manifests.push({
2459
+ kind,
2460
+ name,
2461
+ namespace
2462
+ });
2463
+ }
2464
+ const out = [];
2465
+ out.push(title ? `scene "${title}" theme=paper` : `scene theme=paper`);
2466
+ out.push("layout TB");
2467
+ out.push("");
2468
+ const namespaces = new Set(manifests.map((m) => m.namespace || "default"));
2469
+ for (const ns of namespaces) {
2470
+ const nsMembers = manifests.filter((m) => (m.namespace || "default") === ns).map((m) => sanitize(`${m.kind}_${m.name}`));
2471
+ if (nsMembers.length > 0) {
2472
+ out.push(`group ns_${sanitize(ns)} "Namespace: ${ns}": ${nsMembers.join(" ")}`);
2473
+ }
2474
+ }
2475
+ out.push("");
2476
+ for (const m of manifests) {
2477
+ const id = sanitize(`${m.kind}_${m.name}`);
2478
+ let kind = "service";
2479
+ if (m.kind === "Ingress") kind = "gateway";
2480
+ else if (m.kind === "Service") kind = "load_balancer";
2481
+ else if (m.kind === "StatefulSet") kind = "database";
2482
+ else if (m.kind === "CronJob" || m.kind === "Job") kind = "worker";
2483
+ else if (m.kind === "PersistentVolumeClaim") kind = "storage";
2484
+ out.push(`${kind} ${id} "${m.name} (${m.kind})"`);
2485
+ }
2486
+ out.push("");
2487
+ out.push('beat main "Cluster Ingress & Service Mesh":');
2488
+ out.push(" show $nodes stagger=50ms");
2489
+ const ingresses = manifests.filter((m) => m.kind === "Ingress");
2490
+ const services = manifests.filter((m) => m.kind === "Service");
2491
+ const workloads = manifests.filter((m) => ["Deployment", "StatefulSet", "DaemonSet"].includes(m.kind));
2492
+ for (const ing of ingresses) {
2493
+ for (const svc of services) {
2494
+ out.push(` ${sanitize(`${ing.kind}_${ing.name}`)} -> ${sanitize(`${svc.kind}_${svc.name}`)} "route"`);
2495
+ }
2496
+ }
2497
+ for (const svc of services) {
2498
+ for (const wl of workloads) {
2499
+ out.push(` ${sanitize(`${svc.kind}_${svc.name}`)} -> ${sanitize(`${wl.kind}_${wl.name}`)} "balance"`);
2500
+ }
2501
+ }
2502
+ return out.join("\n");
2503
+ }
2504
+
2505
+ // ../compat/src/infra/terraform-transpiler.ts
2506
+ function sanitizeId2(raw) {
2507
+ return raw.replace(/[^a-zA-Z0-9_]/g, "_");
2508
+ }
2509
+ function inferKindFromTfType(type) {
2510
+ if (type.includes("database") || type.includes("db_instance") || type.includes("rds") || type.includes("dynamodb")) {
2511
+ return "database";
2512
+ }
2513
+ if (type.includes("elasticache") || type.includes("redis") || type.includes("memcached")) {
2514
+ return "cache";
2515
+ }
2516
+ if (type.includes("sqs") || type.includes("pubsub") || type.includes("servicebus") || type.includes("queue")) {
2517
+ return "queue";
2518
+ }
2519
+ if (type.includes("s3_bucket") || type.includes("storage_bucket") || type.includes("blob")) {
2520
+ return "storage";
2521
+ }
2522
+ if (type.includes("lb") || type.includes("alb") || type.includes("apigateway") || type.includes("gateway")) {
2523
+ return "gateway";
2524
+ }
2525
+ if (type.includes("cloudfront") || type.includes("cdn")) {
2526
+ return "cdn";
2527
+ }
2528
+ if (type.includes("lambda") || type.includes("cloudfunctions") || type.includes("function_app")) {
2529
+ return "worker";
2530
+ }
2531
+ if (type.includes("eks") || type.includes("gke") || type.includes("aks") || type.includes("cluster")) {
2532
+ return "cluster";
2533
+ }
2534
+ return "service";
2535
+ }
2536
+ function transpileTerraformStateToMarkdy(tfstateContent, sceneTitle = "Cloud Infrastructure Architecture") {
2537
+ let parsed;
2538
+ try {
2539
+ parsed = JSON.parse(tfstateContent);
2540
+ } catch {
2541
+ throw new Error("Invalid Terraform state JSON");
2542
+ }
2543
+ if (!parsed.resources || !Array.isArray(parsed.resources)) {
2544
+ return sceneTitle ? `scene "${sceneTitle}" theme=paper
2545
+ layout LR
2546
+ ` : `scene theme=paper
2547
+ layout LR
2548
+ `;
2549
+ }
2550
+ const nodes = [];
2551
+ const edges = [];
2552
+ const vpcGroups = /* @__PURE__ */ new Map();
2553
+ const arnToResId = /* @__PURE__ */ new Map();
2554
+ for (const res of parsed.resources) {
2555
+ if (res.type.startsWith("aws_iam_") || res.type.includes("route_table") || res.type.includes("security_group")) {
2556
+ continue;
2557
+ }
2558
+ const firstInst = res.instances?.[0]?.attributes;
2559
+ const resId = sanitizeId2(`${res.type}_${res.name}`);
2560
+ const kind = inferKindFromTfType(res.type);
2561
+ const label = firstInst?.tags?.["Name"] || firstInst?.name || `${res.type.split("_").slice(-1)[0]}: ${res.name}`;
2562
+ const vpcId = typeof firstInst?.vpc_id === "string" ? sanitizeId2(firstInst.vpc_id) : void 0;
2563
+ nodes.push({ id: resId, kind, label, vpcId });
2564
+ if (typeof firstInst?.arn === "string") {
2565
+ arnToResId.set(firstInst.arn, resId);
2566
+ }
2567
+ if (vpcId) {
2568
+ if (!vpcGroups.has(vpcId)) vpcGroups.set(vpcId, []);
2569
+ vpcGroups.get(vpcId).push(resId);
2570
+ }
2571
+ if (typeof firstInst?.load_balancer_arn === "string") {
2572
+ edges.push({ from: firstInst.load_balancer_arn, to: resId, label: "routes" });
2573
+ }
2574
+ }
2575
+ for (let i = 0; i < edges.length; i++) {
2576
+ const edge = edges[i];
2577
+ if (edge.from.startsWith("arn:") && arnToResId.has(edge.from)) {
2578
+ edge.from = arnToResId.get(edge.from);
2579
+ } else if (edge.from.startsWith("arn:")) {
2580
+ edge.from = sanitizeId2(edge.from);
2581
+ }
2582
+ }
2583
+ const out = [];
2584
+ out.push(sceneTitle ? `scene "${sceneTitle}" theme=paper` : `scene theme=paper`);
2585
+ out.push("layout LR");
2586
+ out.push("");
2587
+ for (const [vpc, members] of vpcGroups) {
2588
+ if (members.length > 1) {
2589
+ out.push(`group ${vpc} "VPC Network": ${members.join(" ")}`);
2590
+ }
2591
+ }
2592
+ for (const n of nodes) {
2593
+ out.push(`${n.kind} ${n.id} "${n.label}"`);
2594
+ }
2595
+ out.push("");
2596
+ out.push('beat main "Provisioned Infrastructure Flow":');
2597
+ out.push(" show $nodes stagger=40ms");
2598
+ if (edges.length > 0) {
2599
+ for (const e of edges) {
2600
+ out.push(` ${e.from} -> ${e.to} "${e.label || "connect"}"`);
2601
+ }
2602
+ } else if (nodes.length >= 2) {
2603
+ out.push(` ${nodes[0].id} -> ${nodes[1].id} "traffic"`);
2604
+ }
2605
+ return out.join("\n");
2606
+ }
2607
+
2608
+ // ../compat/src/infra/drawio-transpiler.ts
2609
+ function sanitizeId3(raw, fallback) {
2610
+ const cleaned = raw.replace(/[^a-zA-Z0-9_]/g, "");
2611
+ if (/^[0-9]/.test(cleaned) || cleaned.length === 0) {
2612
+ return `${fallback}_${cleaned}`;
2613
+ }
2614
+ return cleaned;
2615
+ }
2616
+ function stripHtml(raw) {
2617
+ return raw.replace(/<br\s*\/?>/gi, " ").replace(/<\/?[^>]+(>|$)/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').trim();
2618
+ }
2619
+ function inferKindFromStyleAndLabel(style, label) {
2620
+ const styleLower = style.toLowerCase();
2621
+ const labelLower = label.toLowerCase();
2622
+ if (styleLower.includes("shape=cylinder") || styleLower.includes("datastore") || styleLower.includes("database")) {
2623
+ return "database";
2624
+ }
2625
+ if (styleLower.includes("shape=cloud") || styleLower.includes("network")) {
2626
+ return "cloud";
2627
+ }
2628
+ if (styleLower.includes("shape=actor") || styleLower.includes("person") || styleLower.includes("user")) {
2629
+ return "user";
2630
+ }
2631
+ if (styleLower.includes("shape=hexagon") || styleLower.includes("gateway")) {
2632
+ return "gateway";
2633
+ }
2634
+ if (styleLower.includes("queue") || styleLower.includes("message") || styleLower.includes("kafka") || styleLower.includes("sqs")) {
2635
+ return "queue";
2636
+ }
2637
+ if (styleLower.includes("cache") || styleLower.includes("redis")) {
2638
+ return "cache";
2639
+ }
2640
+ if (styleLower.includes("storage") || styleLower.includes("bucket") || styleLower.includes("s3")) {
2641
+ return "storage";
2642
+ }
2643
+ const profile = classifyTechnology(labelLower);
2644
+ if (profile.kind && profile.kind !== "service") {
2645
+ return profile.kind;
2646
+ }
2647
+ return "service";
2648
+ }
2649
+ function parseDrawioXml(xml, defaultTitle = "Imported Draw.io") {
2650
+ const cells = [];
2651
+ const diagramTitleMatch = xml.match(/<diagram[^>]*name="([^"]+)"/i);
2652
+ const title = diagramTitleMatch ? diagramTitleMatch[1] : defaultTitle;
2653
+ const cellRegex = /<mxCell\s+([^>]+)(?:\/>|>([\s\S]*?)<\/mxCell>)/gi;
2654
+ let match;
2655
+ while ((match = cellRegex.exec(xml)) !== null) {
2656
+ const attrStr = match[1];
2657
+ const innerContent = match[2] || "";
2658
+ const idMatch = attrStr.match(/id="([^"]+)"/i);
2659
+ if (!idMatch) continue;
2660
+ const id = idMatch[1];
2661
+ if (id === "0" || id === "1") continue;
2662
+ const valueAttrMatch = attrStr.match(/value="([^"]*)"/i);
2663
+ let rawValue = valueAttrMatch ? valueAttrMatch[1] : "";
2664
+ if (!rawValue && innerContent) {
2665
+ rawValue = innerContent;
2666
+ }
2667
+ const cleanValue = stripHtml(rawValue);
2668
+ const styleMatch = attrStr.match(/style="([^"]*)"/i);
2669
+ const style = styleMatch ? styleMatch[1] : "";
2670
+ const isVertex = /vertex="1"/i.test(attrStr);
2671
+ const isEdge = /edge="1"/i.test(attrStr);
2672
+ const sourceMatch = attrStr.match(/source="([^"]+)"/i);
2673
+ const targetMatch = attrStr.match(/target="([^"]+)"/i);
2674
+ const parentMatch = attrStr.match(/parent="([^"]+)"/i);
2675
+ cells.push({
2676
+ id,
2677
+ value: cleanValue,
2678
+ style,
2679
+ isVertex,
2680
+ isEdge,
2681
+ source: sourceMatch ? sourceMatch[1] : void 0,
2682
+ target: targetMatch ? targetMatch[1] : void 0,
2683
+ parent: parentMatch ? parentMatch[1] : void 0
2684
+ });
2685
+ }
2686
+ return { title, cells };
2687
+ }
2688
+ async function transpileDrawioToMarkdy(source, customTitle) {
2689
+ let xml = source.trim();
2690
+ if (xml.includes("<diagram") && !xml.includes("<mxGraphModel>")) {
2691
+ const diagramMatch = xml.match(/<diagram[^>]*>([\s\S]*?)<\/diagram>/i);
2692
+ if (diagramMatch) {
2693
+ const payload = diagramMatch[1].trim();
2694
+ try {
2695
+ const binaryStr = atob(payload);
2696
+ if (binaryStr.includes("<mxGraphModel")) {
2697
+ xml = binaryStr;
2698
+ } else if (typeof DecompressionStream !== "undefined") {
2699
+ const uint8 = new Uint8Array(binaryStr.length);
2700
+ for (let i = 0; i < binaryStr.length; i++) {
2701
+ uint8[i] = binaryStr.charCodeAt(i);
2702
+ }
2703
+ try {
2704
+ const ds = new DecompressionStream("deflate-raw");
2705
+ const writer = ds.writable.getWriter();
2706
+ writer.write(uint8);
2707
+ writer.close();
2708
+ const reader = ds.readable.getReader();
2709
+ const chunks = [];
2710
+ let totalLen = 0;
2711
+ while (true) {
2712
+ const { done, value } = await reader.read();
2713
+ if (done) break;
2714
+ if (value) {
2715
+ chunks.push(value);
2716
+ totalLen += value.length;
2717
+ }
2718
+ }
2719
+ const decompressed = new Uint8Array(totalLen);
2720
+ let offset = 0;
2721
+ for (const c of chunks) {
2722
+ decompressed.set(c, offset);
2723
+ offset += c.length;
2724
+ }
2725
+ const decodedStr = new TextDecoder().decode(decompressed);
2726
+ xml = decodeURIComponent(decodedStr);
2727
+ } catch (e) {
2728
+ }
2729
+ }
2730
+ } catch {
2731
+ }
2732
+ }
2733
+ }
2734
+ const model = parseDrawioXml(xml, customTitle);
2735
+ const vertexCells = model.cells.filter((c) => c.isVertex);
2736
+ const edgeCells = model.cells.filter((c) => c.isEdge);
2737
+ const cellIdToNodeId = /* @__PURE__ */ new Map();
2738
+ const nodes = [];
2739
+ for (const cell of vertexCells) {
2740
+ const rawLabel = cell.value || `Component_${cell.id}`;
2741
+ const rawIdentifier = isNaN(Number(cell.id)) && cell.id.length > 0 ? cell.id : cell.value || `node_${cell.id}`;
2742
+ const nodeId = sanitizeId3(rawIdentifier, `node_${cell.id}`);
2743
+ const kind = inferKindFromStyleAndLabel(cell.style, rawLabel);
2744
+ cellIdToNodeId.set(cell.id, nodeId);
2745
+ nodes.push({ id: nodeId, kind, label: rawLabel });
2746
+ }
2747
+ const lines = [];
2748
+ const sceneName = customTitle || model.title;
2749
+ lines.push(sceneName ? `scene "${sceneName}" theme=paper` : `scene theme=paper`);
2750
+ lines.push(`layout LR`);
2751
+ lines.push(``);
2752
+ if (nodes.length > 0) {
2753
+ for (const node of nodes) {
2754
+ lines.push(`${node.kind} ${node.id} "${node.label}"`);
2755
+ }
2756
+ lines.push(``);
2757
+ }
2758
+ const flows = [];
2759
+ for (const edge of edgeCells) {
2760
+ if (edge.source && edge.target) {
2761
+ const sourceId = cellIdToNodeId.get(edge.source);
2762
+ const targetId = cellIdToNodeId.get(edge.target);
2763
+ if (sourceId && targetId) {
2764
+ flows.push({
2765
+ from: sourceId,
2766
+ to: targetId,
2767
+ label: edge.value || void 0
2768
+ });
2769
+ }
2770
+ }
2771
+ }
2772
+ lines.push(`beat main "System Flow":`);
2773
+ lines.push(` show $nodes stagger=60ms`);
2774
+ if (flows.length > 0) {
2775
+ for (const flow of flows) {
2776
+ if (flow.label) {
2777
+ lines.push(` ${flow.from} -> ${flow.to} "${flow.label}"`);
2778
+ } else {
2779
+ lines.push(` ${flow.from} -> ${flow.to}`);
2780
+ }
2781
+ }
2782
+ }
2783
+ return {
2784
+ code: lines.join("\n"),
2785
+ nodeCount: nodes.length,
2786
+ edgeCount: flows.length
2787
+ };
2788
+ }
2789
+
11
2790
  // src/tools.ts
12
- import {
13
- parse,
14
- validateArchitecture,
15
- analyzeAndBuildRepairPrompt
16
- } from "@markdy/core";
17
- import {
18
- transpileMermaidToMarkdy,
19
- transpileDockerComposeToMarkdy,
20
- transpileKubernetesManifestsToMarkdy,
21
- transpileTerraformStateToMarkdy,
22
- transpileDrawioToMarkdy
23
- } from "@markdy/compat";
2791
+ var ARCHITECTURE_TEMPLATES = [
2792
+ {
2793
+ id: "microservices-db",
2794
+ title: "Cloud Microservices & Database Tier",
2795
+ category: "Cloud / Microservices",
2796
+ description: "Multi-tier microservices architecture with API gateway, auth, database, and Redis cache.",
2797
+ code: `scene theme=paper width=1440 height=760
2798
+ layout LR
2799
+
2800
+ browser WebApp "Web Application"
2801
+ mobile MobileApp "Mobile Client"
2802
+ gateway ApiGateway "Cloud Gateway"
2803
+ auth AuthService "Auth / OAuth2"
2804
+ service OrderService "Order Service"
2805
+ service PaymentService "Payment Gateway"
2806
+ database MainDB "PostgreSQL"
2807
+ cache RedisCache "Redis Cluster"
2808
+
2809
+ group clients "User Surfaces": WebApp MobileApp
2810
+ group backend "Service Tier": ApiGateway AuthService OrderService PaymentService
2811
+ group dataTier "Data Tier": MainDB RedisCache
2812
+
2813
+ beat reveal "System Overview":
2814
+ show $nodes stagger=40ms
2815
+
2816
+ beat authFlow "Authenticate Request":
2817
+ frame clients ApiGateway AuthService zoom=1.12
2818
+ WebApp -> ApiGateway "GET /profile" -> AuthService "validate_jwt"
2819
+ WebApp <- ApiGateway "200 OK (Claims)"
2820
+
2821
+ beat checkout "Process Order":
2822
+ frame ApiGateway OrderService PaymentService dataTier zoom=1.1
2823
+ MobileApp -> ApiGateway "POST /checkout" -> OrderService "create_order"
2824
+ OrderService -> RedisCache "check inventory"
2825
+ OrderService -> PaymentService "authorize charge"
2826
+ PaymentService -> MainDB "record transaction"
2827
+ MobileApp <- ApiGateway "201 Created"`
2828
+ },
2829
+ {
2830
+ id: "ai-rag-pipeline",
2831
+ title: "AI Agent & RAG Pipeline",
2832
+ category: "AI / Machine Learning",
2833
+ description: "Retrieval-Augmented Generation agent flow with embedding vector search, LLM synthesis, and sandbox tool execution.",
2834
+ code: `scene theme=editorial width=1440 height=760
2835
+ layout LR
2836
+
2837
+ user User "Engineer"
2838
+ browser ChatUI "Chat Interface"
2839
+ service Orchestrator "Agent Orchestrator"
2840
+ service Embedder "Embedding Model"
2841
+ database VectorDB "Vector Index (Qdrant)"
2842
+ service LLM "Claude 3.5 / Gemini"
2843
+ service Tools "Tool Execution Engine"
2844
+
2845
+ group aiCore "Intelligence Engine": Embedder VectorDB LLM
2846
+ group execution "Tools & Sandbox": Tools
2847
+
2848
+ beat init "System Reveal":
2849
+ show $nodes stagger=50ms
2850
+
2851
+ beat retrieve "Query & Vector Search":
2852
+ frame User ChatUI Orchestrator aiCore zoom=1.12
2853
+ User -> ChatUI "Ask technical question" -> Orchestrator "parse intent"
2854
+ Orchestrator -> Embedder "embed(query)" -> VectorDB "cosine search (k=5)"
2855
+ Orchestrator <- VectorDB "retrieved context chunks"
2856
+
2857
+ beat generate "Synthesis & Tool Execution":
2858
+ frame Orchestrator LLM Tools zoom=1.15
2859
+ Orchestrator -> LLM "prompt + context"
2860
+ LLM -> Tools "execute_code(sql)"
2861
+ LLM <- Tools "tool_result"
2862
+ ChatUI <- Orchestrator "streamed response with citations"
2863
+ glow ChatUI color=#10b981`
2864
+ },
2865
+ {
2866
+ id: "kafka-event-driven",
2867
+ title: "Event-Driven Architecture & Kafka Fan-Out",
2868
+ category: "Messaging / Streaming",
2869
+ description: "Event ingestion and parallel asynchronous consumer worker fan-out with dead-letter queue.",
2870
+ code: `scene theme=midnight width=1440 height=760
2871
+ layout LR
2872
+
2873
+ service IngestionAPI "Ingestion API"
2874
+ queue KafkaTopic "orders.events"
2875
+ worker InventoryWorker "Inventory Worker"
2876
+ worker NotificationWorker "Email/SMS Worker"
2877
+ worker AnalyticsWorker "Clickhouse Sink"
2878
+ database InventoryDB "Inventory DB"
2879
+ database AnalyticsDB "Clickhouse"
2880
+ queue DLQ "Dead Letter Queue"
2881
+
2882
+ group workers "Consumer Worker Group": InventoryWorker NotificationWorker AnalyticsWorker
2883
+
2884
+ beat reveal "Topology":
2885
+ show $nodes stagger=40ms
2886
+
2887
+ beat publish "Publish Event":
2888
+ frame IngestionAPI KafkaTopic zoom=1.15
2889
+ IngestionAPI ~> KafkaTopic "publish(OrderPlaced)"
2890
+ glow KafkaTopic color=#38bdf8
2891
+
2892
+ beat fanout "Parallel Fan-out Processing":
2893
+ frame KafkaTopic workers zoom=1.12
2894
+ KafkaTopic ~> InventoryWorker "consume event" & KafkaTopic ~> NotificationWorker "consume event" & KafkaTopic ~> AnalyticsWorker "consume event"
2895
+ InventoryWorker -> InventoryDB "UPDATE stock"
2896
+ AnalyticsWorker -> AnalyticsDB "INSERT analytics"`
2897
+ },
2898
+ {
2899
+ id: "k8s-ingress-cluster",
2900
+ title: "Kubernetes Cluster & Cloud Ingress",
2901
+ category: "DevOps / Infrastructure",
2902
+ description: "Cloudflare edge, Traefik ingress controller, pod deployments, ClusterIP routing, and persistent storage volumes.",
2903
+ code: `scene theme=blueprint width=1440 height=800
2904
+ layout LR
2905
+
2906
+ cloud CDN "Cloudflare CDN"
2907
+ network Ingress "Traefik Ingress Controller"
2908
+ pod WebPod1 "web-frontend-pod-1"
2909
+ pod WebPod2 "web-frontend-pod-2"
2910
+ service ClusterIP "api-service (ClusterIP)"
2911
+ pod ApiPod1 "api-backend-pod-1"
2912
+ pod ApiPod2 "api-backend-pod-2"
2913
+ storage PV "Ceph CSI Volume"
2914
+
2915
+ group frontendPods "Frontend Deployment": WebPod1 WebPod2
2916
+ group apiPods "API Deployment": ApiPod1 ApiPod2
2917
+
2918
+ beat reveal "Cluster Architecture":
2919
+ show $nodes stagger=40ms
2920
+
2921
+ beat routing "Ingress Traffic Routing":
2922
+ frame CDN Ingress frontendPods zoom=1.12
2923
+ CDN -> Ingress "HTTPS Request" -> WebPod1 "reverse proxy"
2924
+ WebPod1 -> ClusterIP "internal call" -> ApiPod1 "gRPC invocation"
2925
+ ApiPod1 -> PV "read/write volume"
2926
+ CDN <- Ingress "200 HTTP OK"`
2927
+ },
2928
+ {
2929
+ id: "cicd-gitops-pipeline",
2930
+ title: "CI/CD GitOps Delivery Pipeline",
2931
+ category: "DevOps / CI-CD",
2932
+ description: "GitHub commits, automated GitHub Actions testing, container registry push, and ArgoCD production deployment.",
2933
+ code: `scene theme=graphite width=1440 height=720
2934
+ layout LR
2935
+
2936
+ user Dev "Developer"
2937
+ service GitHub "GitHub Repository"
2938
+ worker Actions "GitHub Actions CI"
2939
+ registry DockerHub "Container Registry"
2940
+ service ArgoCD "ArgoCD Controller"
2941
+ cluster Production "Kubernetes Prod"
2942
+
2943
+ beat reveal "Pipeline Infrastructure":
2944
+ show $nodes stagger=45ms
2945
+
2946
+ beat build "Commit & Build Validation":
2947
+ frame Dev GitHub Actions DockerHub zoom=1.12
2948
+ Dev -> GitHub "git push origin main"
2949
+ GitHub ~> Actions "trigger workflow"
2950
+ Actions -> Actions "run unit & visual tests"
2951
+ Actions -> DockerHub "docker push image:v1.0.7"
2952
+ glow DockerHub color=#10b981
2953
+
2954
+ beat deploy "GitOps Sync & Deployment":
2955
+ frame DockerHub ArgoCD Production zoom=1.15
2956
+ ArgoCD -> GitHub "detect manifest drift"
2957
+ ArgoCD -> DockerHub "pull image:v1.0.7"
2958
+ ArgoCD -> Production "apply rollout"
2959
+ glow Production color=#22c55e`
2960
+ },
2961
+ {
2962
+ id: "oauth2-oidc-flow",
2963
+ title: "OAuth2 / OIDC Authentication Flow",
2964
+ category: "Security / Identity",
2965
+ description: "End-to-end authorization code grant flow with IdP redirect, consent, token exchange, and protected API access.",
2966
+ code: `scene theme=paper width=1440 height=720
2967
+ layout LR
2968
+
2969
+ browser User "End User Browser"
2970
+ service ClientApp "OAuth Client App"
2971
+ auth IdP "Identity Provider (Auth0/Okta)"
2972
+ service ResourceServer "Protected API Server"
2973
+
2974
+ beat reveal "System Overview":
2975
+ show $nodes stagger=50ms
2976
+
2977
+ beat redirect "Authorize & Consent":
2978
+ frame User ClientApp IdP zoom=1.15
2979
+ User -> ClientApp "click 'Login with IdP'"
2980
+ User <- ClientApp "302 Redirect to /authorize"
2981
+ User -> IdP "submit credentials & consent"
2982
+ User <- IdP "302 Redirect with ?code=AUTH_CODE"
2983
+
2984
+ beat exchange "Token Exchange & API Access":
2985
+ frame ClientApp IdP ResourceServer zoom=1.15
2986
+ ClientApp -> IdP "POST /token (code + secret)"
2987
+ ClientApp <- IdP "200 OK (access_token + id_token)"
2988
+ ClientApp -> ResourceServer "GET /userinfo (Bearer Token)"
2989
+ ClientApp <- ResourceServer "200 OK (User Profile)"
2990
+ glow ClientApp color=#10b981`
2991
+ },
2992
+ {
2993
+ id: "multi-region-ha-cache",
2994
+ title: "Resilient Multi-Region High Availability & Cache-Aside",
2995
+ category: "Distributed Systems",
2996
+ description: "GeoDNS global routing, primary/failover regions, Redis master/replica cache-aside pattern, and Aurora global storage replication.",
2997
+ code: `scene theme=midnight width=1440 height=760
2998
+ layout LR
2999
+
3000
+ gateway GeoDNS "Global Route53 / Anycast"
3001
+ gateway RegionEast "US-East Gateway"
3002
+ gateway RegionWest "US-West Gateway"
3003
+ cache RedisPrimary "Redis Master"
3004
+ cache RedisReplica "Redis Read Replica"
3005
+ database AuroraGlobal "Aurora Multi-Region DB"
3006
+
3007
+ group eastTier "US-East (Primary)": RegionEast RedisPrimary
3008
+ group westTier "US-West (Failover)": RegionWest RedisReplica
3009
+
3010
+ beat reveal "Global Infrastructure":
3011
+ show $nodes stagger=40ms
3012
+
3013
+ beat readCache "Cache-Aside Read Flow":
3014
+ frame GeoDNS eastTier AuroraGlobal zoom=1.12
3015
+ GeoDNS -> RegionEast "route nearest user" -> RedisPrimary "GET item:101"
3016
+ RegionEast <- RedisPrimary "cache miss"
3017
+ RegionEast -> AuroraGlobal "SELECT FROM db"
3018
+ RegionEast -> RedisPrimary "SET item:101 (TTL 60s)"
3019
+ GeoDNS <- RegionEast "200 OK (Payload)"
3020
+
3021
+ beat replication "Global Storage Replication":
3022
+ frame RedisPrimary RedisReplica AuroraGlobal zoom=1.15
3023
+ RedisPrimary ~> RedisReplica "async sync" & AuroraGlobal ~> AuroraGlobal "storage replication"`
3024
+ },
3025
+ {
3026
+ id: "decision-flowchart",
3027
+ title: "Quality Gate Decision Flowchart",
3028
+ category: "Workflows / Flowcharts",
3029
+ description: "Top-down pull request quality evaluation workflow with branch decision diamonds and fallback rejection states.",
3030
+ code: `scene theme=sketchy width=1280 height=720 type=flowchart
3031
+ layout TB
3032
+
3033
+ start PR "New Pull Request"
3034
+ decision LintCheck "Lint & Typecheck Passed?"
3035
+ decision TestCheck "All 142 Tests Passed?"
3036
+ decision A11yCheck "Lighthouse 100/100 Score?"
3037
+ step Merge "Merge into Main"
3038
+ end Reject "Reject & Post PR Feedback"
3039
+
3040
+ beat reveal "Quality Gates":
3041
+ show $nodes stagger=50ms
3042
+
3043
+ beat evaluate "Validation Pipeline":
3044
+ PR -> LintCheck "run eslint & tsc"
3045
+ LintCheck -> TestCheck "yes"
3046
+ TestCheck -> A11yCheck "yes"
3047
+ A11yCheck -> Merge "yes (approved)"
3048
+ glow Merge color=#10b981
3049
+
3050
+ beat failure "Fallback Reject Path":
3051
+ LintCheck -> Reject "no (syntax error)"
3052
+ TestCheck -> Reject "no (broken tests)"`
3053
+ }
3054
+ ];
24
3055
  function handleValidateMarkdy(code, checkArchitecture = true) {
25
3056
  try {
26
3057
  const ast = parse(code);
@@ -136,33 +3167,105 @@ function handleExplainArchitecture(code) {
136
3167
  }
137
3168
  function handleGenerateMarkdyPrompt(userGoal) {
138
3169
  const prompt = [
139
- `You are an expert system architecture designer specializing in MarkdyScript 0.8+ syntax and DSL.`,
3170
+ `You are an expert system architecture designer specializing in MarkdyScript syntax and DSL.`,
140
3171
  `Goal: ${userGoal}`,
141
3172
  ``,
142
3173
  `### Instructions & Authoritative Reference:`,
143
- `1. Follow the canonical MarkdyScript 0.8+ syntax and specification hosted at: https://markdy.com/AGENT.md`,
144
- `2. Start the scene with: \`scene theme=paper layout=LR\``,
145
- `3. Define nodes using semantic types (e.g. \`browser client\`, \`gateway api_gw\`, \`service auth_svc\`, \`database pg_db\`, \`cache redis\`, \`queue kafka\`, \`worker worker\`).`,
146
- `4. Organize components in \`group <id> "<Label>": <members...>\` boundaries.`,
147
- `5. Animate flows with canonical operators: \`->\` (request), \`<-\` (response), \`~>\` (event), and \`--\` (dependency).`,
148
- `6. Group narrative cues into \`beat <id> "<Description>":\` with \`show $nodes\`, \`glow\`, \`focus\`, \`frame\`.`,
149
- `7. Keep syntax clean, do not invent unsupported directives, and output ONLY valid MarkdyScript.`
3174
+ `1. Follow the canonical MarkdyScript specification hosted at: https://markdy.com/AGENT.md`,
3175
+ `2. Structure the diagram linearly in 4 distinct steps:`,
3176
+ ` - Step 1: Directives: \`scene theme=paper width=1280 height=720\` and \`layout LR\``,
3177
+ ` - Step 2: Semantic Nodes: \`<kind> <Id> ["Human Label"]\` (e.g., \`browser Client "Shopper"\`, \`gateway Gateway "API Gateway"\`, \`service OrderService\`, \`database OrdersDB\`)`,
3178
+ ` - Step 3: Groups (optional): \`group <id> "<Label>": <Node1> <Node2>\``,
3179
+ ` - Step 4: Storyboard Beats: \`beat <id> "<Caption>":\` containing indented flows and cues.`,
3180
+ `3. Flow Operators & Cycle Safety:`,
3181
+ ` - Use \`->\` for forward requests/calls.`,
3182
+ ` - Use \`<-\` for responses/returns (prevents cyclical layout overlap!). Never use \`->\` for return paths.`,
3183
+ ` - Use \`~>\` for asynchronous events and pub-sub messaging.`,
3184
+ `4. Visual Cues: Use canonical cues: \`show $nodes\`, \`hide\`, \`frame <targets> zoom=1.15\`, \`glow <targets> color=#hex\`, \`focus\`, and \`&\` for parallel execution.`,
3185
+ `5. Output self-contained, valid MarkdyScript only.`
150
3186
  ].join("\n");
151
3187
  return {
152
3188
  content: [{ type: "text", text: prompt }]
153
3189
  };
154
3190
  }
3191
+ function handleGetArchitectureCatalog(filterCategory) {
3192
+ const filtered = filterCategory ? ARCHITECTURE_TEMPLATES.filter(
3193
+ (t) => t.category.toLowerCase().includes(filterCategory.toLowerCase()) || t.id.toLowerCase().includes(filterCategory.toLowerCase())
3194
+ ) : ARCHITECTURE_TEMPLATES;
3195
+ const text = [
3196
+ `### Markdy Architecture Templates Catalog (${filtered.length} templates)`,
3197
+ "",
3198
+ ...filtered.map(
3199
+ (t) => [
3200
+ `#### ${t.title} (\`${t.id}\`)`,
3201
+ `- **Category:** ${t.category}`,
3202
+ `- **Description:** ${t.description}`,
3203
+ "```markdy",
3204
+ t.code,
3205
+ "```",
3206
+ ""
3207
+ ].join("\n")
3208
+ )
3209
+ ].join("\n");
3210
+ return {
3211
+ content: [{ type: "text", text }]
3212
+ };
3213
+ }
3214
+ function handleReadResource(uri) {
3215
+ switch (uri) {
3216
+ case "markdy://spec/agent-reference":
3217
+ return {
3218
+ contents: [
3219
+ {
3220
+ uri,
3221
+ mimeType: "text/markdown",
3222
+ text: `# MarkdyScript Canonical Specification Summary
3223
+
3224
+ Canonical URL: https://markdy.com/AGENT.md
3225
+
3226
+ Core Mental Model: Directives -> Nodes -> Groups -> Beats.
3227
+ Cycle Safety: Always use <- for responses back to callers to prevent layout rank cycles.`
3228
+ }
3229
+ ]
3230
+ };
3231
+ case "markdy://templates/catalog":
3232
+ return {
3233
+ contents: [
3234
+ {
3235
+ uri,
3236
+ mimeType: "application/json",
3237
+ text: JSON.stringify(ARCHITECTURE_TEMPLATES, null, 2)
3238
+ }
3239
+ ]
3240
+ };
3241
+ case "markdy://governance/rules":
3242
+ return {
3243
+ contents: [
3244
+ {
3245
+ uri,
3246
+ mimeType: "application/json",
3247
+ text: JSON.stringify(ARCH_RULE_PRESETS, null, 2)
3248
+ }
3249
+ ]
3250
+ };
3251
+ default:
3252
+ throw new Error(`Resource not found: ${uri}`);
3253
+ }
3254
+ }
155
3255
 
156
3256
  // src/index.ts
3257
+ var MCP_SERVER_VERSION = "1.0.25";
157
3258
  function createMarkdyMcpServer() {
158
3259
  const server = new Server(
159
3260
  {
160
3261
  name: "markdy-mcp-server",
161
- version: "0.8.26"
3262
+ version: MCP_SERVER_VERSION
162
3263
  },
163
3264
  {
164
3265
  capabilities: {
165
- tools: {}
3266
+ tools: {},
3267
+ resources: {},
3268
+ prompts: {}
166
3269
  }
167
3270
  }
168
3271
  );
@@ -171,12 +3274,18 @@ function createMarkdyMcpServer() {
171
3274
  tools: [
172
3275
  {
173
3276
  name: "validate_markdy_code",
174
- description: "Validates MarkdyScript syntax, detects architectural rule violations, and outputs diagnostics and healing suggestions.",
3277
+ description: "Validates MarkdyScript diagram syntax, detects architectural rule violations, and outputs diagnostic suggestions with AI healing prompts.",
175
3278
  inputSchema: {
176
3279
  type: "object",
177
3280
  properties: {
178
- code: { type: "string", description: "The MarkdyScript diagram code to validate." },
179
- checkArchitecture: { type: "boolean", description: "Whether to run Well-Architected governance rule checks." }
3281
+ code: {
3282
+ type: "string",
3283
+ description: "The complete MarkdyScript (.markdy) diagram code starting with 'scene'."
3284
+ },
3285
+ checkArchitecture: {
3286
+ type: "boolean",
3287
+ description: "Whether to run Well-Architected governance rule checks (e.g. layer boundaries, cycle detection, gateway checks). Default: true."
3288
+ }
180
3289
  },
181
3290
  required: ["code"]
182
3291
  }
@@ -187,38 +3296,63 @@ function createMarkdyMcpServer() {
187
3296
  inputSchema: {
188
3297
  type: "object",
189
3298
  properties: {
190
- source: { type: "string", description: "The source code or content to transpile." },
3299
+ source: {
3300
+ type: "string",
3301
+ description: "The source code or markup content to transpile into MarkdyScript."
3302
+ },
191
3303
  format: {
192
3304
  type: "string",
193
3305
  enum: ["mermaid", "docker-compose", "k8s", "terraform", "drawio"],
194
- description: "The source format."
3306
+ description: "The source format to transpile from. Must be exactly one of: 'mermaid', 'docker-compose', 'k8s', 'terraform', 'drawio'."
195
3307
  },
196
- title: { type: "string", description: "Optional title for the resulting scene." }
3308
+ title: {
3309
+ type: "string",
3310
+ description: "Optional human-readable title for the generated scene header."
3311
+ }
197
3312
  },
198
3313
  required: ["source", "format"]
199
3314
  }
200
3315
  },
201
3316
  {
202
3317
  name: "explain_architecture",
203
- description: "Analyzes a MarkdyScript AST and generates a structured summary of components, topology, and governance health.",
3318
+ description: "Analyzes a MarkdyScript AST to output structural topology summaries, component role counts, and governance health metrics.",
204
3319
  inputSchema: {
205
3320
  type: "object",
206
3321
  properties: {
207
- code: { type: "string", description: "The MarkdyScript code to analyze." }
3322
+ code: {
3323
+ type: "string",
3324
+ description: "The MarkdyScript diagram code to inspect."
3325
+ }
208
3326
  },
209
3327
  required: ["code"]
210
3328
  }
211
3329
  },
212
3330
  {
213
3331
  name: "generate_markdy_prompt",
214
- description: "Generates optimal LLM system prompts and grammar constraints for building high-quality Markdy architecture animations.",
3332
+ description: "Generates optimal LLM system prompts and grammar constraints for building high-quality, hallucination-resistant Markdy architecture animations.",
215
3333
  inputSchema: {
216
3334
  type: "object",
217
3335
  properties: {
218
- userGoal: { type: "string", description: "The architecture or flow description the user wants to visualize." }
3336
+ userGoal: {
3337
+ type: "string",
3338
+ description: "The architecture, workflow, or system flow description the user wants to visualize."
3339
+ }
219
3340
  },
220
3341
  required: ["userGoal"]
221
3342
  }
3343
+ },
3344
+ {
3345
+ name: "get_architecture_catalog",
3346
+ description: "Returns the catalog of production-grade golden architecture templates (Microservices, RAG, Kafka, Kubernetes Ingress, GitOps CI/CD, OAuth2, HA Multi-Region, Flowcharts) with full runnable MarkdyScript code.",
3347
+ inputSchema: {
3348
+ type: "object",
3349
+ properties: {
3350
+ filterCategory: {
3351
+ type: "string",
3352
+ description: "Optional filter string (e.g. 'Cloud', 'AI', 'Messaging', 'Security', 'DevOps')."
3353
+ }
3354
+ }
3355
+ }
222
3356
  }
223
3357
  ]
224
3358
  };
@@ -242,10 +3376,164 @@ function createMarkdyMcpServer() {
242
3376
  return handleExplainArchitecture(String(safeArgs.code ?? ""));
243
3377
  case "generate_markdy_prompt":
244
3378
  return handleGenerateMarkdyPrompt(String(safeArgs.userGoal ?? ""));
3379
+ case "get_architecture_catalog":
3380
+ return handleGetArchitectureCatalog(safeArgs.filterCategory ? String(safeArgs.filterCategory) : void 0);
245
3381
  default:
246
3382
  throw new Error(`Unknown tool: ${name}`);
247
3383
  }
248
3384
  });
3385
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
3386
+ return {
3387
+ resources: [
3388
+ {
3389
+ uri: "markdy://spec/agent-reference",
3390
+ name: "MarkdyScript AI Agent Specification",
3391
+ description: "Canonical reference for MarkdyScript syntax, closed node vocabularies, and cycle-safety rules.",
3392
+ mimeType: "text/markdown"
3393
+ },
3394
+ {
3395
+ uri: "markdy://templates/catalog",
3396
+ name: "Markdy Architecture Templates Catalog",
3397
+ description: "JSON catalog of curated golden architecture templates.",
3398
+ mimeType: "application/json"
3399
+ },
3400
+ {
3401
+ uri: "markdy://governance/rules",
3402
+ name: "Well-Architected Governance Rules",
3403
+ description: "Lint presets and architecture rules for validating cloud architectures.",
3404
+ mimeType: "application/json"
3405
+ }
3406
+ ]
3407
+ };
3408
+ });
3409
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
3410
+ const { uri } = request.params;
3411
+ return handleReadResource(uri);
3412
+ });
3413
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
3414
+ return {
3415
+ prompts: [
3416
+ {
3417
+ name: "create_architecture_diagram",
3418
+ description: "Guided workflow to design an animated Markdy architecture diagram following the 4-step mental model.",
3419
+ arguments: [
3420
+ {
3421
+ name: "userGoal",
3422
+ description: "The system or architecture to visualize.",
3423
+ required: true
3424
+ },
3425
+ {
3426
+ name: "theme",
3427
+ description: "Preferred visual theme (paper, editorial, midnight, blueprint, graphite, nebula, sketchy, terminal).",
3428
+ required: false
3429
+ },
3430
+ {
3431
+ name: "layout",
3432
+ description: "Layout direction (LR, TB, RL, BT).",
3433
+ required: false
3434
+ }
3435
+ ]
3436
+ },
3437
+ {
3438
+ name: "audit_architecture",
3439
+ description: "Review a MarkdyScript diagram for Well-Architected governance, cycle overlap, and layer violations.",
3440
+ arguments: [
3441
+ {
3442
+ name: "code",
3443
+ description: "The MarkdyScript code to audit.",
3444
+ required: true
3445
+ }
3446
+ ]
3447
+ },
3448
+ {
3449
+ name: "transpile_architecture",
3450
+ description: "Migrate existing Mermaid, Docker Compose, Kubernetes, or Terraform configurations into animated MarkdyScript.",
3451
+ arguments: [
3452
+ {
3453
+ name: "source",
3454
+ description: "The source code to convert.",
3455
+ required: true
3456
+ },
3457
+ {
3458
+ name: "format",
3459
+ description: "Source format (mermaid, docker-compose, k8s, terraform, drawio).",
3460
+ required: true
3461
+ }
3462
+ ]
3463
+ }
3464
+ ]
3465
+ };
3466
+ });
3467
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
3468
+ const { name, arguments: args } = request.params;
3469
+ const safeArgs = args || {};
3470
+ switch (name) {
3471
+ case "create_architecture_diagram": {
3472
+ const theme = safeArgs.theme || "paper";
3473
+ const layout = safeArgs.layout || "LR";
3474
+ return {
3475
+ description: `Create an animated Markdy architecture diagram for: ${safeArgs.userGoal}`,
3476
+ messages: [
3477
+ {
3478
+ role: "user",
3479
+ content: {
3480
+ type: "text",
3481
+ text: `Design an animated Markdy architecture diagram for: ${safeArgs.userGoal}
3482
+
3483
+ Configuration:
3484
+ - Theme: ${theme}
3485
+ - Layout: ${layout}
3486
+
3487
+ Follow the canonical 4-step Markdy mental model:
3488
+ 1. Directives (scene theme=${theme} layout=${layout})
3489
+ 2. Node declarations (<kind> <Id> ["Human Label"])
3490
+ 3. Groups (group <id> "<Label>": ...)
3491
+ 4. Animated Storyboard beats with cycle-safe routing (use '<-' for return calls).`
3492
+ }
3493
+ }
3494
+ ]
3495
+ };
3496
+ }
3497
+ case "audit_architecture": {
3498
+ return {
3499
+ description: "Audit Markdy diagram against Well-Architected rules",
3500
+ messages: [
3501
+ {
3502
+ role: "user",
3503
+ content: {
3504
+ type: "text",
3505
+ text: `Please validate and audit the following MarkdyScript diagram for syntax integrity, cycle overlap, and Well-Architected rules:
3506
+
3507
+ \`\`\`markdy
3508
+ ${safeArgs.code}
3509
+ \`\`\``
3510
+ }
3511
+ }
3512
+ ]
3513
+ };
3514
+ }
3515
+ case "transpile_architecture": {
3516
+ return {
3517
+ description: `Transpile ${safeArgs.format} to MarkdyScript`,
3518
+ messages: [
3519
+ {
3520
+ role: "user",
3521
+ content: {
3522
+ type: "text",
3523
+ text: `Please convert the following ${safeArgs.format} configuration into an animated Markdy diagram:
3524
+
3525
+ \`\`\`
3526
+ ${safeArgs.source}
3527
+ \`\`\``
3528
+ }
3529
+ }
3530
+ ]
3531
+ };
3532
+ }
3533
+ default:
3534
+ throw new Error(`Unknown prompt: ${name}`);
3535
+ }
3536
+ });
249
3537
  return server;
250
3538
  }
251
3539
  async function startMcpServer() {
@@ -253,16 +3541,20 @@ async function startMcpServer() {
253
3541
  const transport = new StdioServerTransport();
254
3542
  await server.connect(transport);
255
3543
  }
256
- if (process.argv[1] && process.argv[1].endsWith("index.js")) {
3544
+ var isDirectExecution = typeof process !== "undefined" && Boolean(process.argv[1]) && (process.argv[1].endsWith("index.js") || process.argv[1].endsWith("index.mjs") || process.argv[1].endsWith("markdy-mcp") || process.argv[1].endsWith("markdy-mcp.js") || process.argv[1].endsWith("markdy-mcp.mjs") || process.argv[1].includes("mcp-server"));
3545
+ if (isDirectExecution) {
257
3546
  startMcpServer().catch((err) => {
258
3547
  console.error("Fatal MCP Server Error:", err);
259
3548
  process.exit(1);
260
3549
  });
261
3550
  }
262
3551
  export {
3552
+ MCP_SERVER_VERSION,
263
3553
  createMarkdyMcpServer,
264
3554
  handleExplainArchitecture,
265
3555
  handleGenerateMarkdyPrompt,
3556
+ handleGetArchitectureCatalog,
3557
+ handleReadResource,
266
3558
  handleTranspileToMarkdy,
267
3559
  handleValidateMarkdy,
268
3560
  startMcpServer