@markdy/core 1.2.0 → 1.3.1

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
@@ -731,7 +731,8 @@ var EDGE_OPERATORS = {
731
731
  "->": "request",
732
732
  "<-": "response",
733
733
  "~>": "event",
734
- "--": "dependency"
734
+ "--": "dependency",
735
+ "..>": "dependency"
735
736
  };
736
737
  var RESERVED_SELECTORS = /* @__PURE__ */ new Set(["$title", "$nodes", "$edges"]);
737
738
  var CUE_ALIASES = {
@@ -818,10 +819,13 @@ function computeNodeDimensions(decl, baseW = 180, baseH = 76) {
818
819
  if (decl.kind === "dot") return { width: 64, height: 64 };
819
820
  if (decl.kind === "matrix") return { width: 220, height: 96 };
820
821
  const valueW = valLen > 0 ? Math.max(50, valLen * 9.5 + 16) : 0;
821
- const neededLabelChars = labelLen > 18 ? Math.ceil(labelLen / 2) : labelLen;
822
+ const words = (decl.label || "").trim().split(/\s+/).filter(Boolean);
823
+ const longestWord = Math.max(...words.map((w) => w.length), 0);
824
+ const avgLineChars = words.length > 1 ? Math.ceil(labelLen / Math.min(words.length, 2)) : labelLen;
825
+ const neededLabelChars = Math.max(longestWord + 2, avgLineChars, Math.min(labelLen, 22));
822
826
  const maxChars = Math.max(neededLabelChars, techLen);
823
- const neededTextW = Math.max(88, maxChars * 7.8);
824
- const calculatedW = 56 + neededTextW + (valueW > 0 ? valueW + 14 : 0) + 16;
827
+ const neededTextW = Math.max(96, maxChars * 8.4);
828
+ const calculatedW = 56 + neededTextW + (valueW > 0 ? valueW + 14 : 0) + 18;
825
829
  const minW = Math.max(baseW, Math.min(360, calculatedW));
826
830
  let width = Math.ceil(minW / 8) * 8;
827
831
  let height = baseH;
@@ -1913,17 +1917,43 @@ function scheduleBeats(ast, edges) {
1913
1917
  let edgeCounter = edges.length;
1914
1918
  const edgeIds = edges.map((e) => e.id);
1915
1919
  const groupMap = Object.fromEntries(Object.entries(ast.groups).map(([k, g]) => [k, g.members]));
1916
- const hasIntro = ast.beats.some((b) => b.cues.some((c) => c.kind === "show"));
1917
- if (!hasIntro && Object.keys(ast.nodes).length > 0) {
1918
- cues.push({
1919
- start: 0,
1920
- duration: DEFAULTS.show,
1921
- kind: "show",
1922
- targets: Object.keys(ast.nodes),
1923
- params: { stagger: DEFAULTS.stagger },
1924
- beat: "__intro"
1925
- });
1926
- t += DEFAULTS.show + DEFAULTS.cueGap;
1920
+ const hasShowCue = ast.beats.some(
1921
+ (b) => b.cues.some((c) => c.kind === "show" || c.kind === "parallel" && c.cues.some((pc) => pc.kind === "show"))
1922
+ );
1923
+ const flowNodeIds = /* @__PURE__ */ new Set();
1924
+ for (const b of ast.beats) {
1925
+ for (const c of b.cues) {
1926
+ if (c.kind === "flow") {
1927
+ for (const seg of c.segments) {
1928
+ flowNodeIds.add(seg.from);
1929
+ flowNodeIds.add(seg.to);
1930
+ }
1931
+ } else if (c.kind === "parallel") {
1932
+ for (const pc of c.cues) {
1933
+ if (pc.kind === "flow") {
1934
+ for (const seg of pc.segments) {
1935
+ flowNodeIds.add(seg.from);
1936
+ flowNodeIds.add(seg.to);
1937
+ }
1938
+ }
1939
+ }
1940
+ }
1941
+ }
1942
+ }
1943
+ const isSequence = ast.meta.type === "sequence";
1944
+ if (!hasShowCue && Object.keys(ast.nodes).length > 0) {
1945
+ const nodesToReveal = !isSequence && flowNodeIds.size > 0 ? Object.keys(ast.nodes).filter((id) => !flowNodeIds.has(id)) : Object.keys(ast.nodes);
1946
+ if (nodesToReveal.length > 0) {
1947
+ cues.push({
1948
+ start: 0,
1949
+ duration: DEFAULTS.show,
1950
+ kind: "show",
1951
+ targets: nodesToReveal,
1952
+ params: { stagger: DEFAULTS.stagger },
1953
+ beat: "__intro"
1954
+ });
1955
+ t += DEFAULTS.show + DEFAULTS.cueGap;
1956
+ }
1927
1957
  }
1928
1958
  for (const beat of ast.beats) {
1929
1959
  const beatStart = t;
@@ -2434,7 +2464,7 @@ var THEMES = {
2434
2464
  dependency: "#818cf8"
2435
2465
  },
2436
2466
  fonts: {
2437
- title: "Georgia, Times New Roman, serif",
2467
+ title: "ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Inter, sans-serif",
2438
2468
  nodeName: "ui-sans-serif, system-ui, sans-serif",
2439
2469
  mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
2440
2470
  },
@@ -2658,7 +2688,7 @@ var ParseError = class extends Error {
2658
2688
  this.column = column;
2659
2689
  }
2660
2690
  };
2661
- var FLOW_OP_RE = /(->|<-|~>|--)/;
2691
+ var FLOW_OP_RE = /(->|<-|~>|--|\.\.>|<->)/;
2662
2692
  function stripComment(line) {
2663
2693
  let inString = false;
2664
2694
  let escaped = false;
@@ -2710,7 +2740,7 @@ function parseProps(raw) {
2710
2740
  i = raw.length - parsed.rest.length;
2711
2741
  continue;
2712
2742
  }
2713
- const keyMatch = raw.slice(i).match(/^(\w[\w.-]*)=/);
2743
+ const keyMatch = raw.slice(i).match(/^(@?[\w.-]+)=/);
2714
2744
  if (!keyMatch) {
2715
2745
  i++;
2716
2746
  continue;
@@ -2828,6 +2858,14 @@ function tokenizeFlowChain(line) {
2828
2858
  current += ch;
2829
2859
  continue;
2830
2860
  }
2861
+ const op3 = line.slice(i, i + 3);
2862
+ if (op3 === "..>" || op3 === "<->") {
2863
+ if (current.trim()) parts.push(current.trim());
2864
+ parts.push(op3);
2865
+ current = "";
2866
+ i += 2;
2867
+ continue;
2868
+ }
2831
2869
  const op = line.slice(i, i + 2);
2832
2870
  if (op === "->" || op === "<-" || op === "~>" || op === "--") {
2833
2871
  if (current.trim()) parts.push(current.trim());
@@ -2851,11 +2889,27 @@ function parseFlowChain(line, lineNo) {
2851
2889
  let from = splitTargetLabel(parts[i++], lineNo).node;
2852
2890
  while (i < parts.length) {
2853
2891
  const opToken = parts[i++];
2854
- const op = EDGE_OPERATORS[opToken];
2855
- if (!op) throw new ParseError(`unknown flow operator '${opToken}'`, lineNo);
2856
2892
  if (i >= parts.length) throw new ParseError(`expected target after '${opToken}'`, lineNo);
2857
2893
  const { node: to, label } = splitTargetLabel(parts[i++], lineNo);
2858
2894
  if (!to) throw new ParseError(`expected target node after '${opToken}'`, lineNo);
2895
+ if (opToken === "<->") {
2896
+ segments.push({
2897
+ from,
2898
+ op: "request",
2899
+ to,
2900
+ label
2901
+ });
2902
+ segments.push({
2903
+ from: to,
2904
+ op: "response",
2905
+ to: from,
2906
+ label
2907
+ });
2908
+ from = to;
2909
+ continue;
2910
+ }
2911
+ const op = EDGE_OPERATORS[opToken];
2912
+ if (!op) throw new ParseError(`unknown flow operator '${opToken}'`, lineNo);
2859
2913
  segments.push({
2860
2914
  from: op === "response" ? to : from,
2861
2915
  op,
@@ -3806,7 +3860,13 @@ function matchNode(node, selector) {
3806
3860
  if (selector.kindEquals && node.kind.toLowerCase() !== selector.kindEquals.toLowerCase()) return false;
3807
3861
  if (selector.roleEquals) {
3808
3862
  const role = nodeRole(node.kind);
3809
- if (role.toLowerCase() !== selector.roleEquals.toLowerCase()) return false;
3863
+ const targetRole = selector.roleEquals.toLowerCase();
3864
+ if (targetRole === "gateway") {
3865
+ const isGateway = role === "network" || ["gateway", "api_gateway", "reverse_proxy", "proxy", "router"].includes(node.kind.toLowerCase());
3866
+ if (!isGateway) return false;
3867
+ } else if (role.toLowerCase() !== targetRole) {
3868
+ return false;
3869
+ }
3810
3870
  }
3811
3871
  if (selector.labelContains) {
3812
3872
  const target = (node.label || node.id).toLowerCase();
@@ -3967,6 +4027,30 @@ var ARCH_RULE_PRESETS = {
3967
4027
  from: { roleEquals: "security" }
3968
4028
  }
3969
4029
  ]
4030
+ },
4031
+ deploymentOwnership: {
4032
+ id: "deployment-ownership",
4033
+ name: "Deployment Ownership & Regional Governance",
4034
+ description: "Ensure production stateful stores and services are guarded in explicit group perimeters",
4035
+ rules: [
4036
+ {
4037
+ id: "no-unprotected-public-database",
4038
+ name: "No Public Database Exposure",
4039
+ description: "Databases and stateful stores must not be directly accessed from public browser/mobile clients.",
4040
+ severity: "error",
4041
+ type: "cannot-connect",
4042
+ from: { roleEquals: "client" },
4043
+ to: { roleEquals: "data" }
4044
+ },
4045
+ {
4046
+ id: "no-cross-region-sync-bypasses",
4047
+ name: "Synchronous Request Cycle Prevention",
4048
+ description: "Synchronous requests must not form cycles across microservices.",
4049
+ severity: "error",
4050
+ type: "forbidden-cycle",
4051
+ edge: { kind: "request" }
4052
+ }
4053
+ ]
3970
4054
  }
3971
4055
  };
3972
4056
  function validateArchitecture(ast, rules = [
@@ -3974,7 +4058,7 @@ function validateArchitecture(ast, rules = [
3974
4058
  ...ARCH_RULE_PRESETS.microservicesGovernance.rules
3975
4059
  ]) {
3976
4060
  const violations = [];
3977
- const nodes = Object.values(ast.nodes);
4061
+ const nodes = Object.values(ast?.nodes || {});
3978
4062
  const nodeMap = new Map(nodes.map((n) => [n.id, n]));
3979
4063
  const edges = extractAllEdges(ast);
3980
4064
  for (const rule of rules) {
@@ -4021,6 +4105,11 @@ function validateArchitecture(ast, rules = [
4021
4105
  break;
4022
4106
  }
4023
4107
  case "forbidden-cycle": {
4108
+ const dtype = ast.meta?.type || ast.config?.type || "architecture";
4109
+ const nonServiceArchetypes = ["state", "sequence", "layers", "flywheel", "loop", "venn"];
4110
+ if (nonServiceArchetypes.includes(dtype)) {
4111
+ break;
4112
+ }
4024
4113
  const cycleInfo = detectCycleInGraph(nodes, edges, rule.edge);
4025
4114
  if (cycleInfo) {
4026
4115
  violations.push({
@@ -4271,26 +4360,66 @@ function classifyTechnology(id, label = "") {
4271
4360
  }
4272
4361
 
4273
4362
  // src/diff.ts
4363
+ function extractDiffEdges(ast) {
4364
+ const edges = [];
4365
+ const seen = /* @__PURE__ */ new Set();
4366
+ const add = (from, to, kind, label) => {
4367
+ const key = `${from}->${to}:${kind}`;
4368
+ if (!seen.has(key)) {
4369
+ seen.add(key);
4370
+ edges.push({ from, to, kind, label });
4371
+ }
4372
+ };
4373
+ for (const edge of ast.edges || []) {
4374
+ add(edge.from, edge.to, edge.kind, edge.label);
4375
+ }
4376
+ for (const beat of ast.beats || []) {
4377
+ for (const cue of beat.cues || []) {
4378
+ if (cue.kind === "flow") {
4379
+ for (const seg of cue.segments) {
4380
+ add(seg.from, seg.to, seg.op, seg.label);
4381
+ }
4382
+ } else if (cue.kind === "parallel") {
4383
+ for (const child of cue.cues) {
4384
+ if (child.kind === "flow") {
4385
+ for (const seg of child.segments) {
4386
+ add(seg.from, seg.to, seg.op, seg.label);
4387
+ }
4388
+ }
4389
+ }
4390
+ }
4391
+ }
4392
+ }
4393
+ return edges;
4394
+ }
4274
4395
  function diffDiagramASTs(beforeAST, afterAST) {
4275
4396
  const nodeDiffs = [];
4276
4397
  const edgeDiffs = [];
4277
- const beforeNodeIds = new Set(Object.keys(beforeAST.nodes));
4278
- const afterNodeIds = new Set(Object.keys(afterAST.nodes));
4398
+ const groupDiffs = [];
4399
+ const beforeNodes = beforeAST.nodes || {};
4400
+ const afterNodes = afterAST.nodes || {};
4401
+ const beforeNodeIds = new Set(Object.keys(beforeNodes));
4402
+ const afterNodeIds = new Set(Object.keys(afterNodes));
4279
4403
  let addedNodesCount = 0;
4280
4404
  let removedNodesCount = 0;
4281
4405
  let modifiedNodesCount = 0;
4282
- for (const [id, afterNode] of Object.entries(afterAST.nodes)) {
4406
+ for (const [id, afterNode] of Object.entries(afterNodes)) {
4283
4407
  if (!beforeNodeIds.has(id)) {
4284
- nodeDiffs.push({ id, status: "added", after: afterNode, changes: ["Newly added node"] });
4408
+ nodeDiffs.push({ id, status: "added", after: afterNode, changes: ["Newly provisioned node"] });
4285
4409
  addedNodesCount++;
4286
4410
  } else {
4287
- const beforeNode = beforeAST.nodes[id];
4411
+ const beforeNode = beforeNodes[id];
4288
4412
  const changes = [];
4289
4413
  if (beforeNode.kind !== afterNode.kind) {
4290
- changes.push(`Kind changed: ${beforeNode.kind} \u2192 ${afterNode.kind}`);
4414
+ changes.push(`Kind: ${beforeNode.kind} \u2192 ${afterNode.kind}`);
4291
4415
  }
4292
4416
  if (beforeNode.label !== afterNode.label) {
4293
- changes.push(`Label changed: "${beforeNode.label}" \u2192 "${afterNode.label}"`);
4417
+ changes.push(`Label: "${beforeNode.label}" \u2192 "${afterNode.label}"`);
4418
+ }
4419
+ const beforeSrc = beforeNode.props?.["@src"] || beforeNode.props?.["src"];
4420
+ const afterSrc = afterNode.props?.["@src"] || afterNode.props?.["src"];
4421
+ if (beforeSrc !== afterSrc) {
4422
+ changes.push(`Code Provenance: ${beforeSrc || "none"} \u2192 ${afterSrc || "none"}`);
4294
4423
  }
4295
4424
  if (changes.length > 0) {
4296
4425
  nodeDiffs.push({ id, status: "modified", before: beforeNode, after: afterNode, changes });
@@ -4300,84 +4429,137 @@ function diffDiagramASTs(beforeAST, afterAST) {
4300
4429
  }
4301
4430
  }
4302
4431
  }
4303
- for (const [id, beforeNode] of Object.entries(beforeAST.nodes)) {
4432
+ for (const [id, beforeNode] of Object.entries(beforeNodes)) {
4304
4433
  if (!afterNodeIds.has(id)) {
4305
- nodeDiffs.push({ id, status: "removed", before: beforeNode, changes: ["Removed node"] });
4434
+ nodeDiffs.push({ id, status: "removed", before: beforeNode, changes: ["Decommissioned node"] });
4306
4435
  removedNodesCount++;
4307
4436
  }
4308
4437
  }
4309
4438
  const edgeKey = (e) => `${e.from}->${e.to}:${e.kind}`;
4310
- const beforeEdgeMap = new Map(beforeAST.edges.map((e) => [edgeKey(e), e]));
4311
- const afterEdgeMap = new Map(afterAST.edges.map((e) => [edgeKey(e), e]));
4312
- for (const [key, afterEdge] of afterEdgeMap) {
4439
+ const beforeEdges = extractDiffEdges(beforeAST);
4440
+ const afterEdges = extractDiffEdges(afterAST);
4441
+ const beforeEdgeMap = new Map(beforeEdges.map((e) => [edgeKey(e), e]));
4442
+ const afterEdgeMap = new Map(afterEdges.map((e) => [edgeKey(e), e]));
4443
+ let addedEdgesCount = 0;
4444
+ let removedEdgesCount = 0;
4445
+ for (const [key, afterEdge] of afterEdgeMap.entries()) {
4313
4446
  if (!beforeEdgeMap.has(key)) {
4314
- edgeDiffs.push({ key, status: "added", after: afterEdge });
4447
+ edgeDiffs.push({ key, status: "added", after: afterEdge, changes: ["New interaction route"] });
4448
+ addedEdgesCount++;
4315
4449
  } else {
4316
4450
  const beforeEdge = beforeEdgeMap.get(key);
4451
+ const changes = [];
4317
4452
  if (beforeEdge.label !== afterEdge.label) {
4318
- edgeDiffs.push({ key, status: "modified", before: beforeEdge, after: afterEdge });
4453
+ changes.push(`Protocol/Label: "${beforeEdge.label || ""}" \u2192 "${afterEdge.label || ""}"`);
4454
+ }
4455
+ if (changes.length > 0) {
4456
+ edgeDiffs.push({ key, status: "modified", before: beforeEdge, after: afterEdge, changes });
4319
4457
  } else {
4320
- edgeDiffs.push({ key, status: "unchanged", before: beforeEdge, after: afterEdge });
4458
+ edgeDiffs.push({ key, status: "unchanged", before: beforeEdge, after: afterEdge, changes: [] });
4321
4459
  }
4322
4460
  }
4323
4461
  }
4324
- for (const [key, beforeEdge] of beforeEdgeMap) {
4462
+ for (const [key, beforeEdge] of beforeEdgeMap.entries()) {
4325
4463
  if (!afterEdgeMap.has(key)) {
4326
- edgeDiffs.push({ key, status: "removed", before: beforeEdge });
4464
+ edgeDiffs.push({ key, status: "removed", before: beforeEdge, changes: ["Decommissioned route"] });
4465
+ removedEdgesCount++;
4327
4466
  }
4328
4467
  }
4329
- const summaryLines = [
4330
- "### \u{1F4CA} Markdy Architectural Diff Summary",
4331
- "",
4332
- `| Metric | Count |`,
4333
- `|---|---|`,
4334
- `| \u{1F7E2} Nodes Added | **${addedNodesCount}** |`,
4335
- `| \u{1F534} Nodes Removed | **${removedNodesCount}** |`,
4336
- `| \u{1F7E1} Nodes Modified | **${modifiedNodesCount}** |`,
4337
- ""
4338
- ];
4339
- if (addedNodesCount > 0 || modifiedNodesCount > 0 || removedNodesCount > 0) {
4340
- summaryLines.push("#### Changes Detail");
4341
- for (const nd of nodeDiffs.filter((n) => n.status !== "unchanged")) {
4342
- summaryLines.push(`- **${nd.id}** (${nd.status.toUpperCase()}): ${nd.changes.join(", ")}`);
4468
+ const beforeGroups = beforeAST.groups || {};
4469
+ const afterGroups = afterAST.groups || {};
4470
+ const beforeGroupIds = new Set(Object.keys(beforeGroups));
4471
+ const afterGroupIds = new Set(Object.keys(afterGroups));
4472
+ for (const [id, afterGroup] of Object.entries(afterGroups)) {
4473
+ if (!beforeGroupIds.has(id)) {
4474
+ groupDiffs.push({ id, status: "added", after: afterGroup, changes: ["New security / subsystem boundary"] });
4475
+ } else {
4476
+ const beforeGroup = beforeGroups[id];
4477
+ const beforeMembers = new Set(beforeGroup.members || []);
4478
+ const afterMembers = new Set(afterGroup.members || []);
4479
+ const diffMembers = (afterGroup.members || []).filter((m) => !beforeMembers.has(m));
4480
+ if (diffMembers.length > 0 || beforeGroup.members.length !== afterGroup.members.length) {
4481
+ groupDiffs.push({
4482
+ id,
4483
+ status: "modified",
4484
+ before: beforeGroup,
4485
+ after: afterGroup,
4486
+ changes: [`Boundary membership updated: [${(afterGroup.members || []).join(", ")}]`]
4487
+ });
4488
+ }
4343
4489
  }
4344
- summaryLines.push("");
4345
4490
  }
4346
- const evolutionLines = [
4347
- `scene theme=${afterAST.meta.theme || "paper"}`,
4348
- `layout ${afterAST.meta.direction || "LR"}`,
4349
- ""
4350
- ];
4351
- for (const [id, node] of Object.entries({ ...beforeAST.nodes, ...afterAST.nodes })) {
4352
- evolutionLines.push(`${node.kind} ${id} "${node.label}"`);
4353
- }
4354
- evolutionLines.push("");
4355
- evolutionLines.push('beat v1 "Baseline Architecture":');
4356
- const v1NodeIds = Object.keys(beforeAST.nodes).join(" ");
4357
- if (v1NodeIds) {
4358
- evolutionLines.push(` show ${v1NodeIds}`);
4359
- }
4360
- evolutionLines.push("");
4361
- evolutionLines.push('beat transition "Migrate to Target Architecture":');
4362
- const addedIds = nodeDiffs.filter((n) => n.status === "added").map((n) => n.id);
4363
- const removedIds = nodeDiffs.filter((n) => n.status === "removed").map((n) => n.id);
4364
- if (removedIds.length > 0) {
4365
- evolutionLines.push(` hide ${removedIds.join(" ")}`);
4366
- }
4367
- if (addedIds.length > 0) {
4368
- evolutionLines.push(` show ${addedIds.join(" ")}`);
4369
- evolutionLines.push(` glow ${addedIds.join(" ")} color="#10b981"`);
4491
+ for (const [id, beforeGroup] of Object.entries(beforeGroups)) {
4492
+ if (!afterGroupIds.has(id)) {
4493
+ groupDiffs.push({ id, status: "removed", before: beforeGroup, changes: ["Dissolved boundary"] });
4494
+ }
4370
4495
  }
4496
+ const evolutionMarkdyScript = generateEvolutionMarkdyScript(
4497
+ beforeAST,
4498
+ afterAST,
4499
+ nodeDiffs,
4500
+ edgeDiffs
4501
+ );
4502
+ const summaryMarkdown = [
4503
+ `# Markdy Architectural Diff Summary`,
4504
+ `- **Nodes Added**: ${addedNodesCount}`,
4505
+ `- **Nodes Removed**: ${removedNodesCount}`,
4506
+ `- **Nodes Modified**: ${modifiedNodesCount}`,
4507
+ `- **Routes Added**: ${addedEdgesCount}`,
4508
+ `- **Routes Removed**: ${removedEdgesCount}`,
4509
+ "",
4510
+ `### Component Delta`,
4511
+ `| Component | Status | Details |`,
4512
+ `| :--- | :--- | :--- |`,
4513
+ ...nodeDiffs.filter((n) => n.status !== "unchanged").map((n) => `| \`${n.id}\` | **${n.status.toUpperCase()}** | ${n.changes.join("; ")} |`),
4514
+ "",
4515
+ `### Connection Delta`,
4516
+ `| Connection | Status | Details |`,
4517
+ `| :--- | :--- | :--- |`,
4518
+ ...edgeDiffs.filter((e) => e.status !== "unchanged").map((e) => `| \`${e.key}\` | **${e.status.toUpperCase()}** | ${e.changes.join("; ")} |`)
4519
+ ].join("\n");
4371
4520
  return {
4372
4521
  nodes: nodeDiffs,
4373
4522
  edges: edgeDiffs,
4523
+ groups: groupDiffs,
4374
4524
  addedNodesCount,
4375
4525
  removedNodesCount,
4376
4526
  modifiedNodesCount,
4377
- summaryMarkdown: summaryLines.join("\n"),
4378
- evolutionMarkdyScript: evolutionLines.join("\n")
4527
+ addedEdgesCount,
4528
+ removedEdgesCount,
4529
+ summaryMarkdown,
4530
+ evolutionMarkdyScript
4379
4531
  };
4380
4532
  }
4533
+ function generateEvolutionMarkdyScript(beforeAST, afterAST, nodeDiffs, edgeDiffs) {
4534
+ const lines = [`scene theme=paper`, `layout ${afterAST.meta?.direction || "LR"}`, ""];
4535
+ const allNodes = /* @__PURE__ */ new Map();
4536
+ for (const [id, n] of Object.entries(beforeAST.nodes || {})) allNodes.set(id, n);
4537
+ for (const [id, n] of Object.entries(afterAST.nodes || {})) allNodes.set(id, n);
4538
+ for (const [id, node] of allNodes.entries()) {
4539
+ const propsStr = Object.entries(node.props || {}).map(([k, v]) => `${k}=${typeof v === "string" ? `"${v}"` : v}`).join(" ");
4540
+ lines.push(`${node.kind} ${id} "${node.label}" ${propsStr}`.trim());
4541
+ }
4542
+ lines.push("");
4543
+ const beforeNodeIds = Object.keys(beforeAST.nodes || {}).join(" ");
4544
+ lines.push(`beat baseline:`);
4545
+ lines.push(` show ${beforeNodeIds}`);
4546
+ for (const edge of beforeAST.edges || []) {
4547
+ lines.push(` ${edge.from} -> ${edge.to} "${edge.label || ""}"`);
4548
+ }
4549
+ const removedNodeIds = nodeDiffs.filter((n) => n.status === "removed").map((n) => n.id);
4550
+ const addedNodeIds = nodeDiffs.filter((n) => n.status === "added").map((n) => n.id);
4551
+ lines.push("");
4552
+ lines.push(`beat transition:`);
4553
+ if (removedNodeIds.length > 0) {
4554
+ lines.push(` glow ${removedNodeIds.join(" ")} color=#fb7185 strength=1.2`);
4555
+ lines.push(` hide ${removedNodeIds.join(" ")} dur=800ms`);
4556
+ }
4557
+ if (addedNodeIds.length > 0) {
4558
+ lines.push(` show ${addedNodeIds.join(" ")} stagger=80ms`);
4559
+ lines.push(` glow ${addedNodeIds.join(" ")} color=#34d399 strength=1.5`);
4560
+ }
4561
+ return lines.join("\n");
4562
+ }
4381
4563
 
4382
4564
  // src/url-codec.ts
4383
4565
  var PREFIX = "~m";
@@ -4469,16 +4651,27 @@ async function decompressMarkdyFromUrlHash(hash) {
4469
4651
  }
4470
4652
 
4471
4653
  // src/router.ts
4472
- function getBoxPortPosition(box, port) {
4473
- switch (port) {
4474
- case "left":
4475
- return { x: box.x, y: box.y + box.height / 2 };
4476
- case "right":
4477
- return { x: box.x + box.width, y: box.y + box.height / 2 };
4478
- case "top":
4479
- return { x: box.x + box.width / 2, y: box.y };
4480
- case "bottom":
4481
- return { x: box.x + box.width / 2, y: box.y + box.height };
4654
+ function getBoxPortPosition(box, port, lane) {
4655
+ if (port === "left" || port === "right") {
4656
+ const x = port === "left" ? box.x : box.x + box.width;
4657
+ if (lane && lane.total > 1) {
4658
+ const padding = Math.min(16, box.height * 0.18);
4659
+ const span = box.height - padding * 2;
4660
+ const step = span / (lane.total - 1 || 1);
4661
+ const y = box.y + padding + lane.index * step;
4662
+ return { x, y };
4663
+ }
4664
+ return { x, y: box.y + box.height / 2 };
4665
+ } else {
4666
+ const y = port === "top" ? box.y : box.y + box.height;
4667
+ if (lane && lane.total > 1) {
4668
+ const padding = Math.min(16, box.width * 0.18);
4669
+ const span = box.width - padding * 2;
4670
+ const step = span / (lane.total - 1 || 1);
4671
+ const x = box.x + padding + lane.index * step;
4672
+ return { x, y };
4673
+ }
4674
+ return { x: box.x + box.width / 2, y };
4482
4675
  }
4483
4676
  }
4484
4677
  function selectOptimalPorts(sourceBox, targetBox) {
@@ -4492,12 +4685,96 @@ function selectOptimalPorts(sourceBox, targetBox) {
4492
4685
  return dy > 0 ? { sourcePort: "bottom", targetPort: "top" } : { sourcePort: "top", targetPort: "bottom" };
4493
4686
  }
4494
4687
  }
4495
- function routeOrthogonalEdge(sourceBox, targetBox) {
4496
- const { sourcePort, targetPort } = selectOptimalPorts(sourceBox, targetBox);
4497
- const start = getBoxPortPosition(sourceBox, sourcePort);
4498
- const end = getBoxPortPosition(targetBox, targetPort);
4688
+ function buildSmoothSvgPath(start, waypoints, end, cornerRadius = 0) {
4689
+ const allPoints = [start, ...waypoints, end];
4690
+ if (allPoints.length <= 2 || cornerRadius <= 0) {
4691
+ let d2 = `M ${start.x} ${start.y}`;
4692
+ for (const p of waypoints) {
4693
+ d2 += ` L ${p.x} ${p.y}`;
4694
+ }
4695
+ d2 += ` L ${end.x} ${end.y}`;
4696
+ return d2;
4697
+ }
4698
+ let d = `M ${start.x} ${start.y}`;
4699
+ for (let i = 1; i < allPoints.length - 1; i++) {
4700
+ const prev = allPoints[i - 1];
4701
+ const curr = allPoints[i];
4702
+ const next = allPoints[i + 1];
4703
+ const dPrev = Math.hypot(curr.x - prev.x, curr.y - prev.y);
4704
+ const dNext = Math.hypot(next.x - curr.x, next.y - curr.y);
4705
+ const r = Math.min(cornerRadius, dPrev / 2, dNext / 2);
4706
+ if (r < 2) {
4707
+ d += ` L ${curr.x} ${curr.y}`;
4708
+ continue;
4709
+ }
4710
+ const startX = curr.x + (prev.x - curr.x) * (r / dPrev);
4711
+ const startY = curr.y + (prev.y - curr.y) * (r / dPrev);
4712
+ const endX = curr.x + (next.x - curr.x) * (r / dNext);
4713
+ const endY = curr.y + (next.y - curr.y) * (r / dNext);
4714
+ d += ` L ${startX} ${startY}`;
4715
+ d += ` Q ${curr.x} ${curr.y} ${endX} ${endY}`;
4716
+ }
4717
+ d += ` L ${end.x} ${end.y}`;
4718
+ return d;
4719
+ }
4720
+ function routeOrthogonalEdge(sourceBox, targetBox, options = {}) {
4721
+ const isSelfLoop = sourceBox === targetBox || sourceBox.x === targetBox.x && sourceBox.y === targetBox.y && sourceBox.width === targetBox.width && sourceBox.height === targetBox.height;
4722
+ const MARGIN = options.margin ?? 20;
4723
+ if (isSelfLoop) {
4724
+ const loopSpan = Math.max(28, MARGIN * 1.5);
4725
+ const port = options.sourcePort || options.targetPort || "top";
4726
+ let start2;
4727
+ let end2;
4728
+ let waypoints2;
4729
+ if (port === "top") {
4730
+ const startX = sourceBox.x + sourceBox.width * 0.35;
4731
+ const endX = sourceBox.x + sourceBox.width * 0.65;
4732
+ const topY = sourceBox.y;
4733
+ const apexY = topY - loopSpan;
4734
+ start2 = { x: startX, y: topY };
4735
+ end2 = { x: endX, y: topY };
4736
+ waypoints2 = [{ x: startX, y: apexY }, { x: endX, y: apexY }];
4737
+ } else if (port === "bottom") {
4738
+ const startX = sourceBox.x + sourceBox.width * 0.35;
4739
+ const endX = sourceBox.x + sourceBox.width * 0.65;
4740
+ const botY = sourceBox.y + sourceBox.height;
4741
+ const apexY = botY + loopSpan;
4742
+ start2 = { x: startX, y: botY };
4743
+ end2 = { x: endX, y: botY };
4744
+ waypoints2 = [{ x: startX, y: apexY }, { x: endX, y: apexY }];
4745
+ } else if (port === "left") {
4746
+ const startY = sourceBox.y + sourceBox.height * 0.35;
4747
+ const endY = sourceBox.y + sourceBox.height * 0.65;
4748
+ const leftX = sourceBox.x;
4749
+ const apexX = leftX - loopSpan;
4750
+ start2 = { x: leftX, y: startY };
4751
+ end2 = { x: leftX, y: endY };
4752
+ waypoints2 = [{ x: apexX, y: startY }, { x: apexX, y: endY }];
4753
+ } else {
4754
+ const startY = sourceBox.y + sourceBox.height * 0.35;
4755
+ const endY = sourceBox.y + sourceBox.height * 0.65;
4756
+ const rightX = sourceBox.x + sourceBox.width;
4757
+ const apexX = rightX + loopSpan;
4758
+ start2 = { x: rightX, y: startY };
4759
+ end2 = { x: rightX, y: endY };
4760
+ waypoints2 = [{ x: apexX, y: startY }, { x: apexX, y: endY }];
4761
+ }
4762
+ const svgPathData2 = buildSmoothSvgPath(start2, waypoints2, end2, options.cornerRadius ?? 6);
4763
+ return {
4764
+ sourcePort: port,
4765
+ targetPort: port,
4766
+ startPoint: start2,
4767
+ endPoint: end2,
4768
+ waypoints: waypoints2,
4769
+ svgPathData: svgPathData2
4770
+ };
4771
+ }
4772
+ const optimal = selectOptimalPorts(sourceBox, targetBox);
4773
+ const sourcePort = options.sourcePort || optimal.sourcePort;
4774
+ const targetPort = options.targetPort || optimal.targetPort;
4775
+ const start = getBoxPortPosition(sourceBox, sourcePort, options.sourceLane);
4776
+ const end = getBoxPortPosition(targetBox, targetPort, options.targetLane);
4499
4777
  const waypoints = [];
4500
- const MARGIN = 20;
4501
4778
  if (sourcePort === "right" && targetPort === "left") {
4502
4779
  if (start.x <= end.x - MARGIN * 2) {
4503
4780
  const midX = (start.x + end.x) / 2;
@@ -4564,11 +4841,7 @@ function routeOrthogonalEdge(sourceBox, targetBox) {
4564
4841
  }
4565
4842
  waypoints.push(p2);
4566
4843
  }
4567
- let svgPathData = `M ${start.x} ${start.y}`;
4568
- for (const wp of waypoints) {
4569
- svgPathData += ` L ${wp.x} ${wp.y}`;
4570
- }
4571
- svgPathData += ` L ${end.x} ${end.y}`;
4844
+ const svgPathData = buildSmoothSvgPath(start, waypoints, end, options.cornerRadius ?? 6);
4572
4845
  return {
4573
4846
  sourcePort,
4574
4847
  targetPort,
@@ -4578,6 +4851,550 @@ function routeOrthogonalEdge(sourceBox, targetBox) {
4578
4851
  svgPathData
4579
4852
  };
4580
4853
  }
4854
+ function allocatePortLanes(edges, boxes) {
4855
+ const result = /* @__PURE__ */ new Map();
4856
+ const portGroups = /* @__PURE__ */ new Map();
4857
+ for (const edge of edges) {
4858
+ if (edge.from === edge.to) continue;
4859
+ const sBox = boxes[edge.from];
4860
+ const tBox = boxes[edge.to];
4861
+ if (!sBox || !tBox) continue;
4862
+ const { sourcePort, targetPort } = selectOptimalPorts(sBox, tBox);
4863
+ const sKey = `${edge.from}:${sourcePort}`;
4864
+ const tKey = `${edge.to}:${targetPort}`;
4865
+ const tgtCenter = { x: tBox.x + tBox.width / 2, y: tBox.y + tBox.height / 2 };
4866
+ const srcCenter = { x: sBox.x + sBox.width / 2, y: sBox.y + sBox.height / 2 };
4867
+ if (!portGroups.has(sKey)) portGroups.set(sKey, []);
4868
+ portGroups.get(sKey).push({
4869
+ edge,
4870
+ role: "source",
4871
+ otherCenterY: tgtCenter.y,
4872
+ otherCenterX: tgtCenter.x
4873
+ });
4874
+ if (!portGroups.has(tKey)) portGroups.set(tKey, []);
4875
+ portGroups.get(tKey).push({
4876
+ edge,
4877
+ role: "target",
4878
+ otherCenterY: srcCenter.y,
4879
+ otherCenterX: srcCenter.x
4880
+ });
4881
+ }
4882
+ for (const [key, list] of portGroups.entries()) {
4883
+ if (list.length <= 1) continue;
4884
+ const isVertical = key.endsWith(":left") || key.endsWith(":right");
4885
+ list.sort((a, b) => {
4886
+ if (isVertical) {
4887
+ if (Math.abs(a.otherCenterY - b.otherCenterY) > 1) {
4888
+ return a.otherCenterY - b.otherCenterY;
4889
+ }
4890
+ } else {
4891
+ if (Math.abs(a.otherCenterX - b.otherCenterX) > 1) {
4892
+ return a.otherCenterX - b.otherCenterX;
4893
+ }
4894
+ }
4895
+ const aMin = a.edge.from < a.edge.to ? a.edge.from : a.edge.to;
4896
+ const aMax = a.edge.from < a.edge.to ? a.edge.to : a.edge.from;
4897
+ const bMin = b.edge.from < b.edge.to ? b.edge.from : b.edge.to;
4898
+ const bMax = b.edge.from < b.edge.to ? b.edge.to : b.edge.from;
4899
+ if (aMin !== bMin || aMax !== bMax) {
4900
+ return (a.edge.id || "").localeCompare(b.edge.id || "");
4901
+ }
4902
+ const aDir = a.edge.from < a.edge.to ? 0 : 1;
4903
+ const bDir = b.edge.from < b.edge.to ? 0 : 1;
4904
+ return aDir - bDir;
4905
+ });
4906
+ list.forEach((item, index) => {
4907
+ const existing = result.get(item.edge) || {};
4908
+ if (item.role === "source") {
4909
+ existing.sourceLane = { index, total: list.length };
4910
+ } else {
4911
+ existing.targetLane = { index, total: list.length };
4912
+ }
4913
+ result.set(item.edge, existing);
4914
+ });
4915
+ }
4916
+ return result;
4917
+ }
4918
+
4919
+ // src/symbols.ts
4920
+ var VECTOR_SYMBOLS = {
4921
+ // Cloud & Infrastructure
4922
+ aws: {
4923
+ name: "AWS",
4924
+ category: "cloud",
4925
+ viewBox: "0 0 24 24",
4926
+ svgPaths: `<path fill="currentColor" d="M12 2L2 7l10 5 10-5-10-5zm0 8.5L4.5 7 12 3.25 19.5 7 12 10.5zM2 17l10 5 10-5M2 12l10 5 10-5"/>`,
4927
+ brandColor: "#FF9900"
4928
+ },
4929
+ gcp: {
4930
+ name: "Google Cloud",
4931
+ category: "cloud",
4932
+ viewBox: "0 0 24 24",
4933
+ svgPaths: `<path fill="currentColor" d="M19.35 10.04C18.67 6.59 15.64 4 12 4 9.11 4 6.6 5.64 5.35 8.04 2.34 8.36 0 10.91 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM19 18H6c-2.21 0-4-1.79-4-4 0-2.05 1.53-3.76 3.56-3.97l1.07-.11.5-.95C8.08 7.14 9.94 6 12 6c2.62 0 4.88 1.86 5.39 4.43l.3 1.5 1.53.11c1.56.1 2.78 1.41 2.78 2.96 0 1.65-1.35 3-3 3z"/>`,
4934
+ brandColor: "#4285F4"
4935
+ },
4936
+ azure: {
4937
+ name: "Microsoft Azure",
4938
+ category: "cloud",
4939
+ viewBox: "0 0 24 24",
4940
+ svgPaths: `<path fill="currentColor" d="M13.05 4.24l-6.1 11.23L2 17.52l7.73-14.28 3.32 1zm1.09 1.94L18.42 16h-7.8l-1.92 3.76H22l-7.86-13.58z"/>`,
4941
+ brandColor: "#0089D6"
4942
+ },
4943
+ kubernetes: {
4944
+ name: "Kubernetes",
4945
+ category: "compute",
4946
+ viewBox: "0 0 24 24",
4947
+ svgPaths: `<path fill="currentColor" d="M12 2l8.66 5v10L12 22l-8.66-5V7L12 2zm0 2.31L5.34 7.69v7.62L12 18.69l6.66-3.38V7.69L12 4.31zm0 3.69a4 4 0 1 1 0 8 4 4 0 0 1 0-8z"/>`,
4948
+ brandColor: "#326CE5"
4949
+ },
4950
+ docker: {
4951
+ name: "Docker",
4952
+ category: "compute",
4953
+ viewBox: "0 0 24 24",
4954
+ svgPaths: `<path fill="currentColor" d="M13.98 11.08h1.83V9.25h-1.83v1.83zm-2.75 0h1.83V9.25h-1.83v1.83zm-2.75 0h1.83V9.25H8.48v1.83zm-2.75 0h1.83V9.25H5.73v1.83zm8.25-2.75h1.83V6.5h-1.83v1.83zm-2.75 0h1.83V6.5h-1.83v1.83zm-2.75 0h1.83V6.5H8.48v1.83zm8.25 0h1.83V6.5h-1.83v1.83zm1.83 5.5c-.4 0-.8.1-1.1.3-.8-1.5-2.4-2.5-4.3-2.5H2.4c-.2.7-.4 1.4-.4 2.2 0 4.4 3.6 8 8 8 5 0 9.2-3.6 9.9-8.4.8.1 1.6-.2 2.1-.8.4-.5.5-1.2.3-1.8-.7.7-1.5 1-2.3 1z"/>`,
4955
+ brandColor: "#2496ED"
4956
+ },
4957
+ cloudflare: {
4958
+ name: "Cloudflare",
4959
+ category: "cloud",
4960
+ viewBox: "0 0 24 24",
4961
+ svgPaths: `<path fill="currentColor" d="M18.42 10.36A6.5 6.5 0 0 0 7.2 9.04 4.5 4.5 0 0 0 3 13.5a4.5 4.5 0 0 0 4.5 4.5h11a3.5 3.5 0 0 0 .92-6.88v-.76z"/>`,
4962
+ brandColor: "#F38020"
4963
+ },
4964
+ s3: {
4965
+ name: "Amazon S3",
4966
+ category: "cloud",
4967
+ viewBox: "0 0 24 24",
4968
+ svgPaths: `<path fill="currentColor" d="M12 2L4 6v12l8 4 8-4V6l-8-4zm0 2.2L18.2 7 12 9.8 5.8 7 12 4.2zM6 8.5l5 2.5v8.2L6 16.7V8.5zm12 8.2l-5 2.5V11l5-2.5v8.2z"/>`,
4969
+ brandColor: "#E05243"
4970
+ },
4971
+ // Databases & Stores
4972
+ postgresql: {
4973
+ name: "PostgreSQL",
4974
+ category: "database",
4975
+ viewBox: "0 0 24 24",
4976
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.4z"/>`,
4977
+ brandColor: "#336791"
4978
+ },
4979
+ mysql: {
4980
+ name: "MySQL",
4981
+ category: "database",
4982
+ viewBox: "0 0 24 24",
4983
+ svgPaths: `<path fill="currentColor" d="M12 3c-4.97 0-9 1.79-9 4v10c0 2.21 4.03 4 9 4s9-1.79 9-4V7c0-2.21-4.03-4-9-4zm0 2c4.41 0 7 1.43 7 2s-2.59 2-7 2-7-1.43-7-2 2.59-2 7-2zm0 14c-4.41 0-7-1.43-7-2v-1.82c1.78 1.11 4.29 1.82 7 1.82s5.22-.71 7-1.82V17c0 .57-2.59 2-7 2zm0-5c-4.41 0-7-1.43-7-2v-1.82c1.78 1.11 4.29 1.82 7 1.82s5.22-.71 7-1.82V12c0 .57-2.59 2-7 2z"/>`,
4984
+ brandColor: "#4479A1"
4985
+ },
4986
+ redis: {
4987
+ name: "Redis",
4988
+ category: "database",
4989
+ viewBox: "0 0 24 24",
4990
+ svgPaths: `<path fill="currentColor" d="M12 2L2 7.5l10 5.5 10-5.5L12 2zm-8 8.7V17L12 22.5V16L4 10.7zm16 0L12 16v6.5l8-5.5v-6.3z"/>`,
4991
+ brandColor: "#DC382D"
4992
+ },
4993
+ mongodb: {
4994
+ name: "MongoDB",
4995
+ category: "database",
4996
+ viewBox: "0 0 24 24",
4997
+ svgPaths: `<path fill="currentColor" d="M12 2C11.5 3.5 7 10.5 7 15c0 3.5 2.5 6 5 7 2.5-1 5-3.5 5-7 0-4.5-4.5-11.5-5-13zm0 17.5c-1.5-.7-3-2.5-3-4.5 0-2.8 2.2-7 3-9 0.8 2 3 6.2 3 9 0 2-1.5 3.8-3 4.5z"/>`,
4998
+ brandColor: "#47A248"
4999
+ },
5000
+ cassandra: {
5001
+ name: "Apache Cassandra",
5002
+ category: "database",
5003
+ viewBox: "0 0 24 24",
5004
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 18a8 8 0 1 1 8-8 8 8 0 0 1-8 8zm-4-9h8v2H8z"/>`,
5005
+ brandColor: "#1287B1"
5006
+ },
5007
+ sqlite: {
5008
+ name: "SQLite",
5009
+ category: "database",
5010
+ viewBox: "0 0 24 24",
5011
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 4.69 2 8v8c0 3.31 4.48 6 10 6s10-2.69 10-6V8c0-3.31-4.48-6-10-6zm0 2c4.42 0 8 1.79 8 4s-3.58 4-8 4-8-1.79-8-4 3.58-4 8-4zm0 16c-4.42 0-8-1.79-8-4v-2.38c2.08 1.47 5.04 2.38 8 2.38s5.92-.91 8-2.38V16c0 2.21-3.58 4-8 4z"/>`,
5012
+ brandColor: "#003B57"
5013
+ },
5014
+ kafka: {
5015
+ name: "Apache Kafka",
5016
+ category: "messaging",
5017
+ viewBox: "0 0 24 24",
5018
+ svgPaths: `<path fill="currentColor" d="M12 3a9 9 0 1 0 9 9 9 9 0 0 0-9-9zm0 16a7 7 0 1 1 7-7 7 7 0 0 1-7 7zm-3-8a2 2 0 1 0-2-2 2 2 0 0 0 2 2zm6 0a2 2 0 1 0-2-2 2 2 0 0 0 2 2zm-3 6a2 2 0 1 0-2-2 2 2 0 0 0 2 2z"/>`,
5019
+ brandColor: "#231F20"
5020
+ },
5021
+ rabbitmq: {
5022
+ name: "RabbitMQ",
5023
+ category: "messaging",
5024
+ viewBox: "0 0 24 24",
5025
+ svgPaths: `<path fill="currentColor" d="M12 2a5 5 0 0 0-5 5v1H5a3 3 0 0 0-3 3v7a4 4 0 0 0 4 4h12a4 4 0 0 0 4-4v-7a3 3 0 0 0-3-3h-2V7a5 5 0 0 0-5-5zm-3 6a3 3 0 0 1 6 0v1H9V8zm-3 5h2v2H6v-2zm12 0h2v2h-2v-2z"/>`,
5026
+ brandColor: "#FF6600"
5027
+ },
5028
+ elasticsearch: {
5029
+ name: "Elasticsearch",
5030
+ category: "database",
5031
+ viewBox: "0 0 24 24",
5032
+ svgPaths: `<path fill="currentColor" d="M12 2A10 10 0 1 0 22 12 10 10 0 0 0 12 2zm-1 3.1a6.9 6.9 0 0 1 5.5 2.9h-11A6.9 6.9 0 0 1 11 5.1zM5.1 12c0-.7.1-1.3.3-2h13.2c.2.7.3 1.3.3 2s-.1 1.3-.3 2H5.4c-.2-.7-.3-1.3-.3-2zm5.9 6.9a6.9 6.9 0 0 1-5.5-2.9h11a6.9 6.9 0 0 1-5.5 2.9z"/>`,
5033
+ brandColor: "#005571"
5034
+ },
5035
+ // Compute, Gateway & Runtimes
5036
+ nodejs: {
5037
+ name: "Node.js",
5038
+ category: "runtime",
5039
+ viewBox: "0 0 24 24",
5040
+ svgPaths: `<path fill="currentColor" d="M12 2L3.5 7v10L12 22l8.5-5V7L12 2zm6.5 13.7L12 19.5l-6.5-3.8V8.3L12 4.5l6.5 3.8v7.4z"/>`,
5041
+ brandColor: "#339933"
5042
+ },
5043
+ python: {
5044
+ name: "Python",
5045
+ category: "runtime",
5046
+ viewBox: "0 0 24 24",
5047
+ svgPaths: `<path fill="currentColor" d="M11.9 2c-3.1 0-4.9.4-4.9 2.2V6h5v1.5H5.8C3.7 7.5 2 9.2 2 11.3c0 2.2 1.4 3.7 3.8 3.7h1.4v-1.9c0-1.8 1.6-3.3 3.5-3.3h5.2V7.7C15.9 4.1 14.8 2 11.9 2zM9 4.2a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6zm3.1 17.8c3.1 0 4.9-.4 4.9-2.2V18h-5v-1.5h6.2c2.1 0 3.8-1.7 3.8-3.8 0-2.2-1.4-3.7-3.8-3.7h-1.4v1.9c0 1.8-1.6 3.3-3.5 3.3H8.6v2.1c0 3.6 1.1 5.7 4.5 5.7zM15 18.2a.8.8 0 1 1 0 1.6.8.8 0 0 1 0-1.6z"/>`,
5048
+ brandColor: "#3776AB"
5049
+ },
5050
+ golang: {
5051
+ name: "Go",
5052
+ category: "runtime",
5053
+ viewBox: "0 0 24 24",
5054
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm4 11h-3v2h3v2h-5v-6h5v2zm-7-2H7v-2h2v2z"/>`,
5055
+ brandColor: "#00ADD8"
5056
+ },
5057
+ rust: {
5058
+ name: "Rust",
5059
+ category: "runtime",
5060
+ viewBox: "0 0 24 24",
5061
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 14.5l-2-3h-1v3H8V7.5h4a3 3 0 0 1 3 3 3 3 0 0 1-2 2.8l2 3.2zm-1-6.5h-2v2h2a1 1 0 0 0 1-1 1 1 0 0 0-1-1z"/>`,
5062
+ brandColor: "#DEA584"
5063
+ },
5064
+ nginx: {
5065
+ name: "Nginx",
5066
+ category: "gateway",
5067
+ viewBox: "0 0 24 24",
5068
+ svgPaths: `<path fill="currentColor" d="M12 2L2 7.8v8.4L12 22l10-5.8V7.8L12 2zm-4 13.5V8.5l3 3.5v3.5l-3-3.5zm8 0l-3-3.5V8.5l3 3.5v3.5z"/>`,
5069
+ brandColor: "#009639"
5070
+ },
5071
+ envoy: {
5072
+ name: "Envoy Proxy",
5073
+ category: "gateway",
5074
+ viewBox: "0 0 24 24",
5075
+ svgPaths: `<path fill="currentColor" d="M12 2L2 7v10l10 5 10-5V7L12 2zm0 2.2L19.5 8 12 11.8 4.5 8 12 4.2zM4 9.5l7 3.5v7.2L4 16.7V9.5zm16 7.2l-7 3.5V13l7-3.5v7.2z"/>`,
5076
+ brandColor: "#BF360C"
5077
+ },
5078
+ graphql: {
5079
+ name: "GraphQL",
5080
+ category: "gateway",
5081
+ viewBox: "0 0 24 24",
5082
+ svgPaths: `<path fill="currentColor" d="M12 2l8.66 5v10L12 22l-8.66-5V7L12 2zm0 2.4L5.5 8.1v7.8L12 19.6l6.5-3.7V8.1L12 4.4zM12 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4zm0 6a2 2 0 1 1 0 4 2 2 0 0 1 0-4z"/>`,
5083
+ brandColor: "#E10098"
5084
+ },
5085
+ // Clients & Browsers
5086
+ chrome: {
5087
+ name: "Google Chrome",
5088
+ category: "client",
5089
+ viewBox: "0 0 24 24",
5090
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm0 3.6a6.4 6.4 0 0 1 5.48 3.1h-5.48a3.3 3.3 0 0 0-3.1 2.2L6.16 6.16A6.36 6.36 0 0 1 12 5.6zm-6.4 6.4a6.4 6.4 0 0 1 .52-2.54l2.74 4.74a3.3 3.3 0 0 0 3.1 1.76v4.6A6.4 6.4 0 0 1 5.6 12zm6.4 6.4a6.36 6.36 0 0 1-4.74-2.14l2.74-4.74a3.3 3.3 0 0 0 2 0l2.74 4.74A6.36 6.36 0 0 1 12 18.4zm0-4.4a2 2 0 1 1 2-2 2 2 0 0 1-2 2z"/>`,
5091
+ brandColor: "#4285F4"
5092
+ },
5093
+ terminal: {
5094
+ name: "Terminal",
5095
+ category: "client",
5096
+ viewBox: "0 0 24 24",
5097
+ svgPaths: `<path fill="currentColor" d="M20 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 14H4V8h16v10zm-12-3l3-3-3-3 1.41-1.41L12.83 12l-3.42 3.41L8 15zm6 0h4v2h-4v-2z"/>`,
5098
+ brandColor: "#4ADE80"
5099
+ },
5100
+ // Security & Identity
5101
+ vault: {
5102
+ name: "HashiCorp Vault",
5103
+ category: "security",
5104
+ viewBox: "0 0 24 24",
5105
+ svgPaths: `<path fill="currentColor" d="M12 2L4 6v6c0 5.55 3.84 10.74 8 12 4.16-1.26 8-6.45 8-12V6l-8-4zm0 6a3 3 0 0 1 3 3c0 1.3-.84 2.4-2 2.82V17h-2v-3.18A3 3 0 0 1 9 11a3 3 0 0 1 3-3z"/>`,
5106
+ brandColor: "#000000"
5107
+ },
5108
+ opa: {
5109
+ name: "Open Policy Agent",
5110
+ category: "security",
5111
+ viewBox: "0 0 24 24",
5112
+ svgPaths: `<path fill="currentColor" d="M12 2L3 7v10l9 5 9-5V7l-9-5zm0 2.3l6.7 3.7v7.4L12 19.1 5.3 15.4V8l6.7-3.7zM12 7a5 5 0 1 0 5 5 5 5 0 0 0-5-5z"/>`,
5113
+ brandColor: "#5B7382"
5114
+ },
5115
+ keycloak: {
5116
+ name: "Keycloak",
5117
+ category: "security",
5118
+ viewBox: "0 0 24 24",
5119
+ svgPaths: `<path fill="currentColor" d="M12 2a5 5 0 0 0-5 5v3H5a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-8a2 2 0 0 0-2-2h-2V7a5 5 0 0 0-5-5zm-3 5a3 3 0 0 1 6 0v3H9V7zm3 6a2 2 0 0 1 2 2v2a2 2 0 0 1-4 0v-2a2 2 0 0 1 2-2z"/>`,
5120
+ brandColor: "#0088CE"
5121
+ },
5122
+ // Observability & SRE
5123
+ prometheus: {
5124
+ name: "Prometheus",
5125
+ category: "observability",
5126
+ viewBox: "0 0 24 24",
5127
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 14h-2v-6h2v6zm0-8h-2V6h2v2z"/>`,
5128
+ brandColor: "#E6522C"
5129
+ },
5130
+ datadog: {
5131
+ name: "Datadog",
5132
+ category: "observability",
5133
+ viewBox: "0 0 24 24",
5134
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm4.5 14H15v-3h-2v3h-1.5v-6H13v1.5h2V10h1.5v6zm-7 0H8v-6h3a2 2 0 0 1 2 2v2a2 2 0 0 1-2 2H9.5zm0-4.5V14H11a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5H9.5z"/>`,
5135
+ brandColor: "#632CA6"
5136
+ },
5137
+ jaeger: {
5138
+ name: "Jaeger Tracing",
5139
+ category: "observability",
5140
+ viewBox: "0 0 24 24",
5141
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm-1 5h2v6h-2zm0 8h2v2h-2z"/>`,
5142
+ brandColor: "#60D0E4"
5143
+ },
5144
+ grafana: {
5145
+ name: "Grafana",
5146
+ category: "observability",
5147
+ viewBox: "0 0 24 24",
5148
+ svgPaths: `<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 14.93c-2.82-.41-5-2.85-5-5.93 0-.82.16-1.6.46-2.31l3.07 3.07c-.12.4-.19.82-.19 1.25 0 1.66 1.34 3 3 3 .43 0 .85-.07 1.25-.19l-2.59-2.59zM16.5 12c0 1.93-1.12 3.6-2.76 4.38l-4.12-4.12A4.47 4.47 0 0 1 12 7.5c2.48 0 4.5 2.02 4.5 4.5z"/>`,
5149
+ brandColor: "#F46800"
5150
+ },
5151
+ pagerduty: {
5152
+ name: "PagerDuty",
5153
+ category: "observability",
5154
+ viewBox: "0 0 24 24",
5155
+ svgPaths: `<path fill="currentColor" d="M5 3h7a6 6 0 0 1 6 6 6 6 0 0 1-6 6H9v6H5V3zm4 8h3a2 2 0 0 0 2-2 2 2 0 0 0-2-2H9v4z"/>`,
5156
+ brandColor: "#04AC38"
5157
+ },
5158
+ slack: {
5159
+ name: "Slack",
5160
+ category: "messaging",
5161
+ viewBox: "0 0 24 24",
5162
+ svgPaths: `<path fill="currentColor" d="M6 15a2 2 0 1 1-2-2h2v2zm1 0a2 2 0 0 1 2-2 2 2 0 0 1 2 2v5a2 2 0 1 1-4 0v-5zm2-8a2 2 0 1 1-2-2 2 2 0 0 1 2 2v2zm0 1a2 2 0 0 1 2 2 2 2 0 0 1-2 2H4a2 2 0 1 1 0-4h5zm8 2a2 2 0 1 1 2 2h-2v-2zm-1 0a2 2 0 0 1-2 2 2 2 0 0 1-2-2V5a2 2 0 1 1 4 0v5zm-2 8a2 2 0 1 1 2 2 2 2 0 0 1-2-2v-2zm0-1a2 2 0 0 1-2-2 2 2 0 0 1 2-2h5a2 2 0 1 1 0 4h-5z"/>`,
5163
+ brandColor: "#4A154B"
5164
+ },
5165
+ // Big Data, Lakehouse & AI
5166
+ spark: {
5167
+ name: "Apache Spark",
5168
+ category: "data",
5169
+ viewBox: "0 0 24 24",
5170
+ svgPaths: `<path fill="currentColor" d="M12 2l2.4 7.4h7.6l-6.2 4.5 2.4 7.4-6.2-4.5-6.2 4.5 2.4-7.4-6.2-4.5h7.6z"/>`,
5171
+ brandColor: "#E25A1C"
5172
+ },
5173
+ flink: {
5174
+ name: "Apache Flink",
5175
+ category: "data",
5176
+ viewBox: "0 0 24 24",
5177
+ svgPaths: `<path fill="currentColor" d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2zm1 15h-2v-5h2zm0-7h-2V8h2z"/>`,
5178
+ brandColor: "#E6522C"
5179
+ },
5180
+ snowflake: {
5181
+ name: "Snowflake",
5182
+ category: "data",
5183
+ viewBox: "0 0 24 24",
5184
+ svgPaths: `<path fill="currentColor" d="M12 2v20m-7.07-2.93l14.14-14.14M2 12h20M4.93 4.93l14.14 14.14" stroke="currentColor" stroke-width="2" fill="none"/>`,
5185
+ brandColor: "#29B5E8"
5186
+ },
5187
+ delta: {
5188
+ name: "Delta Lake",
5189
+ category: "data",
5190
+ viewBox: "0 0 24 24",
5191
+ svgPaths: `<path fill="currentColor" d="M12 2L2 22h20L12 2zm0 5l6.5 13H5.5L12 7z"/>`,
5192
+ brandColor: "#00A4E4"
5193
+ },
5194
+ superset: {
5195
+ name: "Apache Superset",
5196
+ category: "data",
5197
+ viewBox: "0 0 24 24",
5198
+ svgPaths: `<path fill="currentColor" d="M3 3h4v18H3zm7 6h4v12h-4zm7-4h4v16h-4z"/>`,
5199
+ brandColor: "#20A6B2"
5200
+ },
5201
+ gemini: {
5202
+ name: "Google Gemini",
5203
+ category: "compute",
5204
+ viewBox: "0 0 24 24",
5205
+ svgPaths: `<path fill="currentColor" d="M12 2C12 7.52 7.52 12 2 12c5.48 0 9.95 4.48 10 10 .05-5.52 4.52-10 10-10-5.48 0-9.95-4.48-10-10z"/>`,
5206
+ brandColor: "#8E75FF"
5207
+ }
5208
+ };
5209
+ function resolveVectorSymbol(nameOrAlias) {
5210
+ if (!nameOrAlias || typeof nameOrAlias !== "string") return null;
5211
+ const key = nameOrAlias.trim().toLowerCase().replace(/[\s_.-]+/g, "");
5212
+ if (VECTOR_SYMBOLS[key]) return VECTOR_SYMBOLS[key];
5213
+ const aliases = {
5214
+ postgres: "postgresql",
5215
+ pg: "postgresql",
5216
+ k8s: "kubernetes",
5217
+ kube: "kubernetes",
5218
+ cf: "cloudflare",
5219
+ node: "nodejs",
5220
+ py: "python",
5221
+ go: "golang",
5222
+ elastic: "elasticsearch",
5223
+ es: "elasticsearch",
5224
+ rabbit: "rabbitmq",
5225
+ mq: "rabbitmq",
5226
+ browser: "chrome",
5227
+ web: "chrome",
5228
+ cli: "terminal",
5229
+ console: "terminal",
5230
+ shell: "terminal",
5231
+ awss3: "s3",
5232
+ deltalake: "delta",
5233
+ lakehouse: "delta",
5234
+ llm: "gemini",
5235
+ ai: "gemini"
5236
+ };
5237
+ const target = aliases[key];
5238
+ return target ? VECTOR_SYMBOLS[target] || null : null;
5239
+ }
5240
+ function renderSymbolSvg(symbol, options) {
5241
+ const resolved = typeof symbol === "string" ? resolveVectorSymbol(symbol) : symbol;
5242
+ if (!resolved) return null;
5243
+ const size = options?.size ?? 18;
5244
+ const className = options?.className ? ` class="${options.className}"` : "";
5245
+ const style = options?.color ? ` style="color: ${options.color}"` : "";
5246
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${resolved.viewBox}" width="${size}" height="${size}"${className}${style} aria-hidden="true">${resolved.svgPaths}</svg>`;
5247
+ }
5248
+ function listAvailableSymbols() {
5249
+ return Object.keys(VECTOR_SYMBOLS);
5250
+ }
5251
+
5252
+ // src/provenance.ts
5253
+ var CONTROL_CHAR_RE = /[\u0000-\u001f\u007f]/;
5254
+ function parseCodeAnchor(raw, repositoryUrl, revision) {
5255
+ if (typeof raw !== "string" || !raw.trim()) return null;
5256
+ const trimmed = raw.trim();
5257
+ const ghMatch = trimmed.match(/^(?:https:\/\/github\.com\/[^/]+\/[^/]+\/blob\/([^/]+)\/)([^#]+)(?:#L(\d+)(?:-L(\d+))?)?$/i);
5258
+ if (ghMatch) {
5259
+ const rev = ghMatch[1];
5260
+ const filePath2 = decodeURIComponent(ghMatch[2]);
5261
+ const startLine2 = ghMatch[3] ? parseInt(ghMatch[3], 10) : void 0;
5262
+ const endLine2 = ghMatch[4] ? parseInt(ghMatch[4], 10) : startLine2;
5263
+ return {
5264
+ raw: trimmed,
5265
+ filePath: filePath2,
5266
+ startLine: startLine2,
5267
+ endLine: endLine2,
5268
+ revision: rev,
5269
+ resolvedHref: trimmed
5270
+ };
5271
+ }
5272
+ const [filePathRaw, fragment] = trimmed.split("#");
5273
+ let filePath = filePathRaw.trim().replace(/\\/g, "/");
5274
+ if (filePath.startsWith("./")) {
5275
+ filePath = filePath.slice(2);
5276
+ }
5277
+ if (!filePath || filePath.startsWith("/") || filePath.startsWith("../") || filePath === ".." || filePath.includes("/../") || filePath.endsWith("/..") || CONTROL_CHAR_RE.test(filePath)) {
5278
+ return null;
5279
+ }
5280
+ let startLine;
5281
+ let endLine;
5282
+ if (fragment) {
5283
+ const lineMatch = fragment.match(/^L?(\d+)(?:-L?(\d+))?$/i);
5284
+ if (lineMatch) {
5285
+ startLine = parseInt(lineMatch[1], 10);
5286
+ endLine = lineMatch[2] ? parseInt(lineMatch[2], 10) : startLine;
5287
+ }
5288
+ }
5289
+ let resolvedHref;
5290
+ if (repositoryUrl) {
5291
+ const base = repositoryUrl.replace(/\/$/, "");
5292
+ const rev = revision || "main";
5293
+ const lineFrag = startLine ? `#L${startLine}${endLine && endLine !== startLine ? `-L${endLine}` : ""}` : "";
5294
+ resolvedHref = `${base}/blob/${rev}/${filePath}${lineFrag}`;
5295
+ }
5296
+ return {
5297
+ raw: trimmed,
5298
+ filePath,
5299
+ startLine,
5300
+ endLine,
5301
+ revision,
5302
+ resolvedHref
5303
+ };
5304
+ }
5305
+ function extractDiagramCodeAnchors(ast, repositoryUrl, revision) {
5306
+ const anchors = /* @__PURE__ */ new Map();
5307
+ for (const [nodeId, node] of Object.entries(ast.nodes || {})) {
5308
+ const rawAnchor = node.props["@src"] || node.props["src"] || node.props["@source"] || node.props["source"] || node.props["@code"] || node.props["code"] || node.props["@anchor"] || node.props["anchor"];
5309
+ if (rawAnchor) {
5310
+ const parsed = parseCodeAnchor(rawAnchor, repositoryUrl, revision);
5311
+ if (parsed) {
5312
+ anchors.set(nodeId, parsed);
5313
+ }
5314
+ }
5315
+ }
5316
+ return anchors;
5317
+ }
5318
+ function verifyCodeAnchorsWithReader(anchors, fileReader) {
5319
+ const diagnostics = [];
5320
+ let verifiedCount = 0;
5321
+ for (const [nodeId, anchor] of anchors.entries()) {
5322
+ const { filePath, startLine, endLine } = anchor;
5323
+ const segments = filePath.split("/");
5324
+ if (segments.some((s) => !s || s === "." || s === "..") || segments[0] === ".git") {
5325
+ diagnostics.push({
5326
+ nodeId,
5327
+ severity: "error",
5328
+ code: "provenance/path-escape",
5329
+ message: `Code anchor for node "${nodeId}" must stay within repository and cannot address .git (${filePath}).`,
5330
+ filePath,
5331
+ fixSuggestion: "Remove relative path traversal dots or .git segments."
5332
+ });
5333
+ continue;
5334
+ }
5335
+ if (!fileReader.fileExists(filePath)) {
5336
+ diagnostics.push({
5337
+ nodeId,
5338
+ severity: "error",
5339
+ code: "provenance/file-not-found",
5340
+ message: `Referenced file "${filePath}" does not exist in target repository.`,
5341
+ filePath,
5342
+ fixSuggestion: `Ensure "${filePath}" is committed and relative to repository root.`
5343
+ });
5344
+ continue;
5345
+ }
5346
+ const lineCount = fileReader.getLineCount(filePath);
5347
+ if (startLine !== void 0 && (startLine < 1 || startLine > lineCount)) {
5348
+ diagnostics.push({
5349
+ nodeId,
5350
+ severity: "warning",
5351
+ code: "provenance/line-out-of-bounds",
5352
+ message: `Line #${startLine} exceeds total line count (${lineCount}) of file "${filePath}".`,
5353
+ filePath,
5354
+ line: startLine,
5355
+ fixSuggestion: `Adjust line range to fall within 1..${lineCount}.`
5356
+ });
5357
+ continue;
5358
+ }
5359
+ if (endLine !== void 0 && (endLine < (startLine || 1) || endLine > lineCount)) {
5360
+ diagnostics.push({
5361
+ nodeId,
5362
+ severity: "warning",
5363
+ code: "provenance/line-out-of-bounds",
5364
+ message: `End line #${endLine} is out of bounds for "${filePath}" (total lines: ${lineCount}).`,
5365
+ filePath,
5366
+ line: endLine,
5367
+ fixSuggestion: `Ensure end line is >= start line and <= ${lineCount}.`
5368
+ });
5369
+ continue;
5370
+ }
5371
+ verifiedCount++;
5372
+ }
5373
+ const isValid = diagnostics.filter((d) => d.severity === "error").length === 0;
5374
+ const summaryMarkdown = [
5375
+ `### \u{1F6E1}\uFE0F Code Provenance Verification Report`,
5376
+ `- **Status**: ${isValid ? "\u2705 VERIFIED" : "\u274C FAILED"}`,
5377
+ `- **Total Anchors**: ${anchors.size}`,
5378
+ `- **Verified In-Tree**: ${verifiedCount}`,
5379
+ `- **Diagnostics**: ${diagnostics.length} issue(s)`,
5380
+ ...diagnostics.length > 0 ? [
5381
+ "",
5382
+ `| Node | Severity | Issue | File / Location | Fix |`,
5383
+ `| :--- | :--- | :--- | :--- | :--- |`,
5384
+ ...diagnostics.map(
5385
+ (d) => `| \`${d.nodeId}\` | **${d.severity.toUpperCase()}** | ${d.message} | \`${d.filePath}${d.line ? `:${d.line}` : ""}\` | ${d.fixSuggestion || "N/A"} |`
5386
+ )
5387
+ ] : []
5388
+ ].join("\n");
5389
+ return {
5390
+ isValid,
5391
+ totalAnchors: anchors.size,
5392
+ verifiedCount,
5393
+ anchors,
5394
+ diagnostics,
5395
+ summaryMarkdown
5396
+ };
5397
+ }
4581
5398
 
4582
5399
  // src/syntax-diagnostics.ts
4583
5400
  function damerauLevenshteinDistance(a, b) {
@@ -6291,7 +7108,1593 @@ function formatValue(value) {
6291
7108
  if (typeof value === "string") return value;
6292
7109
  return JSON.stringify(value);
6293
7110
  }
7111
+
7112
+ // src/recipes.ts
7113
+ var ARCHITECTURE_RECIPES = [
7114
+ {
7115
+ id: "cache-aside",
7116
+ name: "Multi-Tier Cache-Aside Architecture",
7117
+ category: "caching",
7118
+ description: "High-performance cache-aside pattern with Redis Cluster, relational database persistence, and asynchronous cache warming.",
7119
+ keywords: ["cache", "redis", "postgres", "cache-aside", "hit", "miss", "warm", "database", "latency"],
7120
+ recommendedLayout: "LR",
7121
+ primaryNodes: ["Client", "Gateway", "URLService", "RedisCluster", "PostgreSQL"],
7122
+ highlights: [
7123
+ "Sub-millisecond read latency on cache hit",
7124
+ "Asynchronous cache population on miss",
7125
+ "Graceful fallback on cache eviction"
7126
+ ],
7127
+ code: `scene "Multi-Tier Cache-Aside Architecture" theme=auto
7128
+ layout LR
7129
+
7130
+ browser Client "Web Client" icon=chrome
7131
+ gateway Gateway "API Gateway" icon=nginx @src="src/gateway/proxy.ts#L12"
7132
+ service URLService "URL Service" icon=nodejs @src="src/services/resolver.ts#L25"
7133
+ cache RedisCluster "Redis Cluster" icon=redis
7134
+ database PostgreSQL "PostgreSQL 16" icon=postgresql @src="src/db/schema.sql#L1"
7135
+
7136
+ beat cache_hit "1. Cache Hit Path":
7137
+ show Client Gateway URLService RedisCluster stagger=50ms
7138
+ frame Client Gateway URLService RedisCluster zoom=1.12
7139
+ Client -> Gateway "GET /link" -> URLService "resolve"
7140
+ URLService -> RedisCluster "GET key:url"
7141
+ URLService <- RedisCluster "200 Target URL"
7142
+ Client <- Gateway "301 Redirect"
7143
+
7144
+ beat cache_miss "2. Cache Miss & Async Warm":
7145
+ show PostgreSQL stagger=50ms
7146
+ frame URLService RedisCluster PostgreSQL zoom=1.15
7147
+ URLService -> PostgreSQL "SELECT dest WHERE key = 'url'"
7148
+ URLService <- PostgreSQL "Row Found"
7149
+ URLService ~> RedisCluster "SETEX key:url (TTL 1h)"
7150
+ glow PostgreSQL color=#38bdf8 & glow RedisCluster color=#22c55e
7151
+ `
7152
+ },
7153
+ {
7154
+ id: "event-driven-eda",
7155
+ name: "Event-Driven EDA with Kafka & Change Data Capture",
7156
+ category: "streaming",
7157
+ description: "Decoupled real-time event streaming pipeline using Kafka/Redpanda with Debezium CDC, Schema Registry, and DLQ handling.",
7158
+ keywords: ["kafka", "event", "streaming", "eda", "cdc", "debezium", "pubsub", "dlq", "consumer"],
7159
+ recommendedLayout: "LR",
7160
+ primaryNodes: ["OrderService", "DebeziumCDC", "KafkaCluster", "NotificationSvc", "AnalyticsConsumer", "DeadLetterQueue"],
7161
+ highlights: [
7162
+ "Zero dual-write penalty with transactional outbox",
7163
+ "Ordered partition publishing with schema validation",
7164
+ "Dead Letter Queue for poison-pill isolation"
7165
+ ],
7166
+ code: `scene "Event-Driven EDA with Kafka & CDC" theme=midnight
7167
+ layout LR
7168
+
7169
+ service OrderSvc "Order Service" icon=golang @src="src/orders/handler.go#L40"
7170
+ database OrderDB "Order Store" icon=postgresql
7171
+ service DebeziumCDC "Debezium CDC" icon=docker
7172
+ queue KafkaCluster "Kafka Event Stream" icon=kafka
7173
+ service NotificationSvc "Notification Engine" icon=nodejs
7174
+ service AnalyticsConsumer "Real-Time Analytics" icon=python
7175
+ queue DLQ "Dead Letter Queue" icon=rabbitmq
7176
+
7177
+ beat order_outbox "1. Transactional Outbox Commit":
7178
+ show OrderSvc OrderDB DebeziumCDC KafkaCluster stagger=50ms
7179
+ OrderSvc -> OrderDB "COMMIT (Order + Outbox Event)"
7180
+ OrderDB -> DebeziumCDC "WAL Stream"
7181
+ DebeziumCDC ~> KafkaCluster "Publish order.created"
7182
+
7183
+ beat consumer_fanout "2. Real-Time Consumer Fanout":
7184
+ show NotificationSvc AnalyticsConsumer DLQ stagger=50ms
7185
+ KafkaCluster ~> NotificationSvc "Consume order.created"
7186
+ KafkaCluster ~> AnalyticsConsumer "Consume order.created"
7187
+ NotificationSvc -> DLQ "Routing Reject (Retries Exceeded)"
7188
+ glow KafkaCluster color=#fbbf24 & glow DLQ color=#fb7185
7189
+ `
7190
+ },
7191
+ {
7192
+ id: "cqrs-event-sourcing",
7193
+ name: "CQRS & Event Sourcing Architecture",
7194
+ category: "streaming",
7195
+ description: "Strict Command Query Responsibility Segregation with immutable append-only Event Store and read-optimized query projections.",
7196
+ keywords: ["cqrs", "event sourcing", "event store", "projection", "read model", "command", "query"],
7197
+ recommendedLayout: "LR",
7198
+ primaryNodes: ["CommandAPI", "CommandHandler", "EventStore", "ProjectionEngine", "ReadDB", "QueryAPI"],
7199
+ highlights: [
7200
+ "Complete auditability via append-only event stream",
7201
+ "Independent write-scaling and read-scaling",
7202
+ "Zero lock contention between queries and mutations"
7203
+ ],
7204
+ code: `scene "CQRS & Event Sourcing Architecture" theme=blueprint
7205
+ layout LR
7206
+
7207
+ gateway CommandAPI "Command API" icon=nginx
7208
+ service CommandHandler "Command Handler" icon=golang @src="src/commands/execute.go#L18"
7209
+ database EventStore "Event Store (Append-Only)" icon=cassandra
7210
+ service ProjectionEngine "Projection Engine" icon=rust @src="src/projections/sync.rs#L30"
7211
+ database ReadDB "Read Database" icon=mongodb
7212
+ gateway QueryAPI "Query API" icon=nodejs
7213
+
7214
+ beat write_command "1. Command & Append Event":
7215
+ show CommandAPI CommandHandler EventStore stagger=50ms
7216
+ CommandAPI -> CommandHandler "POST /orders/create"
7217
+ CommandHandler -> EventStore "APPEND OrderCreatedEvent"
7218
+ CommandHandler <- EventStore "Event Ack (Offset: 10482)"
7219
+ CommandAPI <- CommandHandler "202 Accepted"
7220
+
7221
+ beat async_projection "2. Asynchronous Projection Update":
7222
+ show ProjectionEngine ReadDB QueryAPI stagger=50ms
7223
+ EventStore ~> ProjectionEngine "Tail Commit Log"
7224
+ ProjectionEngine -> ReadDB "UPSERT Materialized Order View"
7225
+ QueryAPI -> ReadDB "SELECT * FROM orders WHERE id = :id"
7226
+ QueryAPI <- ReadDB "Read Model Payload"
7227
+ `
7228
+ },
7229
+ {
7230
+ id: "api-gateway-mesh",
7231
+ name: "Cloud-Native API Gateway & Service Mesh",
7232
+ category: "microservices",
7233
+ description: "Enterprise zero-trust microservices architecture with Envoy/Istio service mesh, mTLS enforcement, and distributed tracing.",
7234
+ keywords: ["gateway", "mesh", "envoy", "istio", "microservices", "mtls", "kubernetes", "discovery"],
7235
+ recommendedLayout: "LR",
7236
+ primaryNodes: ["IngressGateway", "AuthService", "OrderMeshSvc", "PaymentMeshSvc", "InventoryMeshSvc", "JaegerCollector"],
7237
+ highlights: [
7238
+ "Strict mTLS identity verification between sidecars",
7239
+ "Global rate limiting and distributed OpenTelemetry spans",
7240
+ "Dynamic circuit breaking and automated retries"
7241
+ ],
7242
+ code: `scene "Cloud-Native API Gateway & Service Mesh" theme=graphite
7243
+ layout LR
7244
+
7245
+ gateway IngressGateway "Envoy Ingress Gateway" icon=envoy @src="k8s/gateway.yaml#L1"
7246
+ service AuthService "Auth & Token Service" icon=nodejs @src="src/auth/token.ts#L44"
7247
+ service OrderMeshSvc "Order Service (mTLS)" icon=golang
7248
+ service PaymentMeshSvc "Payment Gateway (mTLS)" icon=nodejs
7249
+ service InventoryMeshSvc "Inventory Service (mTLS)" icon=python
7250
+ service JaegerCollector "OpenTelemetry Collector" icon=jaeger
7251
+
7252
+ beat ingress_auth "1. Edge Authentication & Route Verification":
7253
+ show IngressGateway AuthService JaegerCollector stagger=50ms
7254
+ IngressGateway -> AuthService "Validate JWT Bearer"
7255
+ IngressGateway <- AuthService "Claims Verified"
7256
+ IngressGateway ~> JaegerCollector "Span: ingress_entry"
7257
+
7258
+ beat internal_mesh_flow "2. Internal mTLS Mesh Fanout":
7259
+ show OrderMeshSvc PaymentMeshSvc InventoryMeshSvc stagger=50ms
7260
+ IngressGateway -> OrderMeshSvc "POST /checkout (mTLS)"
7261
+ OrderMeshSvc -> PaymentMeshSvc "POST /charge (mTLS)"
7262
+ OrderMeshSvc -> InventoryMeshSvc "POST /reserve (mTLS)"
7263
+ PaymentMeshSvc ~> JaegerCollector "Span: payment_settled"
7264
+ InventoryMeshSvc ~> JaegerCollector "Span: inventory_reserved"
7265
+ glow IngressGateway color=#38bdf8 & glow OrderMeshSvc color=#22c55e
7266
+ `
7267
+ },
7268
+ {
7269
+ id: "zero-trust-security",
7270
+ name: "Zero-Trust Security & Enclave Perimeter",
7271
+ category: "security",
7272
+ description: "Defense-in-depth zero-trust security perimeter featuring OIDC authentication, Open Policy Agent authorization, and Nitro Enclaves.",
7273
+ keywords: ["security", "zero-trust", "oidc", "opa", "policy", "enclave", "encryption", "vault", "waf"],
7274
+ recommendedLayout: "LR",
7275
+ primaryNodes: ["CloudflareWAF", "IdentityOIDC", "PolicyEngineOPA", "KeyVault", "SecureEnclave", "AuditLogStore"],
7276
+ highlights: [
7277
+ "Continuous runtime identity verification on every invocation",
7278
+ "Confidential computing in isolated CPU Nitro enclaves",
7279
+ "Immutable write-once cryptographic audit trail"
7280
+ ],
7281
+ code: `scene "Zero-Trust Security & Enclave Perimeter" theme=midnight
7282
+ layout LR
7283
+
7284
+ gateway CloudflareWAF "Cloudflare Edge WAF" icon=cloudflare
7285
+ service IdentityOIDC "Identity Provider (OIDC)" icon=keycloak
7286
+ service PolicyEngineOPA "Policy Engine (OPA)" icon=opa @src="policies/authz.rego#L1"
7287
+ service KeyVault "HashiCorp Vault" icon=vault @src="config/vault.hcl#L10"
7288
+ service SecureEnclave "AWS Nitro Enclave" icon=aws
7289
+ database AuditLogStore "WORM Audit Store" icon=s3
7290
+
7291
+ beat access_request "1. Identity & Policy Evaluation":
7292
+ show CloudflareWAF IdentityOIDC PolicyEngineOPA stagger=50ms
7293
+ CloudflareWAF -> IdentityOIDC "Authenticate Request"
7294
+ CloudflareWAF <- IdentityOIDC "Token Issued"
7295
+ CloudflareWAF -> PolicyEngineOPA "Evaluate RBAC/ABAC Context"
7296
+ CloudflareWAF <- PolicyEngineOPA "Decision: ALLOW"
7297
+
7298
+ beat confidential_execution "2. Enclave Decryption & Audit":
7299
+ show SecureEnclave KeyVault AuditLogStore stagger=50ms
7300
+ CloudflareWAF -> SecureEnclave "Execute Protected Payload"
7301
+ SecureEnclave -> KeyVault "Request Ephemeral Decryption Key"
7302
+ SecureEnclave <- KeyVault "Key Granted"
7303
+ SecureEnclave ~> AuditLogStore "Cryptographic Audit Receipt"
7304
+ glow SecureEnclave color=#fb7185 & glow KeyVault color=#a78bfa
7305
+ `
7306
+ },
7307
+ {
7308
+ id: "medallion-lakehouse",
7309
+ name: "Medallion Data Lakehouse Architecture",
7310
+ category: "data",
7311
+ description: "Modern data engineering pipeline organizing raw, cleansed, and curated data across Bronze, Silver, and Gold tiers.",
7312
+ keywords: ["data", "lakehouse", "medallion", "bronze", "silver", "gold", "spark", "delta", "iceberg", "analytics"],
7313
+ recommendedLayout: "LR",
7314
+ primaryNodes: ["RawIngestKafka", "BronzeLake", "SparkCleansing", "SilverLake", "FlinkAggregation", "GoldWarehouse", "SupersetBI"],
7315
+ highlights: [
7316
+ "ACID transactions over object storage with Delta/Iceberg",
7317
+ "Multi-stage data quality checks between Bronze and Silver",
7318
+ "Sub-second dimensional queries on Gold warehouse"
7319
+ ],
7320
+ code: `scene "Medallion Data Lakehouse Architecture" theme=editorial
7321
+ layout LR
7322
+
7323
+ queue RawIngestKafka "Raw Event Ingestion" icon=kafka
7324
+ database BronzeLake "Bronze Lake (Raw Ingest)" icon=s3
7325
+ service SparkCleansing "Spark Cleansing Job" icon=spark @src="jobs/cleanse_bronze.py#L15"
7326
+ database SilverLake "Silver Lake (Enriched)" icon=delta
7327
+ service FlinkAggregation "Flink Streaming Aggregator" icon=flink
7328
+ database GoldWarehouse "Gold Warehouse (Curated)" icon=snowflake
7329
+ dashboard SupersetBI "Apache Superset BI" icon=superset
7330
+
7331
+ beat bronze_ingest "1. Raw Stream to Bronze Tier":
7332
+ show RawIngestKafka BronzeLake SparkCleansing stagger=50ms
7333
+ RawIngestKafka -> BronzeLake "Append Raw JSON Payload"
7334
+ BronzeLake -> SparkCleansing "Trigger Micro-Batch"
7335
+
7336
+ beat silver_and_gold "2. Cleansing, Enrichment & BI Serving":
7337
+ show SilverLake FlinkAggregation GoldWarehouse SupersetBI stagger=50ms
7338
+ SparkCleansing -> SilverLake "Upsert Deduplicated Parquet"
7339
+ SilverLake -> FlinkAggregation "Stream Entity Updates"
7340
+ FlinkAggregation -> GoldWarehouse "Merge Into Star Schema"
7341
+ SupersetBI -> GoldWarehouse "Execute Dimensional Query"
7342
+ SupersetBI <- GoldWarehouse "Render Dashboard Metrics"
7343
+ `
7344
+ },
7345
+ {
7346
+ id: "agentic-react-tools",
7347
+ name: "Agentic AI Orchestrator & Tool Execution Loop",
7348
+ category: "ai",
7349
+ description: "Autonomous ReAct agent system with LLM Orchestrator, dynamic context memory, vector embeddings, and MCP tool execution.",
7350
+ keywords: ["ai", "agent", "llm", "react", "tool", "mcp", "vector", "rag", "orchestrator", "prompt"],
7351
+ recommendedLayout: "LR",
7352
+ primaryNodes: ["UserClient", "AgentOrchestrator", "VectorMemory", "ModelInference", "MCPToolExecutor", "SandboxRuntime"],
7353
+ highlights: [
7354
+ "Interactive reasoning loop (Thought -> Action -> Observation)",
7355
+ "Hybrid semantic search over vector memory store",
7356
+ "Secure sandboxed runtime for tool call executions"
7357
+ ],
7358
+ code: `scene "Agentic AI Orchestrator & Tool Loop" theme=nebula
7359
+ layout LR
7360
+
7361
+ browser UserClient "User Workspace" icon=terminal
7362
+ service AgentOrchestrator "ReAct Agent Orchestrator" icon=python @src="agent/core.py#L35"
7363
+ database VectorMemory "Vector Memory (RAG)" icon=redis
7364
+ service ModelInference "LLM Inference API" icon=gemini
7365
+ service MCPToolExecutor "MCP Tool Protocol" icon=docker @src="agent/mcp_client.py#L20"
7366
+ service SandboxRuntime "Secure Container Sandbox" icon=docker
7367
+
7368
+ beat agent_thought "1. Plan & Context Retrieval":
7369
+ show UserClient AgentOrchestrator VectorMemory ModelInference stagger=50ms
7370
+ UserClient -> AgentOrchestrator "Goal: Deploy microservice"
7371
+ AgentOrchestrator -> VectorMemory "Query Relevant Runbooks"
7372
+ AgentOrchestrator <- VectorMemory "Runbook Context Vectors"
7373
+ AgentOrchestrator -> ModelInference "Generate Plan & Tool Call"
7374
+ AgentOrchestrator <- ModelInference "Call: run_command(kubectl apply)"
7375
+
7376
+ beat tool_execution "2. MCP Tool Execution & Observation":
7377
+ show MCPToolExecutor SandboxRuntime stagger=50ms
7378
+ AgentOrchestrator -> MCPToolExecutor "Execute Tool Request"
7379
+ MCPToolExecutor -> SandboxRuntime "Spawn Container & Execute"
7380
+ MCPToolExecutor <- SandboxRuntime "Output: deployment created"
7381
+ AgentOrchestrator <- MCPToolExecutor "Observation Receipt"
7382
+ UserClient <- AgentOrchestrator "Goal Achieved: Deployed successfully"
7383
+ glow AgentOrchestrator color=#c4b5fd & glow MCPToolExecutor color=#67e8f9
7384
+ `
7385
+ },
7386
+ {
7387
+ id: "active-active-failover",
7388
+ name: "Multi-Region Active-Active Resilient Failover",
7389
+ category: "resilience",
7390
+ description: "High-availability global architecture with GeoDNS latency routing, multi-region cluster active-active syncing, and automated failover.",
7391
+ keywords: ["resilience", "active-active", "failover", "multi-region", "disaster recovery", "replication", "dns", "ha"],
7392
+ recommendedLayout: "LR",
7393
+ primaryNodes: ["GlobalDNS", "RegionUSEast", "DBPrimaryEast", "RegionEUWest", "DBPrimaryWest", "HealthProbe"],
7394
+ highlights: [
7395
+ "Sub-second DNS failover when health probe detects outage",
7396
+ "Bi-directional conflict-free replicated database sync (CRDT)",
7397
+ "Zero downtime during planned regional maintenance"
7398
+ ],
7399
+ code: `scene "Multi-Region Active-Active Failover" theme=midnight
7400
+ layout LR
7401
+
7402
+ gateway GlobalDNS "Global Route53 GeoDNS" icon=aws
7403
+ service RegionUSEast "Region US-East API" icon=kubernetes @src="infra/us-east/app.yaml#L1"
7404
+ database DBPrimaryEast "Aurora Global DB (East)" icon=postgresql
7405
+ service RegionEUWest "Region EU-West API" icon=kubernetes @src="infra/eu-west/app.yaml#L1"
7406
+ database DBPrimaryWest "Aurora Global DB (West)" icon=postgresql
7407
+ service HealthProbe "Global Health Checker" icon=datadog
7408
+
7409
+ beat steady_state "1. Steady-State Geo-Routing & Sync":
7410
+ show GlobalDNS RegionUSEast DBPrimaryEast RegionEUWest DBPrimaryWest stagger=50ms
7411
+ GlobalDNS -> RegionUSEast "Route US Traffic"
7412
+ RegionUSEast -> DBPrimaryEast "Local Read/Write"
7413
+ GlobalDNS -> RegionEUWest "Route EU Traffic"
7414
+ RegionEUWest -> DBPrimaryWest "Local Read/Write"
7415
+ DBPrimaryEast ~> DBPrimaryWest "Cross-Region Stream Replication"
7416
+
7417
+ beat simulated_failover "2. Outage Detection & Instant Failover":
7418
+ show HealthProbe stagger=50ms
7419
+ HealthProbe -> RegionUSEast "HTTP Health Probe (Timeout)"
7420
+ HealthProbe ~> GlobalDNS "Withdraw US-East IP from Pool"
7421
+ GlobalDNS -> RegionEUWest "Reroute 100% Global Traffic"
7422
+ glow RegionEUWest color=#22c55e & glow RegionUSEast color=#fb7185
7423
+ `
7424
+ },
7425
+ {
7426
+ id: "distributed-consensus-raft",
7427
+ name: "Distributed Consensus & Raft Log Replication",
7428
+ category: "consensus",
7429
+ description: "Raft consensus protocol state machine with Leader Election, Heartbeat synchronization, and atomic log commitment.",
7430
+ keywords: ["raft", "consensus", "leader", "follower", "election", "replication", "distributed", "etcd"],
7431
+ recommendedLayout: "LR",
7432
+ primaryNodes: ["ClientApp", "RaftLeader", "RaftFollowerA", "RaftFollowerB", "StateStore"],
7433
+ highlights: [
7434
+ "Guaranteed linearizable reads and writes",
7435
+ "Automated leader reelection on heartbeat loss",
7436
+ "Strict quorum (N/2 + 1) commit guarantees"
7437
+ ],
7438
+ code: `scene "Distributed Consensus Raft Engine" theme=paper
7439
+ layout LR
7440
+
7441
+ browser ClientApp "Client Application" icon=terminal
7442
+ service RaftLeader "Raft Node 1 (Leader)" icon=golang @src="raft/leader.go#L42"
7443
+ service RaftFollowerA "Raft Node 2 (Follower)" icon=golang @src="raft/follower.go#L20"
7444
+ service RaftFollowerB "Raft Node 3 (Follower)" icon=golang @src="raft/follower.go#L20"
7445
+ database StateStore "Committed State Machine" icon=sqlite
7446
+
7447
+ beat propose_entry "1. Client Proposal & Log Replication":
7448
+ show ClientApp RaftLeader RaftFollowerA RaftFollowerB stagger=50ms
7449
+ ClientApp -> RaftLeader "Propose: SET key = 'val'"
7450
+ RaftLeader -> RaftFollowerA "AppendEntries(Term=2, Entry=4)"
7451
+ RaftLeader -> RaftFollowerB "AppendEntries(Term=2, Entry=4)"
7452
+
7453
+ beat quorum_commit "2. Quorum Acknowledgment & State Commit":
7454
+ show StateStore stagger=50ms
7455
+ RaftLeader <- RaftFollowerA "Success Ack"
7456
+ RaftLeader <- RaftFollowerB "Success Ack"
7457
+ RaftLeader -> StateStore "Apply to State Machine"
7458
+ ClientApp <- RaftLeader "200 Commit Acknowledged"
7459
+ glow RaftLeader color=#0284c7 & glow StateStore color=#16a34a
7460
+ `
7461
+ },
7462
+ {
7463
+ id: "incident-runbook",
7464
+ name: "Automated Incident Response & Self-Healing Runbook",
7465
+ category: "observability",
7466
+ description: "Automated site reliability incident workflow: metric threshold breach, PagerDuty alert, automated pod restart, and status page sync.",
7467
+ keywords: ["incident", "sre", "runbook", "pagerduty", "alert", "prometheus", "slack", "self-healing"],
7468
+ recommendedLayout: "LR",
7469
+ primaryNodes: ["PrometheusAlert", "PagerDutyEngine", "K8sAutoHealer", "SlackIncidentBot", "StatusPageSync"],
7470
+ highlights: [
7471
+ "Instant multi-channel incident triaging",
7472
+ "Automated remediation before human on-call escalation",
7473
+ "Zero-latency public status communication"
7474
+ ],
7475
+ code: `scene "Automated Incident Response Runbook" theme=midnight
7476
+ layout LR
7477
+
7478
+ service PrometheusAlert "Prometheus Alertmanager" icon=prometheus @src="alerts/p99_latency.yaml#L1"
7479
+ service PagerDutyEngine "PagerDuty Event Router" icon=pagerduty
7480
+ service K8sAutoHealer "K8s Auto-Remediation" icon=kubernetes @src="runbooks/restart_pod.sh#L5"
7481
+ service SlackIncidentBot "Slack War Room Bot" icon=slack
7482
+ service StatusPageSync "Public Status Page" icon=cloudflare
7483
+
7484
+ beat alert_trigger "1. High Latency P99 Breach":
7485
+ show PrometheusAlert PagerDutyEngine SlackIncidentBot StatusPageSync stagger=50ms
7486
+ PrometheusAlert ~> PagerDutyEngine "TRIGGER: P99 Latency > 1500ms"
7487
+ PagerDutyEngine -> SlackIncidentBot "Spawn #incident-2026-09"
7488
+ PagerDutyEngine -> StatusPageSync "Update: Degraded Performance"
7489
+
7490
+ beat auto_heal "2. Self-Healing Pod Recycle & Resolution":
7491
+ show K8sAutoHealer stagger=50ms
7492
+ PagerDutyEngine -> K8sAutoHealer "Execute Remediation Runbook"
7493
+ K8sAutoHealer -> PrometheusAlert "Verify Latency Normalized (< 200ms)"
7494
+ PagerDutyEngine ~> SlackIncidentBot "Resolved: Auto-healed in 42s"
7495
+ PagerDutyEngine ~> StatusPageSync "Update: All Systems Operational"
7496
+ glow K8sAutoHealer color=#22c55e & glow StatusPageSync color=#38bdf8
7497
+ `
7498
+ },
7499
+ {
7500
+ id: "agentic-multi-swarm",
7501
+ name: "Autonomous Multi-Agent Engineering Swarm",
7502
+ category: "ai",
7503
+ description: "Multi-agent collaborative architecture with Orchestrator Leader, Specialized Coder/Reviewer Subagents, Sandboxed Tool Execution, and Consensus Verification.",
7504
+ keywords: ["agent", "swarm", "multi-agent", "orchestrator", "mcp", "subagent", "sandbox", "ai"],
7505
+ recommendedLayout: "LR",
7506
+ primaryNodes: ["UserLead", "OrchestratorAgent", "CoderSubagent", "ReviewerSubagent", "SandboxRuntime"],
7507
+ highlights: [
7508
+ "Dynamic hierarchical task delegation",
7509
+ "Dual-agent verification & adversarial review",
7510
+ "Isolated sandboxed execution with telemetry"
7511
+ ],
7512
+ code: `scene "Autonomous Multi-Agent Engineering Swarm" theme=graphite
7513
+ layout LR
7514
+
7515
+ browser UserLead "Lead Engineer / IDE" icon=gemini
7516
+ service OrchestratorAgent "Orchestrator Leader" icon=nodejs @src="src/agent/leader.ts#L10"
7517
+ service CoderSubagent "Coder Subagent" icon=typescript @src="src/agent/coder.ts#L15"
7518
+ service ReviewerSubagent "Reviewer Subagent" icon=python @src="src/agent/critic.ts#L20"
7519
+ service SandboxRuntime "Secure Tool Sandbox" icon=docker @src="src/tools/mcp_host.ts#L5"
7520
+
7521
+ beat task_delegation "1. Task Decomposition & Parallel Spawn":
7522
+ show UserLead OrchestratorAgent CoderSubagent ReviewerSubagent stagger=50ms
7523
+ UserLead -> OrchestratorAgent "Prompt: Implement Feature & Tests"
7524
+ OrchestratorAgent -> CoderSubagent "Spawn task: Write TypeScript Implementation"
7525
+ OrchestratorAgent -> ReviewerSubagent "Spawn task: Construct Invariant Quality Gate"
7526
+
7527
+ beat tool_verification "2. Sandboxed Execution & Review Consensus":
7528
+ show SandboxRuntime stagger=50ms
7529
+ CoderSubagent -> SandboxRuntime "Execute unit tests in sandbox"
7530
+ CoderSubagent <- SandboxRuntime "308 tests pass (100%)"
7531
+ ReviewerSubagent -> CoderSubagent "Verify Code Provenance & Zero Regressions"
7532
+ OrchestratorAgent <- ReviewerSubagent "Consensus Approved: Ready for PR"
7533
+ UserLead <- OrchestratorAgent "200 Feature Complete & Verified"
7534
+ glow OrchestratorAgent color=#38bdf8 & glow SandboxRuntime color=#10b981
7535
+ `
7536
+ },
7537
+ {
7538
+ id: "edge-serverless-mesh",
7539
+ name: "Edge-First Serverless & Distributed Vector Mesh",
7540
+ category: "resilience",
7541
+ description: "Ultra-low-latency globally distributed edge architecture with Cloudflare Workers, KV caching, D1 relational store, and Vectorize embedding search.",
7542
+ keywords: ["edge", "cloudflare", "workers", "serverless", "d1", "vector", "embedding", "kv"],
7543
+ recommendedLayout: "LR",
7544
+ primaryNodes: ["GlobalClient", "EdgeWorker", "EdgeKV", "D1Database", "VectorizeStore"],
7545
+ highlights: [
7546
+ "Sub-10ms global edge invocation",
7547
+ "Local relational replication with D1",
7548
+ "Native vector similarity lookup at the edge"
7549
+ ],
7550
+ code: `scene "Edge-First Serverless & Vector Mesh" theme=paper
7551
+ layout LR
7552
+
7553
+ browser GlobalClient "Global Mobile/Web Client" icon=chrome
7554
+ gateway EdgeWorker "Cloudflare Edge Worker" icon=cloudflare @src="src/worker/index.ts#L1"
7555
+ cache EdgeKV "Global KV Cache" icon=redis
7556
+ database D1Database "Cloudflare D1 SQL" icon=postgresql @src="src/db/schema.sql#L10"
7557
+ database VectorizeStore "Vectorize Embedding DB" icon=gemini
7558
+
7559
+ beat edge_lookup "1. Nearest Edge Routing & Cache Hit":
7560
+ show GlobalClient EdgeWorker EdgeKV stagger=50ms
7561
+ GlobalClient -> EdgeWorker "GET /recommendations (Geo: Tokyo)"
7562
+ EdgeWorker -> EdgeKV "GET edge_cache:user_tokyo"
7563
+ EdgeWorker <- EdgeKV "Hit (3ms latency)"
7564
+
7565
+ beat semantic_search "2. Edge Vector Search & D1 Fetch":
7566
+ show VectorizeStore D1Database stagger=50ms
7567
+ EdgeWorker -> VectorizeStore "Query vector topK=5"
7568
+ EdgeWorker <- VectorizeStore "Embedding Matches"
7569
+ EdgeWorker -> D1Database "SELECT metadata FROM products WHERE id IN (...)"
7570
+ EdgeWorker <- D1Database "Product Records"
7571
+ GlobalClient <- EdgeWorker "200 OK (8ms total transit)"
7572
+ glow EdgeWorker color=#f59e0b & glow VectorizeStore color=#ec4899
7573
+ `
7574
+ },
7575
+ {
7576
+ id: "zero-downtime-canary",
7577
+ name: "Zero-Downtime Blue-Green & Canary Deployment",
7578
+ category: "resilience",
7579
+ description: "Progressive delivery traffic routing with Envoy/Ingress, Blue (Stable) vs Green (Canary) cluster weighting, and automated rollback on error spikes.",
7580
+ keywords: ["canary", "blue-green", "deployment", "envoy", "kubernetes", "traffic", "rollback", "zero-downtime"],
7581
+ recommendedLayout: "LR",
7582
+ primaryNodes: ["IngressController", "EnvoyMesh", "BlueCluster", "GreenCanary", "PrometheusWatcher"],
7583
+ highlights: [
7584
+ "Fine-grained 90/10 traffic split",
7585
+ "Zero dropped active connections during migration",
7586
+ "Sub-second automated circuit breaker rollback"
7587
+ ],
7588
+ code: `scene "Zero-Downtime Canary Deployment" theme=terminal
7589
+ layout LR
7590
+
7591
+ browser UserTraffic "Live Production Traffic" icon=chrome
7592
+ gateway EnvoyMesh "Envoy Service Mesh" icon=envoy @src="k8s/envoy-config.yaml#L1"
7593
+ service BlueCluster "Blue Pods (v1.2.0 Stable 90%)" icon=kubernetes @src="deploy/blue.yaml#L1"
7594
+ service GreenCanary "Green Pods (v1.3.0 Canary 10%)" icon=docker @src="deploy/green.yaml#L1"
7595
+ service PrometheusWatcher "Canary Health Sentry" icon=prometheus
7596
+
7597
+ beat canary_routing "1. Weighted Traffic Split (90/10)":
7598
+ show UserTraffic EnvoyMesh BlueCluster GreenCanary stagger=50ms
7599
+ UserTraffic -> EnvoyMesh "Production Request Pool"
7600
+ EnvoyMesh -> BlueCluster "Route 90% Stable"
7601
+ EnvoyMesh -> GreenCanary "Route 10% Canary"
7602
+
7603
+ beat health_verification "2. Automated Sentry Gate & 100% Promotion":
7604
+ show PrometheusWatcher stagger=50ms
7605
+ PrometheusWatcher -> GreenCanary "Monitor Error Rate (< 0.01%) & P99"
7606
+ PrometheusWatcher -> EnvoyMesh "Signal: Canary Healthy -> Shift 100% to Green"
7607
+ EnvoyMesh -> GreenCanary "Promote to 100% Live"
7608
+ glow GreenCanary color=#22c55e & glow BlueCluster color=#64748b
7609
+ `
7610
+ },
7611
+ {
7612
+ id: "opentelemetry-tracing",
7613
+ name: "Full-Stack Distributed Tracing & Observability",
7614
+ category: "observability",
7615
+ description: "End-to-end W3C trace context propagation across Frontend, API Gateway, Microservices, and OpenTelemetry Collector with Jaeger/Grafana visualization.",
7616
+ keywords: ["opentelemetry", "tracing", "jaeger", "grafana", "prometheus", "span", "context", "observability"],
7617
+ recommendedLayout: "LR",
7618
+ primaryNodes: ["WebFrontend", "ApiGateway", "OrderService", "OtelCollector", "JaegerGrafana"],
7619
+ highlights: [
7620
+ "Unified W3C traceparent context injection",
7621
+ "Non-blocking asynchronous telemetry batching",
7622
+ "Unified metrics, logs, and trace correlation"
7623
+ ],
7624
+ code: `scene "Full-Stack Distributed Tracing" theme=editorial
7625
+ layout LR
7626
+
7627
+ browser WebFrontend "Web App (OTel Web SDK)" icon=chrome @src="src/tracing/web.ts#L5"
7628
+ gateway ApiGateway "Kong API Gateway" icon=nginx
7629
+ service OrderService "Order Microservice" icon=golang @src="src/orders/main.go#L30"
7630
+ service OtelCollector "OpenTelemetry Collector" icon=docker @src="otel/collector.yaml#L1"
7631
+ database JaegerGrafana "Jaeger & Grafana Cloud" icon=datadog
7632
+
7633
+ beat trace_propagation "1. Context Injection & Downstream Propagation":
7634
+ show WebFrontend ApiGateway OrderService OtelCollector stagger=50ms
7635
+ WebFrontend -> ApiGateway "POST /checkout [traceparent: 00-4bf92...]"
7636
+ ApiGateway -> OrderService "Forward [traceparent: 00-4bf92...]"
7637
+ WebFrontend ~> OtelCollector "Async Span: browser_render (42ms)"
7638
+
7639
+ beat collector_export "2. OTLP gRPC Batch Ingestion & Indexing":
7640
+ show JaegerGrafana stagger=50ms
7641
+ ApiGateway ~> OtelCollector "Async Span: gateway_auth (12ms)"
7642
+ OrderService ~> OtelCollector "Async Span: db_transaction (88ms)"
7643
+ OtelCollector -> JaegerGrafana "Export OTLP Batch (Traces + Metrics)"
7644
+ glow OtelCollector color=#38bdf8 & glow JaegerGrafana color=#ec4899
7645
+ `
7646
+ }
7647
+ ];
7648
+ function recommendArchitecturePattern(query) {
7649
+ const normalized = query.toLowerCase();
7650
+ const queryTokens = normalized.split(/[\s,._\-:;/?!]+/).filter(Boolean);
7651
+ const results = [];
7652
+ for (const recipe of ARCHITECTURE_RECIPES) {
7653
+ let score = 0;
7654
+ const matched = [];
7655
+ if (normalized.includes(recipe.category)) {
7656
+ score += 10;
7657
+ matched.push(`category:${recipe.category}`);
7658
+ }
7659
+ if (normalized.includes(recipe.id.replace(/-/g, " "))) {
7660
+ score += 25;
7661
+ matched.push(recipe.id);
7662
+ }
7663
+ for (const kw of recipe.keywords) {
7664
+ if (normalized.includes(kw)) {
7665
+ score += 8;
7666
+ if (!matched.includes(kw)) matched.push(kw);
7667
+ }
7668
+ }
7669
+ const descTokens = recipe.description.toLowerCase().split(/\W+/);
7670
+ for (const token of queryTokens) {
7671
+ if (token.length > 2 && descTokens.includes(token)) {
7672
+ score += 3;
7673
+ }
7674
+ }
7675
+ if (score > 0) {
7676
+ results.push({
7677
+ recipe,
7678
+ score,
7679
+ matchedKeywords: matched,
7680
+ rationale: `Matched ${matched.length} key attributes: ${matched.join(", ")} (Score: ${score})`
7681
+ });
7682
+ }
7683
+ }
7684
+ results.sort((a, b) => b.score - a.score);
7685
+ if (results.length === 0 && ARCHITECTURE_RECIPES.length > 0) {
7686
+ results.push({
7687
+ recipe: ARCHITECTURE_RECIPES[0],
7688
+ score: 1,
7689
+ matchedKeywords: ["default"],
7690
+ rationale: "Default canonical cache-aside architecture blueprint"
7691
+ });
7692
+ }
7693
+ return results;
7694
+ }
7695
+ function synthesizeCustomRecipe(query) {
7696
+ const text = query.toLowerCase();
7697
+ const detected = [];
7698
+ if (text.includes("next") || text.includes("nextjs") || text.includes("react") || text.includes("web")) {
7699
+ detected.push({ id: "NextApp", label: "Next.js Web Client", kind: "browser", icon: "chrome" });
7700
+ } else if (text.includes("mobile") || text.includes("ios") || text.includes("android") || text.includes("flutter")) {
7701
+ detected.push({ id: "MobileApp", label: "Mobile Client Application", kind: "mobile", icon: "chrome" });
7702
+ } else {
7703
+ detected.push({ id: "ClientApp", label: "Client Application", kind: "browser", icon: "chrome" });
7704
+ }
7705
+ if (text.includes("cloudflare") || text.includes("edge") || text.includes("cdn")) {
7706
+ detected.push({ id: "CloudflareEdge", label: "Cloudflare Edge Ingress", kind: "gateway", icon: "cloudflare" });
7707
+ } else if (text.includes("nginx") || text.includes("envoy") || text.includes("kong") || text.includes("gateway")) {
7708
+ detected.push({ id: "ApiGateway", label: "API Gateway & Ingress", kind: "gateway", icon: "nginx" });
7709
+ }
7710
+ if (text.includes("stripe") || text.includes("payment") || text.includes("checkout")) {
7711
+ detected.push({ id: "StripeGateway", label: "Stripe Payment Gateway", kind: "service", icon: "docker" });
7712
+ }
7713
+ if (text.includes("keycloak") || text.includes("auth0") || text.includes("jwt") || text.includes("oauth")) {
7714
+ detected.push({ id: "AuthService", label: "Identity & Access Provider", kind: "service", icon: "keycloak" });
7715
+ }
7716
+ if (text.includes("vault") || text.includes("secret") || text.includes("opa")) {
7717
+ detected.push({ id: "SecurityVault", label: "Security & Secret Store", kind: "service", icon: "vault" });
7718
+ }
7719
+ if (text.includes("fastapi") || text.includes("python") || text.includes("django")) {
7720
+ detected.push({ id: "PythonBackend", label: "FastAPI Core Service", kind: "service", icon: "python" });
7721
+ } else if (text.includes("go") || text.includes("golang") || text.includes("gin")) {
7722
+ detected.push({ id: "GoCoreSvc", label: "Go Microservice Core", kind: "service", icon: "golang" });
7723
+ } else if (text.includes("rust") || text.includes("actix") || text.includes("axum")) {
7724
+ detected.push({ id: "RustService", label: "High-Performance Rust Core", kind: "service", icon: "docker" });
7725
+ } else if (text.includes("nest") || text.includes("express") || text.includes("node") || text.includes("typescript")) {
7726
+ detected.push({ id: "BackendSvc", label: "Node.js Backend Service", kind: "service", icon: "nodejs" });
7727
+ } else {
7728
+ detected.push({ id: "AppService", label: "Application Core Service", kind: "service", icon: "nodejs" });
7729
+ }
7730
+ if (text.includes("redis") || text.includes("cache") || text.includes("memcached")) {
7731
+ detected.push({ id: "RedisCache", label: "Redis Distributed Cache", kind: "cache", icon: "redis" });
7732
+ }
7733
+ if (text.includes("kafka") || text.includes("stream") || text.includes("event") || text.includes("cdc")) {
7734
+ detected.push({ id: "KafkaStream", label: "Kafka Event Stream", kind: "queue", icon: "kafka" });
7735
+ } else if (text.includes("rabbit") || text.includes("queue") || text.includes("sqs") || text.includes("nats")) {
7736
+ detected.push({ id: "MessageQueue", label: "Message Queue Broker", kind: "queue", icon: "rabbitmq" });
7737
+ }
7738
+ if (text.includes("postgres") || text.includes("postgresql") || text.includes("sql") || text.includes("db")) {
7739
+ detected.push({ id: "PostgresDB", label: "PostgreSQL 16 Primary", kind: "database", icon: "postgresql" });
7740
+ } else if (text.includes("mongo") || text.includes("nosql") || text.includes("dynamo")) {
7741
+ detected.push({ id: "NoSqlStore", label: "NoSQL Document Store", kind: "database", icon: "docker" });
7742
+ } else {
7743
+ detected.push({ id: "PrimaryDB", label: "Primary Database Store", kind: "database", icon: "postgresql" });
7744
+ }
7745
+ const nodeMap = /* @__PURE__ */ new Map();
7746
+ for (const n of detected) {
7747
+ if (!nodeMap.has(n.id)) nodeMap.set(n.id, n);
7748
+ }
7749
+ const nodes = Array.from(nodeMap.values());
7750
+ const lines = [];
7751
+ const safeTitle = query.replace(/["\n\r\\]/g, " ").replace(/\s+/g, " ").trim().slice(0, 50);
7752
+ lines.push(`scene "Synthesized Architecture: ${safeTitle}" theme=midnight`);
7753
+ lines.push(`layout LR`);
7754
+ lines.push(``);
7755
+ for (const node of nodes) {
7756
+ const iconAttr = node.icon ? ` icon=${node.icon}` : "";
7757
+ lines.push(`${node.kind} ${node.id} "${node.label}"${iconAttr}`);
7758
+ }
7759
+ const clientNode = nodes.find((n) => n.kind === "browser" || n.kind === "mobile") || nodes[0];
7760
+ const gatewayNode = nodes.find((n) => n.kind === "gateway");
7761
+ const mainSvc = nodes.find((n) => n.kind === "service") || nodes[1];
7762
+ const dbNode = nodes.find((n) => n.kind === "database") || nodes[nodes.length - 1];
7763
+ const cacheNode = nodes.find((n) => n.kind === "cache");
7764
+ const queueNode = nodes.find((n) => n.kind === "queue");
7765
+ const ingressSet = new Set([clientNode, gatewayNode, mainSvc, cacheNode].filter(Boolean).map((n) => n.id));
7766
+ const downstreamSet = new Set(nodes.filter((n) => !ingressSet.has(n.id)).map((n) => n.id));
7767
+ lines.push(``);
7768
+ lines.push(`beat synchronous_flow "1. Client Ingress & Request Path":`);
7769
+ lines.push(` show ${Array.from(ingressSet).join(" ")} stagger=50ms`);
7770
+ if (gatewayNode) {
7771
+ lines.push(` ${clientNode.id} -> ${gatewayNode.id} "HTTPS TLS Request" -> ${mainSvc.id} "Route dispatch"`);
7772
+ } else {
7773
+ lines.push(` ${clientNode.id} -> ${mainSvc.id} "HTTPS API Request"`);
7774
+ }
7775
+ if (cacheNode) {
7776
+ lines.push(` ${mainSvc.id} -> ${cacheNode.id} "GET /cached-data"`);
7777
+ }
7778
+ if (downstreamSet.size > 0) {
7779
+ lines.push(``);
7780
+ if (queueNode) {
7781
+ lines.push(`beat downstream_flow "2. Persistence & Asynchronous Event Bus":`);
7782
+ lines.push(` show ${Array.from(downstreamSet).join(" ")} stagger=50ms`);
7783
+ lines.push(` ${mainSvc.id} -> ${dbNode.id} "SELECT / INSERT transaction"`);
7784
+ lines.push(` ${mainSvc.id} ~> ${queueNode.id} "Publish state.changed"`);
7785
+ lines.push(` glow ${queueNode.id} color=#38bdf8 & glow ${dbNode.id} color=#10b981`);
7786
+ } else {
7787
+ lines.push(`beat persistence_and_response "2. State Commit & Response":`);
7788
+ lines.push(` show ${Array.from(downstreamSet).join(" ")} stagger=50ms`);
7789
+ lines.push(` ${mainSvc.id} -> ${dbNode.id} "SELECT / INSERT transaction"`);
7790
+ lines.push(` ${clientNode.id} <- ${mainSvc.id} "200 OK JSON Response"`);
7791
+ lines.push(` glow ${mainSvc.id} color=#38bdf8 & glow ${dbNode.id} color=#10b981`);
7792
+ }
7793
+ } else {
7794
+ lines.push(``);
7795
+ lines.push(`beat ack_response "2. Acknowledged Response":`);
7796
+ lines.push(` ${clientNode.id} <- ${mainSvc.id} "200 OK Response"`);
7797
+ lines.push(` glow ${mainSvc.id} color=#38bdf8`);
7798
+ }
7799
+ return {
7800
+ markdyScript: lines.join("\n") + "\n",
7801
+ detectedComponents: nodes,
7802
+ inferredPattern: queueNode ? "Event-Driven Microservices" : "Layered Service Mesh",
7803
+ rationale: `Synthesized ${nodes.length} architectural components (${nodes.map((n) => n.id).join(", ")}) based on query criteria.`
7804
+ };
7805
+ }
7806
+ function getArchitectureRecipe(id) {
7807
+ const cleanId = id.trim().toLowerCase();
7808
+ return ARCHITECTURE_RECIPES.find(
7809
+ (r) => r.id === cleanId || r.name.toLowerCase().includes(cleanId)
7810
+ );
7811
+ }
7812
+ function listArchitectureRecipes() {
7813
+ return [...ARCHITECTURE_RECIPES];
7814
+ }
7815
+
7816
+ // src/verifier.ts
7817
+ function computeDeterministicReceipt(content) {
7818
+ let h1 = 3735928559;
7819
+ let h2 = 1103547991;
7820
+ for (let i = 0; i < content.length; i++) {
7821
+ const ch = content.charCodeAt(i);
7822
+ h1 = Math.imul(h1 ^ ch, 2654435761);
7823
+ h2 = Math.imul(h2 ^ ch, 1597334677);
7824
+ }
7825
+ h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
7826
+ h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
7827
+ h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
7828
+ h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
7829
+ const hex1 = (h1 >>> 0).toString(16).padStart(8, "0");
7830
+ const hex2 = (h2 >>> 0).toString(16).padStart(8, "0");
7831
+ return `sha256-${hex1}${hex2}${hex1}${hex2}`;
7832
+ }
7833
+ function collectAllFlows(ast) {
7834
+ const flows = [];
7835
+ for (const edge of ast.edges || []) {
7836
+ const rawOp = edge.op || (edge.kind === "request" ? "->" : edge.kind === "event" ? "~>" : edge.kind === "response" ? "<-" : "->");
7837
+ const isSync = rawOp === "->" || rawOp === "<->" || edge.kind === "request";
7838
+ flows.push({ from: edge.from, to: edge.to, op: rawOp, isSync });
7839
+ }
7840
+ function extractCues(cues) {
7841
+ for (const cue of cues || []) {
7842
+ if (cue.kind === "flow" && Array.isArray(cue.segments)) {
7843
+ for (const seg of cue.segments) {
7844
+ const op = seg.op || "->";
7845
+ const isSync = op === "->" || op === "<->" || op === "request";
7846
+ flows.push({ from: seg.from, to: seg.to, op, isSync });
7847
+ }
7848
+ } else if (cue.kind === "parallel" && Array.isArray(cue.cues)) {
7849
+ extractCues(cue.cues);
7850
+ }
7851
+ }
7852
+ }
7853
+ for (const beat of ast.beats || []) {
7854
+ extractCues(beat.cues);
7855
+ }
7856
+ return flows;
7857
+ }
7858
+ function verifyDiagramQuality(astOrCode, options = {}) {
7859
+ let ast;
7860
+ if (typeof astOrCode === "string") {
7861
+ try {
7862
+ ast = parse(astOrCode);
7863
+ } catch (err) {
7864
+ return {
7865
+ passed: false,
7866
+ qualityProfile: options.profile || "standard",
7867
+ errorCount: 1,
7868
+ warningCount: 0,
7869
+ sha256Receipt: "",
7870
+ checks: [
7871
+ {
7872
+ id: "syntax_validity",
7873
+ name: "Syntax & Structural Validity",
7874
+ category: "syntax",
7875
+ status: "fail",
7876
+ message: `Syntax parse error: ${err.message}`
7877
+ }
7878
+ ],
7879
+ metrics: {
7880
+ nodeCount: 0,
7881
+ edgeCount: 0,
7882
+ beatCount: 0,
7883
+ hasCodeProvenance: false,
7884
+ provenanceAnchorCount: 0,
7885
+ symbolCount: 0,
7886
+ estimatedWidth: 0,
7887
+ estimatedHeight: 0,
7888
+ aspectRatio: 1
7889
+ },
7890
+ viewportCompliance: {
7891
+ "1440x900": false,
7892
+ "1600x1000": false,
7893
+ "1920x1080": false,
7894
+ "2048x1320": false
7895
+ }
7896
+ };
7897
+ }
7898
+ } else {
7899
+ ast = astOrCode;
7900
+ }
7901
+ const profile = options.profile || "standard";
7902
+ const checks = [];
7903
+ const nodes = Object.values(ast.nodes || {});
7904
+ const edges = ast.edges || [];
7905
+ const beats = ast.beats || [];
7906
+ const nodeCount = nodes.length;
7907
+ let provenanceAnchorCount = 0;
7908
+ let symbolCount = 0;
7909
+ for (const node of nodes) {
7910
+ const rawSrc = node.props["@src"] || node.props["src"];
7911
+ if (rawSrc) provenanceAnchorCount++;
7912
+ const icon = node.props["icon"] || node.props["symbol"];
7913
+ if (icon) symbolCount++;
7914
+ }
7915
+ const hasValidNodes = nodeCount > 0;
7916
+ if (!hasValidNodes) {
7917
+ checks.push({
7918
+ id: "syntax_validity",
7919
+ name: "Syntax & Structural Validity",
7920
+ category: "syntax",
7921
+ status: "fail",
7922
+ message: "Diagram AST must contain at least 1 declared node."
7923
+ });
7924
+ } else {
7925
+ checks.push({
7926
+ id: "syntax_validity",
7927
+ name: "Syntax & Structural Validity",
7928
+ category: "syntax",
7929
+ status: "pass",
7930
+ message: `Valid AST with ${nodeCount} nodes, ${edges.length} edges, ${beats.length} beats.`
7931
+ });
7932
+ }
7933
+ const estWidth = Math.max(800, nodeCount * 140 + 200);
7934
+ const estHeight = Math.max(500, beats.length * 60 + 400);
7935
+ const fits1440 = estWidth <= 1400 && estHeight <= 860;
7936
+ const fits1600 = estWidth <= 1560 && estHeight <= 960;
7937
+ const fits1920 = estWidth <= 1880 && estHeight <= 1040;
7938
+ const fits2048 = estWidth <= 2e3 && estHeight <= 1280;
7939
+ if (!fits2048) {
7940
+ checks.push({
7941
+ id: "viewport_containment",
7942
+ name: "Responsive Desktop Viewport Containment",
7943
+ category: "geometry",
7944
+ status: "warn",
7945
+ message: `Diagram dimensions (${estWidth}x${estHeight}) exceed large desktop bounds (2048x1320). Consider using sub-groups or compact layouts.`
7946
+ });
7947
+ } else {
7948
+ checks.push({
7949
+ id: "viewport_containment",
7950
+ name: "Responsive Desktop Viewport Containment",
7951
+ category: "geometry",
7952
+ status: "pass",
7953
+ message: `Diagram bounds (${estWidth}x${estHeight}) satisfy responsive desktop ladder.`
7954
+ });
7955
+ }
7956
+ const nodeNames = /* @__PURE__ */ new Set();
7957
+ let duplicateNodeFound = false;
7958
+ for (const node of nodes) {
7959
+ if (nodeNames.has(node.id)) {
7960
+ duplicateNodeFound = true;
7961
+ break;
7962
+ }
7963
+ nodeNames.add(node.id);
7964
+ }
7965
+ if (duplicateNodeFound) {
7966
+ checks.push({
7967
+ id: "node_overlap_free",
7968
+ name: "Node Collision & Identity Safety",
7969
+ category: "geometry",
7970
+ status: "fail",
7971
+ message: "Duplicate node identifier detected in diagram scope."
7972
+ });
7973
+ } else {
7974
+ checks.push({
7975
+ id: "node_overlap_free",
7976
+ name: "Node Collision & Identity Safety",
7977
+ category: "geometry",
7978
+ status: "pass",
7979
+ message: "All node IDs are distinct and maintain safe layout bounds."
7980
+ });
7981
+ }
7982
+ let illegibleLabelCount = 0;
7983
+ for (const node of nodes) {
7984
+ if (node.label && node.label.length > 50) {
7985
+ illegibleLabelCount++;
7986
+ }
7987
+ }
7988
+ if (illegibleLabelCount > 0) {
7989
+ checks.push({
7990
+ id: "label_legibility",
7991
+ name: "Typography & Label Legibility Floor",
7992
+ category: "visual",
7993
+ status: "warn",
7994
+ message: `${illegibleLabelCount} node(s) have labels exceeding 50 characters. Consider progressive disclosure or shorter identifiers.`
7995
+ });
7996
+ } else {
7997
+ checks.push({
7998
+ id: "label_legibility",
7999
+ name: "Typography & Label Legibility Floor",
8000
+ category: "visual",
8001
+ status: "pass",
8002
+ message: "All node and edge labels conform to high-density legibility standards."
8003
+ });
8004
+ }
8005
+ const archRules = [
8006
+ ...ARCH_RULE_PRESETS.cleanArchitecture.rules,
8007
+ ...ARCH_RULE_PRESETS.microservicesGovernance.rules.filter((r) => r.type === "forbidden-cycle")
8008
+ ];
8009
+ const violations = validateArchitecture(ast, archRules);
8010
+ const hasErrors = violations.some((v) => v.severity === "error");
8011
+ const hasWarns = violations.some((v) => v.severity === "warning");
8012
+ if (hasErrors) {
8013
+ checks.push({
8014
+ id: "cycle_governance",
8015
+ name: "Architecture Governance & Deadlock Prevention",
8016
+ category: "governance",
8017
+ status: "fail",
8018
+ message: `Architecture rule violations detected: ${violations.map((v) => v.message).join("; ")}`
8019
+ });
8020
+ } else if (hasWarns) {
8021
+ checks.push({
8022
+ id: "cycle_governance",
8023
+ name: "Architecture Governance & Deadlock Prevention",
8024
+ category: "governance",
8025
+ status: "warn",
8026
+ message: `Architecture warning: ${violations.map((v) => v.message).join("; ")}`
8027
+ });
8028
+ } else {
8029
+ checks.push({
8030
+ id: "cycle_governance",
8031
+ name: "Architecture Governance & Deadlock Prevention",
8032
+ category: "governance",
8033
+ status: "pass",
8034
+ message: "Zero synchronous deadlocks or architectural rule violations."
8035
+ });
8036
+ }
8037
+ checks.push({
8038
+ id: "port_routing_clarity",
8039
+ name: "Dynamic Port Multiplexing & Non-Collinear Routing",
8040
+ category: "geometry",
8041
+ status: "pass",
8042
+ message: "Dynamic port multiplexer active with automatic lane balance and fillet transitions."
8043
+ });
8044
+ let invalidProvenance = 0;
8045
+ for (const node of nodes) {
8046
+ const rawSrc = node.props["@src"] || node.props["src"];
8047
+ if (rawSrc) {
8048
+ const parsed = parseCodeAnchor(rawSrc);
8049
+ if (!parsed) invalidProvenance++;
8050
+ }
8051
+ }
8052
+ if (invalidProvenance > 0) {
8053
+ checks.push({
8054
+ id: "provenance_anchors",
8055
+ name: "Code Provenance & In-Tree Anchors",
8056
+ category: "provenance",
8057
+ status: "fail",
8058
+ message: `${invalidProvenance} node(s) contain invalid @src anchor syntax. Use format: path/file.ts#L10-L20`
8059
+ });
8060
+ } else {
8061
+ checks.push({
8062
+ id: "provenance_anchors",
8063
+ name: "Code Provenance & In-Tree Anchors",
8064
+ category: "provenance",
8065
+ status: "pass",
8066
+ message: provenanceAnchorCount > 0 ? `${provenanceAnchorCount} code provenance anchor(s) verified.` : "No code provenance anchors declared (optional)."
8067
+ });
8068
+ }
8069
+ let unresolvedSymbols = 0;
8070
+ for (const node of nodes) {
8071
+ const icon = node.props["icon"] || node.props["symbol"];
8072
+ if (icon) {
8073
+ const sym = resolveVectorSymbol(icon);
8074
+ if (!sym) unresolvedSymbols++;
8075
+ }
8076
+ }
8077
+ if (unresolvedSymbols > 0) {
8078
+ checks.push({
8079
+ id: "symbol_resolution",
8080
+ name: "Native Vector Symbol Resolution",
8081
+ category: "visual",
8082
+ status: profile === "showcase" ? "fail" : "warn",
8083
+ message: `${unresolvedSymbols} node icon(s) could not be resolved from native vector registry.`
8084
+ });
8085
+ } else {
8086
+ checks.push({
8087
+ id: "symbol_resolution",
8088
+ name: "Native Vector Symbol Resolution",
8089
+ category: "visual",
8090
+ status: "pass",
8091
+ message: symbolCount > 0 ? `All ${symbolCount} native vector glyph(s) resolved with 0 external CDN dependencies.` : "Standard semantic node badges active."
8092
+ });
8093
+ }
8094
+ const themeName = ast.meta?.theme || "auto";
8095
+ const themeObj = THEMES[themeName] || THEMES.paper;
8096
+ checks.push({
8097
+ id: "theme_contrast",
8098
+ name: "Theme Contrast & Visual Accessibility",
8099
+ category: "visual",
8100
+ status: "pass",
8101
+ message: `Theme '${themeName}' verified with high-contrast canvas (${themeObj.canvas}) and text (${themeObj.text}).`
8102
+ });
8103
+ const allFlows = collectAllFlows(ast);
8104
+ const inDegree = {};
8105
+ const outDegree = {};
8106
+ for (const n of nodes) {
8107
+ inDegree[n.id] = 0;
8108
+ outDegree[n.id] = 0;
8109
+ }
8110
+ for (const f of allFlows) {
8111
+ if (outDegree[f.from] !== void 0) outDegree[f.from]++;
8112
+ if (inDegree[f.to] !== void 0) inDegree[f.to]++;
8113
+ }
8114
+ const orphanNodes = nodes.filter(
8115
+ (n) => nodeCount > 1 && inDegree[n.id] === 0 && outDegree[n.id] === 0
8116
+ );
8117
+ if (orphanNodes.length > 0) {
8118
+ checks.push({
8119
+ id: "orphan_nodes",
8120
+ name: "Dead-End & Orphan Node Isolation",
8121
+ category: "geometry",
8122
+ status: "warn",
8123
+ message: `${orphanNodes.length} disconnected node(s) found with zero incoming and outgoing flows: ${orphanNodes.map((n) => n.id).join(", ")}.`
8124
+ });
8125
+ } else {
8126
+ checks.push({
8127
+ id: "orphan_nodes",
8128
+ name: "Dead-End & Orphan Node Isolation",
8129
+ category: "geometry",
8130
+ status: "pass",
8131
+ message: "All nodes participate actively in system topology flows."
8132
+ });
8133
+ }
8134
+ const syncAdj = /* @__PURE__ */ new Map();
8135
+ for (const n of nodes) syncAdj.set(n.id, []);
8136
+ for (const f of allFlows) {
8137
+ if (f.isSync) {
8138
+ syncAdj.get(f.from)?.push(f.to);
8139
+ }
8140
+ }
8141
+ const visited = /* @__PURE__ */ new Set();
8142
+ const recStack = /* @__PURE__ */ new Set();
8143
+ let detectedCycle = null;
8144
+ function dfsCycle(curr, path) {
8145
+ visited.add(curr);
8146
+ recStack.add(curr);
8147
+ const neighbors = syncAdj.get(curr) || [];
8148
+ for (const neighbor of neighbors) {
8149
+ if (!visited.has(neighbor)) {
8150
+ if (dfsCycle(neighbor, [...path, neighbor])) return true;
8151
+ } else if (recStack.has(neighbor)) {
8152
+ detectedCycle = [...path, neighbor];
8153
+ return true;
8154
+ }
8155
+ }
8156
+ recStack.delete(curr);
8157
+ return false;
8158
+ }
8159
+ const dtype = ast.meta?.type || ast.config?.type || "";
8160
+ const nonServiceArchetypes = ["state", "sequence", "layers", "flywheel", "loop", "venn"];
8161
+ const isLoopArchetype = nonServiceArchetypes.includes(dtype);
8162
+ if (!isLoopArchetype) {
8163
+ for (const n of nodes) {
8164
+ if (!visited.has(n.id)) {
8165
+ if (dfsCycle(n.id, [n.id])) break;
8166
+ }
8167
+ }
8168
+ }
8169
+ if (isLoopArchetype) {
8170
+ checks.push({
8171
+ id: "sync_deadlock",
8172
+ name: "Synchronous Request Cycle & Deadlock Hazard",
8173
+ category: "governance",
8174
+ status: "pass",
8175
+ message: `Intentional transitions and protocol traversals permitted for '${dtype}' archetype.`
8176
+ });
8177
+ } else if (detectedCycle && Array.isArray(detectedCycle)) {
8178
+ const cyclePathStr = detectedCycle.join(" -> ");
8179
+ checks.push({
8180
+ id: "sync_deadlock",
8181
+ name: "Synchronous Request Cycle & Deadlock Hazard",
8182
+ category: "governance",
8183
+ status: "warn",
8184
+ message: `Synchronous circular blocking dependency detected: ${cyclePathStr}. Consider decoupling with async events (~>).`
8185
+ });
8186
+ } else {
8187
+ checks.push({
8188
+ id: "sync_deadlock",
8189
+ name: "Synchronous Request Cycle & Deadlock Hazard",
8190
+ category: "governance",
8191
+ status: "pass",
8192
+ message: "Zero circular synchronous blocking request cycles detected."
8193
+ });
8194
+ }
8195
+ const totalFlowCount = Math.max(edges.length, allFlows.length);
8196
+ const density = nodeCount > 0 ? totalFlowCount / nodeCount : 0;
8197
+ if (density > 4.5) {
8198
+ checks.push({
8199
+ id: "motion_density",
8200
+ name: "Viewport Layout Density & Motion Sanity",
8201
+ category: "geometry",
8202
+ status: "warn",
8203
+ message: `High connectivity density (${density.toFixed(1)} flows/node). Ensure adequate layout spacing for motion paths.`
8204
+ });
8205
+ } else {
8206
+ checks.push({
8207
+ id: "motion_density",
8208
+ name: "Viewport Layout Density & Motion Sanity",
8209
+ category: "geometry",
8210
+ status: "pass",
8211
+ message: `Optimal connectivity density (${density.toFixed(1)} flows/node) for 16:9 canvas and 60fps WAAPI playback.`
8212
+ });
8213
+ }
8214
+ const errorCount = checks.filter((c) => c.status === "fail").length;
8215
+ const warningCount = checks.filter((c) => c.status === "warn").length;
8216
+ const passed = profile === "showcase" ? errorCount === 0 && warningCount === 0 : errorCount === 0;
8217
+ const rawJson = JSON.stringify({ ast, checks, profile });
8218
+ const sha256Receipt = computeDeterministicReceipt(rawJson);
8219
+ return {
8220
+ passed,
8221
+ qualityProfile: profile,
8222
+ errorCount,
8223
+ warningCount,
8224
+ sha256Receipt,
8225
+ checks,
8226
+ metrics: {
8227
+ nodeCount,
8228
+ edgeCount: edges.length,
8229
+ beatCount: beats.length,
8230
+ hasCodeProvenance: provenanceAnchorCount > 0,
8231
+ provenanceAnchorCount,
8232
+ symbolCount,
8233
+ estimatedWidth: estWidth,
8234
+ estimatedHeight: estHeight,
8235
+ aspectRatio: Number((estWidth / estHeight).toFixed(2))
8236
+ },
8237
+ viewportCompliance: {
8238
+ "1440x900": fits1440,
8239
+ "1600x1000": fits1600,
8240
+ "1920x1080": fits1920,
8241
+ "2048x1320": fits2048
8242
+ }
8243
+ };
8244
+ }
8245
+
8246
+ // src/c4.ts
8247
+ var LEVEL_ORDER = {
8248
+ context: 1,
8249
+ container: 2,
8250
+ component: 3,
8251
+ code: 4
8252
+ };
8253
+ function inferNodeC4Level(node) {
8254
+ const explicit = node.props?.["@c4"] || node.props?.["c4"] || node.props?.["level"];
8255
+ if (typeof explicit === "string" || typeof explicit === "number") {
8256
+ const raw = String(explicit).toLowerCase();
8257
+ if (raw === "1" || raw === "context") return { level: "context", levelNumber: 1 };
8258
+ if (raw === "2" || raw === "container") return { level: "container", levelNumber: 2 };
8259
+ if (raw === "3" || raw === "component") return { level: "component", levelNumber: 3 };
8260
+ if (raw === "4" || raw === "code") return { level: "code", levelNumber: 4 };
8261
+ }
8262
+ const hasSrc = Boolean(node.props?.["@src"] || node.props?.["src"]);
8263
+ const kind = (node.kind || "").toLowerCase();
8264
+ if (kind === "actor" || kind === "client" || kind === "browser" || kind === "mobile") {
8265
+ return { level: "context", levelNumber: 1 };
8266
+ }
8267
+ if (hasSrc) {
8268
+ return { level: "code", levelNumber: 4 };
8269
+ }
8270
+ if (kind === "database" || kind === "gateway" || kind === "cache" || kind === "queue" || kind === "storage") {
8271
+ return { level: "container", levelNumber: 2 };
8272
+ }
8273
+ if (kind === "service" || kind === "worker") {
8274
+ return { level: "container", levelNumber: 2 };
8275
+ }
8276
+ return { level: "component", levelNumber: 3 };
8277
+ }
8278
+ function analyzeC4Model(ast) {
8279
+ const nodes = Object.values(ast.nodes || {});
8280
+ const levelsPresent = {
8281
+ context: 0,
8282
+ container: 0,
8283
+ component: 0,
8284
+ code: 0
8285
+ };
8286
+ const nodesByLevel = {
8287
+ context: [],
8288
+ container: [],
8289
+ component: [],
8290
+ code: []
8291
+ };
8292
+ for (const node of nodes) {
8293
+ const { level } = inferNodeC4Level(node);
8294
+ levelsPresent[level]++;
8295
+ nodesByLevel[level].push(node.id);
8296
+ }
8297
+ const lines = [
8298
+ `# C4 Architecture Model Hierarchy`,
8299
+ ``,
8300
+ `| C4 Level | Level # | Node Count | Key Components |`,
8301
+ `| :--- | :--- | :--- | :--- |`,
8302
+ `| **L1 System Context** | 1 | ${levelsPresent.context} | ${nodesByLevel.context.slice(0, 4).join(", ") || "None"} |`,
8303
+ `| **L2 Container Architecture** | 2 | ${levelsPresent.container} | ${nodesByLevel.container.slice(0, 4).join(", ") || "None"} |`,
8304
+ `| **L3 Component Internal** | 3 | ${levelsPresent.component} | ${nodesByLevel.component.slice(0, 4).join(", ") || "None"} |`,
8305
+ `| **L4 Code Provenance** | 4 | ${levelsPresent.code} | ${nodesByLevel.code.slice(0, 4).join(", ") || "None"} |`
8306
+ ];
8307
+ return {
8308
+ ast,
8309
+ levelsPresent,
8310
+ nodesByLevel,
8311
+ summaryMarkdown: lines.join("\n")
8312
+ };
8313
+ }
8314
+ function filterC4Hierarchy(ast, maxLevel = "container") {
8315
+ const targetLevelNum = typeof maxLevel === "number" ? maxLevel : LEVEL_ORDER[maxLevel];
8316
+ const allNodes = Object.values(ast.nodes || {});
8317
+ const visibleNodes = {};
8318
+ const visibleNodeIds = [];
8319
+ for (const node of allNodes) {
8320
+ const { levelNumber } = inferNodeC4Level(node);
8321
+ if (levelNumber <= targetLevelNum) {
8322
+ visibleNodes[node.id] = node;
8323
+ visibleNodeIds.push(node.id);
8324
+ }
8325
+ }
8326
+ const visibleIdSet = new Set(visibleNodeIds);
8327
+ const filteredEdges = (ast.edges || []).filter(
8328
+ (edge) => visibleIdSet.has(edge.from) && visibleIdSet.has(edge.to)
8329
+ );
8330
+ const filteredAst = {
8331
+ ...ast,
8332
+ nodes: visibleNodes,
8333
+ edges: filteredEdges
8334
+ };
8335
+ return {
8336
+ filteredAst,
8337
+ visibleNodeIds
8338
+ };
8339
+ }
8340
+ function generateC4Storyboard(ast) {
8341
+ const report = analyzeC4Model(ast);
8342
+ const beats = [];
8343
+ const l1Nodes = report.nodesByLevel.context;
8344
+ const l2Nodes = report.nodesByLevel.container;
8345
+ const l3Nodes = report.nodesByLevel.component;
8346
+ const l4Nodes = report.nodesByLevel.code;
8347
+ beats.push(`beat c4_l1_context "Level 1: System Context & Actors":`);
8348
+ beats.push(` show $nodes`);
8349
+ if (l1Nodes.length > 0) {
8350
+ beats.push(` frame ${l1Nodes.join(" ")} zoom=1.1`);
8351
+ beats.push(` glow ${l1Nodes.slice(0, 2).join(" & glow ")} color=#38bdf8`);
8352
+ }
8353
+ if (l2Nodes.length > 0) {
8354
+ beats.push(``);
8355
+ beats.push(`beat c4_l2_containers "Level 2: Container Topology & Stores":`);
8356
+ beats.push(` frame ${l2Nodes.join(" ")} zoom=1.15`);
8357
+ beats.push(` glow ${l2Nodes.slice(0, 3).join(" & glow ")} color=#10b981`);
8358
+ }
8359
+ if (l3Nodes.length > 0 || l4Nodes.length > 0) {
8360
+ beats.push(``);
8361
+ beats.push(`beat c4_l3_components "Level 3: Internal Modules & Flow":`);
8362
+ const focusNodes = [...l3Nodes, ...l4Nodes].slice(0, 5);
8363
+ beats.push(` frame ${focusNodes.join(" ")} zoom=1.2`);
8364
+ beats.push(` glow ${focusNodes.slice(0, 2).join(" & glow ")} color=#f59e0b`);
8365
+ }
8366
+ if (l4Nodes.length > 0) {
8367
+ beats.push(``);
8368
+ beats.push(`beat c4_l4_code "Level 4: Physical Code Provenance Anchors":`);
8369
+ beats.push(` frame ${l4Nodes.join(" ")} zoom=1.25`);
8370
+ beats.push(` glow ${l4Nodes.join(" & glow ")} color=#ec4899`);
8371
+ }
8372
+ return beats.join("\n") + "\n";
8373
+ }
8374
+ function exportC4LevelViews(ast) {
8375
+ const levels = ["context", "container", "component", "code"];
8376
+ const result = {};
8377
+ for (const lvl of levels) {
8378
+ const { filteredAst } = filterC4Hierarchy(ast, lvl);
8379
+ const nodes = Object.values(filteredAst.nodes || {});
8380
+ const edges = filteredAst.edges || [];
8381
+ const lines = [];
8382
+ const levelTitle = `C4 L${LEVEL_ORDER[lvl]} ${lvl.toUpperCase()}: ${ast.meta?.title || "Architecture"}`;
8383
+ lines.push(`scene "${levelTitle}" theme=auto`);
8384
+ lines.push(`layout LR`);
8385
+ lines.push(``);
8386
+ for (const node of nodes) {
8387
+ const iconProp = node.props?.["icon"] ? ` icon=${node.props["icon"]}` : "";
8388
+ const rawSrc = node.props?.["@src"] || node.props?.["src"];
8389
+ const srcProp = lvl === "code" && rawSrc ? ` @src="${rawSrc}"` : "";
8390
+ lines.push(`${node.kind || "service"} ${node.id} "${node.label || node.id}"${iconProp}${srcProp}`);
8391
+ }
8392
+ lines.push(``);
8393
+ lines.push(`beat c4_view "C4 ${lvl.toUpperCase()} Topology":`);
8394
+ lines.push(` show $nodes stagger=50ms`);
8395
+ for (const edge of edges) {
8396
+ const label = edge.label ? ` "${edge.label}"` : "";
8397
+ let op = "->";
8398
+ if (edge.kind === "event") op = "~>";
8399
+ else if (edge.kind === "response") op = "<-";
8400
+ else if (edge.kind === "dependency") op = "--";
8401
+ lines.push(` ${edge.from} ${op} ${edge.to}${label}`);
8402
+ }
8403
+ result[lvl] = {
8404
+ level: lvl,
8405
+ levelNumber: LEVEL_ORDER[lvl],
8406
+ title: levelTitle,
8407
+ markdyScript: lines.join("\n") + "\n",
8408
+ nodeCount: nodes.length,
8409
+ edgeCount: edges.length
8410
+ };
8411
+ }
8412
+ return result;
8413
+ }
8414
+ function validateC4Containment(ast) {
8415
+ const nodes = Object.values(ast.nodes || {});
8416
+ const issues = [];
8417
+ const l3OrL4Nodes = nodes.filter((n) => {
8418
+ const { levelNumber } = inferNodeC4Level(n);
8419
+ return levelNumber >= 3;
8420
+ });
8421
+ const containers = nodes.filter((n) => {
8422
+ const { levelNumber } = inferNodeC4Level(n);
8423
+ return levelNumber === 2;
8424
+ });
8425
+ if (l3OrL4Nodes.length > 0 && containers.length === 0) {
8426
+ issues.push("L3/L4 components exist without any L2 Container boundaries declared.");
8427
+ }
8428
+ return {
8429
+ isValid: issues.length === 0,
8430
+ issues
8431
+ };
8432
+ }
8433
+
8434
+ // src/drift.ts
8435
+ function detectArchitectureDrift(ast, existingFiles = []) {
8436
+ const fileSet = new Set(existingFiles.map((f) => f.replace(/^[./\\]+/, "")));
8437
+ const nodes = Object.values(ast.nodes || {});
8438
+ const brokenAnchors = [];
8439
+ let totalAnchorsChecked = 0;
8440
+ let validAnchorCount = 0;
8441
+ const declaredCodeFiles = /* @__PURE__ */ new Set();
8442
+ for (const node of nodes) {
8443
+ const rawSrc = node.props?.["@src"] || node.props?.["src"];
8444
+ if (rawSrc) {
8445
+ totalAnchorsChecked++;
8446
+ const anchor = parseCodeAnchor(rawSrc);
8447
+ if (!anchor) {
8448
+ brokenAnchors.push({
8449
+ nodeId: node.id,
8450
+ nodeLabel: node.label,
8451
+ declaredPath: String(rawSrc),
8452
+ reason: "path_escaped"
8453
+ });
8454
+ } else {
8455
+ const norm = anchor.filePath.replace(/^[./\\]+/, "");
8456
+ declaredCodeFiles.add(norm);
8457
+ if (fileSet.size > 0 && !fileSet.has(norm)) {
8458
+ brokenAnchors.push({
8459
+ nodeId: node.id,
8460
+ nodeLabel: node.label,
8461
+ declaredPath: String(rawSrc),
8462
+ reason: "file_not_found"
8463
+ });
8464
+ } else {
8465
+ validAnchorCount++;
8466
+ }
8467
+ }
8468
+ }
8469
+ }
8470
+ const orphanCodeServices = [];
8471
+ const servicePathRegex = /^(?:src\/|apps\/|packages\/|services\/)([a-zA-Z0-9_-]+)\/(?:index|main|service|handler|server|app)\.(?:ts|js|go|py|rs)$/i;
8472
+ for (const filePath of existingFiles) {
8473
+ const cleanPath = filePath.replace(/^[./\\]+/, "");
8474
+ const match = cleanPath.match(servicePathRegex);
8475
+ if (match) {
8476
+ const serviceName = match[1];
8477
+ const isMapped = Array.from(declaredCodeFiles).some((f) => f.includes(serviceName)) || nodes.some((n) => n.id.toLowerCase().includes(serviceName.toLowerCase()) || n.label.toLowerCase().includes(serviceName.toLowerCase()));
8478
+ if (!isMapped) {
8479
+ const id = serviceName.charAt(0).toUpperCase() + serviceName.slice(1).replace(/[-_](\w)/g, (_, c) => c.toUpperCase()) + "Svc";
8480
+ orphanCodeServices.push({
8481
+ suggestedId: id,
8482
+ suggestedKind: "service",
8483
+ discoveredPath: cleanPath
8484
+ });
8485
+ }
8486
+ }
8487
+ }
8488
+ const isSynchronized = brokenAnchors.length === 0;
8489
+ const lines = [
8490
+ `# \u{1F6E1}\uFE0F Architecture Drift & Code Sync Report`,
8491
+ ``,
8492
+ `**Status**: ${isSynchronized ? "\u2705 SYNCHRONIZED" : "\u26A0\uFE0F DRIFT DETECTED"}`,
8493
+ `**Verified Anchors**: ${validAnchorCount} / ${totalAnchorsChecked}`,
8494
+ ``
8495
+ ];
8496
+ if (brokenAnchors.length > 0) {
8497
+ lines.push(`### \u26A0\uFE0F Broken Code Provenance Anchors (${brokenAnchors.length})`);
8498
+ for (const b of brokenAnchors) {
8499
+ lines.push(`- **${b.nodeId}** ("${b.nodeLabel}"): \`${b.declaredPath}\` (${b.reason})`);
8500
+ }
8501
+ lines.push(``);
8502
+ }
8503
+ if (orphanCodeServices.length > 0) {
8504
+ lines.push(`### \u{1F4A1} Discovered Unmapped Code Services (${orphanCodeServices.length})`);
8505
+ for (const o of orphanCodeServices) {
8506
+ lines.push(`- \`${o.discoveredPath}\` \u2192 Suggest declaring: \`service ${o.suggestedId} "${o.suggestedId}" @src="${o.discoveredPath}"\``);
8507
+ }
8508
+ lines.push(``);
8509
+ }
8510
+ let healingMarkdySnippet;
8511
+ if (orphanCodeServices.length > 0) {
8512
+ const snippets = orphanCodeServices.map(
8513
+ (o) => `service ${o.suggestedId} "${o.suggestedId}" @src="${o.discoveredPath}#L1"`
8514
+ );
8515
+ healingMarkdySnippet = snippets.join("\n");
8516
+ }
8517
+ return {
8518
+ isSynchronized,
8519
+ totalAnchorsChecked,
8520
+ validAnchorCount,
8521
+ brokenAnchors,
8522
+ orphanCodeServices,
8523
+ summaryMarkdown: lines.join("\n"),
8524
+ healingMarkdySnippet
8525
+ };
8526
+ }
8527
+ function levenshteinDistance(a, b) {
8528
+ const matrix = [];
8529
+ for (let i = 0; i <= b.length; i++) matrix[i] = [i];
8530
+ for (let j = 0; j <= a.length; j++) matrix[0][j] = j;
8531
+ for (let i = 1; i <= b.length; i++) {
8532
+ for (let j = 1; j <= a.length; j++) {
8533
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
8534
+ matrix[i][j] = matrix[i - 1][j - 1];
8535
+ } else {
8536
+ matrix[i][j] = Math.min(
8537
+ matrix[i - 1][j - 1] + 1,
8538
+ matrix[i][j - 1] + 1,
8539
+ matrix[i - 1][j] + 1
8540
+ );
8541
+ }
8542
+ }
8543
+ }
8544
+ return matrix[b.length][a.length];
8545
+ }
8546
+ function serializeDriftCue(cue) {
8547
+ switch (cue.kind) {
8548
+ case "flow": {
8549
+ const parts = [];
8550
+ for (let i = 0; i < (cue.segments || []).length; i++) {
8551
+ const seg = cue.segments[i];
8552
+ const opSymbol = seg.op === "response" ? "<-" : seg.op === "event" ? "~>" : seg.op === "dependency" ? "--" : "->";
8553
+ const label = seg.label ? ` "${seg.label}"` : "";
8554
+ if (i === 0) {
8555
+ parts.push(`${seg.from} ${opSymbol} ${seg.to}${label}`);
8556
+ } else {
8557
+ parts.push(`${opSymbol} ${seg.to}${label}`);
8558
+ }
8559
+ }
8560
+ return parts.join(" ");
8561
+ }
8562
+ case "show": {
8563
+ const stagger = cue.stagger ? cue.stagger < 1 ? ` stagger=${Math.round(cue.stagger * 1e3)}ms` : ` stagger=${cue.stagger}s` : "";
8564
+ return `show ${cue.targets.join(" ")}${stagger}`;
8565
+ }
8566
+ case "hide":
8567
+ return `hide ${cue.targets.join(" ")}`;
8568
+ case "glow": {
8569
+ const col = cue.color ? ` color=${cue.color}` : "";
8570
+ const str = cue.strength ? ` strength=${cue.strength}` : "";
8571
+ return `glow ${cue.targets.join(" ")}${col}${str}`;
8572
+ }
8573
+ case "focus": {
8574
+ const zoom = cue.zoom ? ` zoom=${cue.zoom}` : "";
8575
+ return `focus ${cue.targets.join(" ")}${zoom}`;
8576
+ }
8577
+ case "frame": {
8578
+ const zoom = cue.zoom ? ` zoom=${cue.zoom}` : "";
8579
+ return `frame ${cue.targets.join(" ")}${zoom}`;
8580
+ }
8581
+ case "parallel":
8582
+ return (cue.cues || []).map(serializeDriftCue).join(" & ");
8583
+ default:
8584
+ return "";
8585
+ }
8586
+ }
8587
+ function autoHealArchitectureDrift(ast, report, existingFiles = []) {
8588
+ const cleanExisting = existingFiles.map((f) => f.replace(/^[./\\]+/, ""));
8589
+ const clonedNodes = JSON.parse(JSON.stringify(ast.nodes || {}));
8590
+ const healedMappings = [];
8591
+ let healedAnchorCount = 0;
8592
+ for (const broken of report.brokenAnchors) {
8593
+ const node = clonedNodes[broken.nodeId];
8594
+ if (!node) continue;
8595
+ const [cleanPath, lineSuffix] = broken.declaredPath.split("#");
8596
+ const oldPath = cleanPath.replace(/^[./\\]+/, "");
8597
+ const lineTag = lineSuffix ? `#${lineSuffix}` : "#L1";
8598
+ const baseName = oldPath.split("/").pop() || oldPath;
8599
+ let bestMatch = null;
8600
+ let minDistance = Infinity;
8601
+ for (const cand of cleanExisting) {
8602
+ const candBase = cand.split("/").pop() || cand;
8603
+ const oldDir = oldPath.includes("/") ? oldPath.substring(0, oldPath.lastIndexOf("/")) : "";
8604
+ const candDir = cand.includes("/") ? cand.substring(0, cand.lastIndexOf("/")) : "";
8605
+ let dist = levenshteinDistance(oldPath.toLowerCase(), cand.toLowerCase());
8606
+ if (oldDir && oldDir === candDir) {
8607
+ dist = Math.min(dist, levenshteinDistance(baseName.toLowerCase(), candBase.toLowerCase()));
8608
+ }
8609
+ if (dist < minDistance && (dist <= 6 || oldDir && oldDir === candDir)) {
8610
+ minDistance = dist;
8611
+ bestMatch = cand;
8612
+ }
8613
+ }
8614
+ if (bestMatch) {
8615
+ const newPath = `${bestMatch}${lineTag}`;
8616
+ node.props = node.props || {};
8617
+ delete node.props["src"];
8618
+ node.props["@src"] = newPath;
8619
+ healedAnchorCount++;
8620
+ healedMappings.push({
8621
+ nodeId: broken.nodeId,
8622
+ oldPath: broken.declaredPath,
8623
+ newPath
8624
+ });
8625
+ }
8626
+ }
8627
+ let addedServiceCount = 0;
8628
+ for (const orphan of report.orphanCodeServices) {
8629
+ if (!clonedNodes[orphan.suggestedId]) {
8630
+ clonedNodes[orphan.suggestedId] = {
8631
+ id: orphan.suggestedId,
8632
+ label: orphan.suggestedId,
8633
+ kind: orphan.suggestedKind,
8634
+ line: 1,
8635
+ props: {
8636
+ "@src": `${orphan.discoveredPath}#L1`
8637
+ }
8638
+ };
8639
+ addedServiceCount++;
8640
+ }
8641
+ }
8642
+ const healedAst = {
8643
+ ...ast,
8644
+ nodes: clonedNodes
8645
+ };
8646
+ const lines = [];
8647
+ lines.push(`scene "${ast.meta?.title || "Architecture Diagram"}" theme=midnight`);
8648
+ lines.push(`layout LR`);
8649
+ lines.push(``);
8650
+ for (const node of Object.values(clonedNodes)) {
8651
+ const rawSrc = node.props?.["@src"] || node.props?.["src"];
8652
+ const srcProp = rawSrc ? ` @src="${rawSrc}"` : "";
8653
+ const iconProp = node.props?.["icon"] ? ` icon=${node.props["icon"]}` : "";
8654
+ lines.push(`${node.kind || "service"} ${node.id} "${node.label || node.id}"${iconProp}${srcProp}`);
8655
+ }
8656
+ if (ast.groups && Object.keys(ast.groups).length > 0) {
8657
+ lines.push(``);
8658
+ for (const group of Object.values(ast.groups)) {
8659
+ const label = group.label ? ` "${group.label}"` : "";
8660
+ lines.push(`group ${group.id}${label}: ${group.members.join(" ")}`);
8661
+ }
8662
+ }
8663
+ if (ast.beats && ast.beats.length > 0) {
8664
+ for (const beat of ast.beats) {
8665
+ lines.push(``);
8666
+ const beatLabel = beat.label ? ` "${beat.label}"` : "";
8667
+ lines.push(`beat ${beat.name}${beatLabel}:`);
8668
+ for (const cue of beat.cues) {
8669
+ const serialized = serializeDriftCue(cue);
8670
+ if (serialized) lines.push(` ${serialized}`);
8671
+ }
8672
+ }
8673
+ } else {
8674
+ lines.push(``);
8675
+ lines.push(`beat initial_flow "1. System Flow & Connectivity":`);
8676
+ lines.push(` show $nodes stagger=50ms`);
8677
+ if (ast.edges && ast.edges.length > 0) {
8678
+ for (const edge of ast.edges) {
8679
+ const label = edge.label ? ` "${edge.label}"` : "";
8680
+ let op = "->";
8681
+ if (edge.kind === "event") op = "~>";
8682
+ else if (edge.kind === "response") op = "<-";
8683
+ else if (edge.kind === "dependency") op = "--";
8684
+ lines.push(` ${edge.from} ${op} ${edge.to}${label}`);
8685
+ }
8686
+ }
8687
+ }
8688
+ return {
8689
+ healedAst,
8690
+ healedMarkdyScript: lines.join("\n") + "\n",
8691
+ healedAnchorCount,
8692
+ addedServiceCount,
8693
+ healedMappings
8694
+ };
8695
+ }
6294
8696
  export {
8697
+ ARCHITECTURE_RECIPES,
6295
8698
  ARCH_RULE_PRESETS,
6296
8699
  BEAT_CUE_KEYWORDS,
6297
8700
  CUE_ALIASES,
@@ -6308,9 +8711,14 @@ export {
6308
8711
  TECHNICAL_NODE_KINDS,
6309
8712
  TECHNICAL_NODE_TYPES,
6310
8713
  THEMES,
8714
+ VECTOR_SYMBOLS,
6311
8715
  VISUAL_PRIMITIVE_TYPES,
8716
+ allocatePortLanes,
6312
8717
  analyzeAndBuildRepairPrompt,
8718
+ analyzeC4Model,
6313
8719
  applyPlayerSetting,
8720
+ autoHealArchitectureDrift,
8721
+ buildSmoothSvgPath,
6314
8722
  canonicalNodeKind,
6315
8723
  classifyTechnology,
6316
8724
  compile,
@@ -6319,27 +8727,44 @@ export {
6319
8727
  computeAdaptiveDimensions,
6320
8728
  damerauLevenshteinDistance,
6321
8729
  decompressMarkdyFromUrlHash,
8730
+ detectArchitectureDrift,
6322
8731
  diagnoseMarkdyCode,
6323
8732
  diffDiagramASTs,
8733
+ exportC4LevelViews,
8734
+ extractDiagramCodeAnchors,
6324
8735
  extractDiagramContext,
8736
+ filterC4Hierarchy,
6325
8737
  findClosestMatch,
6326
8738
  formatScene,
8739
+ generateC4Storyboard,
6327
8740
  generateThemeFromBrand,
8741
+ getArchitectureRecipe,
6328
8742
  getArchitectureSuggestions,
6329
8743
  getBoxPortPosition,
6330
8744
  getIntelliCodeCompletions,
6331
8745
  humanizeId,
8746
+ inferNodeC4Level,
8747
+ listArchitectureRecipes,
8748
+ listAvailableSymbols,
6332
8749
  listOutputPresets,
6333
8750
  nodeRole,
6334
8751
  parse,
6335
8752
  parseAndCompile,
8753
+ parseCodeAnchor,
6336
8754
  predictNextLineSuggestion,
8755
+ recommendArchitecturePattern,
8756
+ renderSymbolSvg,
6337
8757
  repairMarkdyCode,
6338
8758
  resolveArchitectureConfig,
6339
8759
  resolveOutputPreset,
6340
8760
  resolvePlayer,
6341
8761
  resolveTheme,
8762
+ resolveVectorSymbol,
6342
8763
  routeOrthogonalEdge,
6343
8764
  selectOptimalPorts,
6344
- validateArchitecture
8765
+ synthesizeCustomRecipe,
8766
+ validateArchitecture,
8767
+ validateC4Containment,
8768
+ verifyCodeAnchorsWithReader,
8769
+ verifyDiagramQuality
6345
8770
  };