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