archdraw-mcp-server 1.0.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.
Files changed (109) hide show
  1. package/dist/index.d.ts +3 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +757 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/lib/__tests__/constants.test.d.ts +2 -0
  6. package/dist/lib/__tests__/constants.test.d.ts.map +1 -0
  7. package/dist/lib/__tests__/constants.test.js +47 -0
  8. package/dist/lib/__tests__/constants.test.js.map +1 -0
  9. package/dist/lib/__tests__/diagram-state.test.d.ts +2 -0
  10. package/dist/lib/__tests__/diagram-state.test.d.ts.map +1 -0
  11. package/dist/lib/__tests__/diagram-state.test.js +75 -0
  12. package/dist/lib/__tests__/diagram-state.test.js.map +1 -0
  13. package/dist/lib/__tests__/elk-runner.test.d.ts +2 -0
  14. package/dist/lib/__tests__/elk-runner.test.d.ts.map +1 -0
  15. package/dist/lib/__tests__/elk-runner.test.js +87 -0
  16. package/dist/lib/__tests__/elk-runner.test.js.map +1 -0
  17. package/dist/lib/checkpoints.d.ts +48 -0
  18. package/dist/lib/checkpoints.d.ts.map +1 -0
  19. package/dist/lib/checkpoints.js +58 -0
  20. package/dist/lib/checkpoints.js.map +1 -0
  21. package/dist/lib/constants.d.ts +29 -0
  22. package/dist/lib/constants.d.ts.map +1 -0
  23. package/dist/lib/constants.js +62 -0
  24. package/dist/lib/constants.js.map +1 -0
  25. package/dist/lib/diagram-state.d.ts +18 -0
  26. package/dist/lib/diagram-state.d.ts.map +1 -0
  27. package/dist/lib/diagram-state.js +55 -0
  28. package/dist/lib/diagram-state.js.map +1 -0
  29. package/dist/lib/elk-runner.d.ts +29 -0
  30. package/dist/lib/elk-runner.d.ts.map +1 -0
  31. package/dist/lib/elk-runner.js +532 -0
  32. package/dist/lib/elk-runner.js.map +1 -0
  33. package/dist/lib/http.d.ts +3 -0
  34. package/dist/lib/http.d.ts.map +1 -0
  35. package/dist/lib/http.js +13 -0
  36. package/dist/lib/http.js.map +1 -0
  37. package/dist/lib/node-catalog.d.ts +10 -0
  38. package/dist/lib/node-catalog.d.ts.map +1 -0
  39. package/dist/lib/node-catalog.js +122 -0
  40. package/dist/lib/node-catalog.js.map +1 -0
  41. package/dist/lib/parse-prompt.d.ts +26 -0
  42. package/dist/lib/parse-prompt.d.ts.map +1 -0
  43. package/dist/lib/parse-prompt.js +289 -0
  44. package/dist/lib/parse-prompt.js.map +1 -0
  45. package/dist/lib/prompt-builder.d.ts +14 -0
  46. package/dist/lib/prompt-builder.d.ts.map +1 -0
  47. package/dist/lib/prompt-builder.js +133 -0
  48. package/dist/lib/prompt-builder.js.map +1 -0
  49. package/dist/lib/schema.d.ts +1231 -0
  50. package/dist/lib/schema.d.ts.map +1 -0
  51. package/dist/lib/schema.js +258 -0
  52. package/dist/lib/schema.js.map +1 -0
  53. package/dist/server.d.ts +36 -0
  54. package/dist/server.d.ts.map +1 -0
  55. package/dist/server.js +287 -0
  56. package/dist/server.js.map +1 -0
  57. package/dist/tools/__tests__/export-diagram.test.d.ts +2 -0
  58. package/dist/tools/__tests__/export-diagram.test.d.ts.map +1 -0
  59. package/dist/tools/__tests__/export-diagram.test.js +40 -0
  60. package/dist/tools/__tests__/export-diagram.test.js.map +1 -0
  61. package/dist/tools/__tests__/update-diagram.test.d.ts +2 -0
  62. package/dist/tools/__tests__/update-diagram.test.d.ts.map +1 -0
  63. package/dist/tools/__tests__/update-diagram.test.js +90 -0
  64. package/dist/tools/__tests__/update-diagram.test.js.map +1 -0
  65. package/dist/tools/apply-template.d.ts +28 -0
  66. package/dist/tools/apply-template.d.ts.map +1 -0
  67. package/dist/tools/apply-template.js +388 -0
  68. package/dist/tools/apply-template.js.map +1 -0
  69. package/dist/tools/export-diagram.d.ts +36 -0
  70. package/dist/tools/export-diagram.d.ts.map +1 -0
  71. package/dist/tools/export-diagram.js +65 -0
  72. package/dist/tools/export-diagram.js.map +1 -0
  73. package/dist/tools/fix-layout.d.ts +19 -0
  74. package/dist/tools/fix-layout.d.ts.map +1 -0
  75. package/dist/tools/fix-layout.js +100 -0
  76. package/dist/tools/fix-layout.js.map +1 -0
  77. package/dist/tools/generate-diagram.d.ts +31 -0
  78. package/dist/tools/generate-diagram.d.ts.map +1 -0
  79. package/dist/tools/generate-diagram.js +385 -0
  80. package/dist/tools/generate-diagram.js.map +1 -0
  81. package/dist/tools/list-nodes.d.ts +18 -0
  82. package/dist/tools/list-nodes.d.ts.map +1 -0
  83. package/dist/tools/list-nodes.js +48 -0
  84. package/dist/tools/list-nodes.js.map +1 -0
  85. package/dist/tools/load-checkpoint.d.ts +18 -0
  86. package/dist/tools/load-checkpoint.d.ts.map +1 -0
  87. package/dist/tools/load-checkpoint.js +24 -0
  88. package/dist/tools/load-checkpoint.js.map +1 -0
  89. package/dist/tools/read-me.d.ts +2 -0
  90. package/dist/tools/read-me.d.ts.map +1 -0
  91. package/dist/tools/read-me.js +326 -0
  92. package/dist/tools/read-me.js.map +1 -0
  93. package/dist/tools/save-checkpoint.d.ts +11 -0
  94. package/dist/tools/save-checkpoint.d.ts.map +1 -0
  95. package/dist/tools/save-checkpoint.js +17 -0
  96. package/dist/tools/save-checkpoint.js.map +1 -0
  97. package/dist/tools/update-diagram.d.ts +20 -0
  98. package/dist/tools/update-diagram.d.ts.map +1 -0
  99. package/dist/tools/update-diagram.js +189 -0
  100. package/dist/tools/update-diagram.js.map +1 -0
  101. package/dist/tools/validate-diagram.d.ts +14 -0
  102. package/dist/tools/validate-diagram.d.ts.map +1 -0
  103. package/dist/tools/validate-diagram.js +181 -0
  104. package/dist/tools/validate-diagram.js.map +1 -0
  105. package/dist/types/index.d.ts +218 -0
  106. package/dist/types/index.d.ts.map +1 -0
  107. package/dist/types/index.js +2 -0
  108. package/dist/types/index.js.map +1 -0
  109. package/package.json +50 -0
@@ -0,0 +1,36 @@
1
+ export declare function exportDiagram(input: unknown): Promise<{
2
+ success: boolean;
3
+ error: string;
4
+ format?: undefined;
5
+ nodes?: undefined;
6
+ edges?: undefined;
7
+ label?: undefined;
8
+ message?: undefined;
9
+ editorUrl?: undefined;
10
+ instructions?: undefined;
11
+ } | {
12
+ success: boolean;
13
+ format: string;
14
+ nodes: import("../types/index.js").ReactFlowNode[];
15
+ edges: import("../types/index.js").ReactFlowEdge[];
16
+ label: string;
17
+ message: string;
18
+ error?: undefined;
19
+ editorUrl?: undefined;
20
+ instructions?: undefined;
21
+ } | {
22
+ success: boolean;
23
+ format: "png" | "svg";
24
+ message: string;
25
+ editorUrl: string | undefined;
26
+ instructions: {
27
+ json: string;
28
+ png: string;
29
+ svg: string;
30
+ };
31
+ error?: undefined;
32
+ nodes?: undefined;
33
+ edges?: undefined;
34
+ label?: undefined;
35
+ }>;
36
+ //# sourceMappingURL=export-diagram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-diagram.d.ts","sourceRoot":"","sources":["../../src/tools/export-diagram.ts"],"names":[],"mappings":"AAMA,wBAAsB,aAAa,CAAC,KAAK,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiEjD"}
@@ -0,0 +1,65 @@
1
+ import { getDiagramState } from '../lib/diagram-state.js';
2
+ import { ExportDiagramInputSchema } from '../lib/schema.js';
3
+ import { fetchWithTimeout } from '../lib/http.js';
4
+ const API_BASE = process.env.API_BASE_URL || 'http://localhost:3000';
5
+ export async function exportDiagram(input) {
6
+ const validated = ExportDiagramInputSchema.parse(input);
7
+ const format = validated.format;
8
+ const sessionId = validated.sessionId || getDiagramState().sessionId;
9
+ const state = getDiagramState();
10
+ if (state.nodes.length === 0) {
11
+ return {
12
+ success: false,
13
+ error: 'No diagram loaded. Generate or load a diagram first.',
14
+ };
15
+ }
16
+ if (format === 'json') {
17
+ return {
18
+ success: true,
19
+ format: 'json',
20
+ nodes: state.nodes,
21
+ edges: state.edges,
22
+ label: 'ArchDraw Diagram',
23
+ message: 'Diagram exported as JSON. You can save this data to a file.',
24
+ };
25
+ }
26
+ if (!sessionId) {
27
+ return {
28
+ success: false,
29
+ error: 'No sessionId available for this diagram. Export as JSON instead, or generate a diagram first.',
30
+ };
31
+ }
32
+ try {
33
+ const response = await fetchWithTimeout(`${API_BASE}/api/diagram/export`, {
34
+ method: 'POST',
35
+ headers: { 'Content-Type': 'application/json' },
36
+ body: JSON.stringify({ sessionId, format }),
37
+ });
38
+ if (!response.ok) {
39
+ const errorData = await response.json().catch(() => ({}));
40
+ return {
41
+ success: false,
42
+ error: errorData.error || `Export failed: ${response.status}`,
43
+ };
44
+ }
45
+ const data = await response.json();
46
+ return {
47
+ success: true,
48
+ format,
49
+ message: `To export as ${format.toUpperCase()}, open the editor URL in your browser and use the export button.`,
50
+ editorUrl: data.editorUrl,
51
+ instructions: {
52
+ json: 'Use format: "json" to get raw diagram data',
53
+ png: 'Open editor and click Export → PNG',
54
+ svg: 'Open editor and click Export → SVG',
55
+ },
56
+ };
57
+ }
58
+ catch (error) {
59
+ return {
60
+ success: false,
61
+ error: error instanceof Error ? error.message : 'Export failed',
62
+ };
63
+ }
64
+ }
65
+ //# sourceMappingURL=export-diagram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-diagram.js","sourceRoot":"","sources":["../../src/tools/export-diagram.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAElD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,uBAAuB,CAAC;AAErE,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,KAAc;IAChD,MAAM,SAAS,GAAG,wBAAwB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACxD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAChC,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,IAAI,eAAe,EAAE,CAAC,SAAS,CAAC;IAErE,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;IAChC,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,sDAAsD;SAC9D,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO;YACL,OAAO,EAAE,IAAI;YACb,MAAM,EAAE,MAAM;YACd,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK,EAAE,kBAAkB;YACzB,OAAO,EAAE,6DAA6D;SACvE,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,+FAA+F;SACvG,CAAC;IACJ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,GAAG,QAAQ,qBAAqB,EAAE;YACxE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;SAC5C,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAuB,CAAC;YAChF,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,kBAAkB,QAAQ,CAAC,MAAM,EAAE;aAC9D,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAkF,CAAC;QAEnH,OAAO;YACL,OAAO,EAAE,IAAI;YACb,MAAM;YACN,OAAO,EAAE,gBAAgB,MAAM,CAAC,WAAW,EAAE,kEAAkE;YAC/G,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,YAAY,EAAE;gBACZ,IAAI,EAAE,4CAA4C;gBAClD,GAAG,EAAE,oCAAoC;gBACzC,GAAG,EAAE,oCAAoC;aAC1C;SACF,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;SAChE,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { FixLayoutInput } from '../lib/schema.js';
2
+ export declare function fixLayout(input: FixLayoutInput): Promise<{
3
+ success: boolean;
4
+ elkPositions: Array<{
5
+ id: string;
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ }>;
11
+ metadata: {
12
+ nodeCount: number;
13
+ edgeCount: number;
14
+ layoutAlgorithm: string;
15
+ direction: string;
16
+ };
17
+ errors?: string[];
18
+ }>;
19
+ //# sourceMappingURL=fix-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-layout.d.ts","sourceRoot":"","sources":["../../src/tools/fix-layout.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAwBvD,wBAAsB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC;IAC9D,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzF,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC,CAsGD"}
@@ -0,0 +1,100 @@
1
+ import { runELKLayout, validateLayout } from '../lib/elk-runner.js';
2
+ import { normalizeLayer, getTierColor } from '../lib/node-catalog.js';
3
+ import { TIER_COLORS, COMM_COLORS } from '../lib/constants.js';
4
+ const DEFAULT_NODE_WIDTH = 220;
5
+ const DEFAULT_NODE_HEIGHT = 100;
6
+ export async function fixLayout(input) {
7
+ const errors = [];
8
+ try {
9
+ const { nodes: inputNodes, edges: inputEdges, direction } = input;
10
+ if (inputNodes.length === 0) {
11
+ return {
12
+ success: false,
13
+ elkPositions: [],
14
+ metadata: {
15
+ nodeCount: 0,
16
+ edgeCount: 0,
17
+ layoutAlgorithm: 'ELK layered',
18
+ direction,
19
+ },
20
+ errors: ['No nodes provided'],
21
+ };
22
+ }
23
+ const architectureNodes = inputNodes.map((node, index) => {
24
+ const normalizedLayer = normalizeLayer(node.layer);
25
+ const tierColor = node.tierColor || getTierColor(normalizedLayer) || TIER_COLORS[normalizedLayer];
26
+ return {
27
+ id: node.id || `node-${index}`,
28
+ type: 'architectureNode',
29
+ label: node.label || `Node ${index}`,
30
+ layer: normalizedLayer,
31
+ tier: normalizedLayer,
32
+ tierColor,
33
+ width: Math.max(node.width || DEFAULT_NODE_WIDTH, 200),
34
+ height: Math.max(node.height || DEFAULT_NODE_HEIGHT, 100),
35
+ icon: 'box',
36
+ metadata: {},
37
+ };
38
+ });
39
+ const nodeIdSet = new Set(architectureNodes.map(n => n.id));
40
+ const architectureEdges = inputEdges.map((edge, index) => {
41
+ const commType = (edge.communicationType || 'sync');
42
+ const commStyle = COMM_COLORS[commType] || COMM_COLORS.sync;
43
+ if (!nodeIdSet.has(edge.source)) {
44
+ errors.push(`Edge ${edge.id || index}: invalid source node ${edge.source}`);
45
+ }
46
+ if (!nodeIdSet.has(edge.target)) {
47
+ errors.push(`Edge ${edge.id || index}: invalid target node ${edge.target}`);
48
+ }
49
+ return {
50
+ id: edge.id || `edge-${index}`,
51
+ source: edge.source,
52
+ target: edge.target,
53
+ sourceHandle: 'right',
54
+ targetHandle: 'left',
55
+ communicationType: (commType),
56
+ pathType: 'smooth',
57
+ label: '',
58
+ labelPosition: 'center',
59
+ animated: commType !== 'sync' && commType !== 'dep',
60
+ style: {
61
+ stroke: commStyle.color,
62
+ strokeDasharray: commStyle.dash,
63
+ strokeWidth: 2,
64
+ },
65
+ markerEnd: 'arrowclosed',
66
+ markerStart: 'none',
67
+ };
68
+ });
69
+ const layoutResult = await runELKLayout(architectureNodes, architectureEdges, { direction });
70
+ const layoutValidation = validateLayout(layoutResult.nodes, layoutResult.edges);
71
+ if (!layoutValidation.valid) {
72
+ errors.push(...layoutValidation.errors);
73
+ }
74
+ return {
75
+ success: true,
76
+ elkPositions: layoutResult.elkPositions,
77
+ metadata: {
78
+ nodeCount: layoutResult.nodes.length,
79
+ edgeCount: layoutResult.edges.length,
80
+ layoutAlgorithm: 'ELK layered',
81
+ direction,
82
+ },
83
+ errors: errors.length > 0 ? errors : undefined,
84
+ };
85
+ }
86
+ catch (error) {
87
+ return {
88
+ success: false,
89
+ elkPositions: [],
90
+ metadata: {
91
+ nodeCount: 0,
92
+ edgeCount: 0,
93
+ layoutAlgorithm: 'ELK layered',
94
+ direction: input.direction,
95
+ },
96
+ errors: [error instanceof Error ? error.message : 'Unknown error'],
97
+ };
98
+ }
99
+ }
100
+ //# sourceMappingURL=fix-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-layout.js","sourceRoot":"","sources":["../../src/tools/fix-layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import type { ReactFlowNode, ReactFlowEdge } from '../types/index.js';
2
+ import type { GenerateDiagramInput } from '../lib/schema.js';
3
+ export declare function generateDiagram(input: GenerateDiagramInput): Promise<{
4
+ success: boolean;
5
+ nodes: ReactFlowNode[];
6
+ edges: ReactFlowEdge[];
7
+ elkPositions: Array<{
8
+ id: string;
9
+ x: number;
10
+ y: number;
11
+ width: number;
12
+ height: number;
13
+ }>;
14
+ metadata: {
15
+ nodeCount: number;
16
+ edgeCount: number;
17
+ layoutAlgorithm: string;
18
+ direction: string;
19
+ groupCount: number;
20
+ };
21
+ diagramUrl?: string;
22
+ sessionId?: string;
23
+ shareUrl?: string;
24
+ embeddedDiagram?: {
25
+ nodes: ReactFlowNode[];
26
+ edges: ReactFlowEdge[];
27
+ };
28
+ message?: string;
29
+ errors?: string[];
30
+ }>;
31
+ //# sourceMappingURL=generate-diagram.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-diagram.d.ts","sourceRoot":"","sources":["../../src/tools/generate-diagram.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAsC,aAAa,EAAE,aAAa,EAAY,MAAM,mBAAmB,CAAC;AACpH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAmL7D,wBAAsB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC;IAC1E,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,YAAY,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACzF,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE;QAAE,KAAK,EAAE,aAAa,EAAE,CAAC;QAAC,KAAK,EAAE,aAAa,EAAE,CAAA;KAAE,CAAC;IACrE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC,CA+RD"}
@@ -0,0 +1,385 @@
1
+ import { runELKLayout, validateLayout } from '../lib/elk-runner.js';
2
+ import { parseUserPrompt } from '../lib/parse-prompt.js';
3
+ import { fetchWithTimeout } from '../lib/http.js';
4
+ import { TIER_COLORS, TIER_ORDER, TIER_RANK, COMM_COLORS, DEFAULT_NODE_WIDTH, DEFAULT_NODE_HEIGHT, DEFAULT_GROUP_WIDTH, DEFAULT_GROUP_HEIGHT, } from '../lib/constants.js';
5
+ function assignTierFromLayer(layer) {
6
+ const normalized = layer.toLowerCase();
7
+ if (TIER_ORDER.includes(normalized))
8
+ return normalized;
9
+ return 'compute';
10
+ }
11
+ function validateNodes(nodes, techStack, customFeatures) {
12
+ const errors = [];
13
+ const ids = new Set();
14
+ for (const node of nodes) {
15
+ if (!node.id) {
16
+ errors.push(`Node missing id: ${node.label}`);
17
+ }
18
+ else if (ids.has(node.id)) {
19
+ errors.push(`Duplicate node id: ${node.id}`);
20
+ }
21
+ else {
22
+ ids.add(node.id);
23
+ }
24
+ if (!node.label)
25
+ errors.push(`Node missing label`);
26
+ const layer = node.tier || node.layer;
27
+ if (!layer)
28
+ errors.push(`Node ${node.label || node.id} missing tier/layer`);
29
+ // Tech stack fidelity: warn if subtitle looks generic
30
+ if (node.subtitle) {
31
+ const genericTerms = ['service', 'server', 'api', 'database', 'storage', 'cache', 'worker', 'backend'];
32
+ const subtitleLower = node.subtitle.toLowerCase();
33
+ const labelLower = (node.label || '').toLowerCase();
34
+ const isGeneric = genericTerms.some(t => subtitleLower === t || subtitleLower === `${labelLower} ${t}`);
35
+ if (isGeneric && techStack && techStack.length > 0) {
36
+ errors.push(`WARNING: Node "${node.label}" has a generic subtitle "${node.subtitle}". With user-specified tech stack, subtitles must include actual technology names.`);
37
+ }
38
+ }
39
+ }
40
+ // Validate parentId references
41
+ for (const node of nodes) {
42
+ if (node.parentId && !ids.has(node.parentId)) {
43
+ errors.push(`Node "${node.id}" has parentId "${node.parentId}" which does not exist`);
44
+ }
45
+ if (node.parentId && node.isGroup) {
46
+ errors.push(`Node "${node.id}" cannot be both a group and a child (parentId + isGroup)`);
47
+ }
48
+ }
49
+ // Tech stack fidelity check: every specified tech must appear in at least one node
50
+ if (techStack && techStack.length > 0) {
51
+ const allText = nodes.map(n => `${n.label} ${n.subtitle || ''}`).join(' ').toLowerCase();
52
+ for (const tech of techStack) {
53
+ if (!allText.includes(tech.toLowerCase())) {
54
+ errors.push(`TECH FIDELITY: User specified "${tech}" but it does not appear in any node label or subtitle. Add a node or update a subtitle to mention "${tech}".`);
55
+ }
56
+ }
57
+ }
58
+ // Custom features check
59
+ if (customFeatures && customFeatures.length > 0) {
60
+ const allText = nodes.map(n => `${n.label} ${n.subtitle || ''}`).join(' ').toLowerCase();
61
+ for (const feature of customFeatures) {
62
+ if (!allText.includes(feature.toLowerCase())) {
63
+ errors.push(`FEATURE MISSING: User requested "${feature}" but it does not appear in any node label or subtitle. Add a dedicated node or update an existing one.`);
64
+ }
65
+ }
66
+ }
67
+ return errors;
68
+ }
69
+ function validateEdgeTopology(nodes, edges) {
70
+ const topologyErrors = [];
71
+ const nodeMap = new Map(nodes.map(n => [n.id, n]));
72
+ // Count how many edges TARGET each node (inbound degree)
73
+ const inboundCount = new Map();
74
+ for (const n of nodes)
75
+ inboundCount.set(n.id, 0);
76
+ for (const e of edges) {
77
+ inboundCount.set(e.target, (inboundCount.get(e.target) || 0) + 1);
78
+ }
79
+ const totalEdges = edges.length;
80
+ for (const node of nodes) {
81
+ if (node.isGroup)
82
+ continue;
83
+ const tier = (node.tier || node.layer || 'compute').toLowerCase();
84
+ const inbound = inboundCount.get(node.id) || 0;
85
+ const label = node.label || node.id;
86
+ // ── RULE 1: Client nodes are SOURCES — max 1-2 inbound edges (responses only) ──
87
+ if (tier === 'client' && inbound > 2) {
88
+ topologyErrors.push(`🚨 STAR TOPOLOGY — Client node "${label}" has ${inbound} edges pointing TO it. ` +
89
+ `Client tier nodes (Web App, Mobile App) INITIATE requests — they are edge SOURCES, not targets. ` +
90
+ `FIX: Reverse these edges. Flow must be: Web Client → API Gateway → Services → Data. ` +
91
+ `Services must NEVER point back to the Web Client.`);
92
+ }
93
+ // ── RULE 2: No single non-gateway node should receive >45% of all edges ──
94
+ if (totalEdges >= 4 && inbound >= Math.ceil(totalEdges * 0.45) && tier !== 'edge') {
95
+ topologyErrors.push(`🚨 STAR TOPOLOGY — Node "${label}" (${tier}) is acting as a HUB with ${inbound}/${totalEdges} edges pointing TO it. ` +
96
+ `Architecture must use tiered flow, not hub-and-spoke. ` +
97
+ `FIX: Route connections through the appropriate gateway/service tier instead of directly to this node.`);
98
+ }
99
+ }
100
+ // ── RULE 3: No backward edges (higher tier → lower tier except response flows) ──
101
+ for (const edge of edges) {
102
+ const sourceNode = nodeMap.get(edge.source);
103
+ const targetNode = nodeMap.get(edge.target);
104
+ if (!sourceNode || !targetNode)
105
+ continue;
106
+ if (sourceNode.isGroup || targetNode.isGroup)
107
+ continue;
108
+ const sourceTier = (sourceNode.tier || sourceNode.layer || 'compute').toLowerCase();
109
+ const targetTier = (targetNode.tier || targetNode.layer || 'compute').toLowerCase();
110
+ const sourceRank = TIER_RANK[sourceTier] ?? 2;
111
+ const targetRank = TIER_RANK[targetTier] ?? 2;
112
+ // Compute/data/async tier nodes must NEVER connect to client tier
113
+ if (sourceRank >= 2 && targetRank === 0) {
114
+ topologyErrors.push(`🚨 BACKWARD EDGE — "${sourceNode.label}" (${sourceTier}) → "${targetNode.label}" (client). ` +
115
+ `Backend services must NEVER send edges to the client tier. ` +
116
+ `FIX: Remove this edge. If the client needs real-time updates, add a WebSocket Gateway or Notification Service as an intermediary.`);
117
+ }
118
+ // Edge tier nodes should generally not point back to client either
119
+ if (sourceRank >= 1 && targetRank === 0 && sourceTier !== 'external') {
120
+ topologyErrors.push(`⚠️ DIRECTION WARNING — "${sourceNode.label}" (${sourceTier}) → "${targetNode.label}" (client). ` +
121
+ `In request-response architecture, edges flow LEFT→RIGHT: client → gateway → services → data. ` +
122
+ `Do not draw reverse edges back to the client.`);
123
+ }
124
+ }
125
+ return topologyErrors;
126
+ }
127
+ export async function generateDiagram(input) {
128
+ const errors = [];
129
+ try {
130
+ const { direction } = input;
131
+ // Handle Mermaid input directly
132
+ if (input.mermaid) {
133
+ const API_BASE = process.env.API_BASE_URL || 'http://localhost:3000';
134
+ const label = input.label || 'AI Diagram';
135
+ const saveResponse = await fetchWithTimeout(`${API_BASE}/api/diagram/load`, {
136
+ method: 'POST',
137
+ headers: { 'Content-Type': 'application/json' },
138
+ body: JSON.stringify({
139
+ mermaid: input.mermaid,
140
+ label,
141
+ source: 'mcp',
142
+ }),
143
+ });
144
+ if (!saveResponse.ok) {
145
+ const errText = await saveResponse.text();
146
+ let errData;
147
+ try {
148
+ errData = JSON.parse(errText);
149
+ }
150
+ catch { }
151
+ const errorMsg = errData?.error || errText || 'Failed to save diagram to API';
152
+ const warnings = errData?.warnings ? ` Warnings: ${errData.warnings.join(', ')}` : '';
153
+ return {
154
+ success: false, nodes: [], edges: [], elkPositions: [],
155
+ metadata: { nodeCount: 0, edgeCount: 0, layoutAlgorithm: 'Mermaid Pipeline', direction: input.direction || 'RIGHT', groupCount: 0 },
156
+ errors: [`${errorMsg}${warnings}`],
157
+ };
158
+ }
159
+ const saveData = await saveResponse.json();
160
+ const urlPath = `/editor?session=${saveData.sessionId}`;
161
+ const diagramUrl = `${API_BASE}${urlPath}`;
162
+ const sessionId = saveData.sessionId;
163
+ const shareUrl = `${API_BASE}/share/${sessionId}`;
164
+ const nodeCount = saveData.nodes.filter(n => !n.data?.isGroup).length;
165
+ const groupCount = saveData.nodes.filter(n => n.data?.isGroup).length;
166
+ const techStack = input.techStack || [];
167
+ const customFeatures = input.customFeatures || [];
168
+ const techLine = techStack.length > 0
169
+ ? `\n\n🔧 **Tech Stack Applied**: ${techStack.join(', ')}`
170
+ : '';
171
+ const featuresLine = customFeatures.length > 0
172
+ ? `\n⚡ **Features Included**: ${customFeatures.join(', ')}`
173
+ : '';
174
+ const promptLine = input.userPrompt
175
+ ? `\n\n💬 **From prompt**: "${input.userPrompt.substring(0, 100)}${input.userPrompt.length > 100 ? '...' : ''}"` : '';
176
+ const message = `✅ Diagram ready! Open this URL to view and edit:\n\n${diagramUrl}\n\n🔗 Shareable link:\n${shareUrl}\n\n📊 ${nodeCount} nodes, ${groupCount} groups, ${saveData.edges.length} edges.${techLine}${featuresLine}${promptLine}\n\n**To export**: Use session ID "${sessionId}" with the export_diagram tool.`;
177
+ return {
178
+ success: true,
179
+ nodes: saveData.nodes,
180
+ edges: saveData.edges,
181
+ elkPositions: [],
182
+ metadata: {
183
+ nodeCount: saveData.nodes.length,
184
+ edgeCount: saveData.edges.length,
185
+ layoutAlgorithm: 'Mermaid Pipeline',
186
+ direction: input.direction || 'RIGHT',
187
+ groupCount,
188
+ },
189
+ diagramUrl,
190
+ sessionId,
191
+ shareUrl,
192
+ embeddedDiagram: { nodes: saveData.nodes, edges: saveData.edges },
193
+ message,
194
+ errors: saveData.warnings && saveData.warnings.length > 0 ? saveData.warnings : undefined,
195
+ };
196
+ }
197
+ if (!input.nodes || input.nodes.length === 0) {
198
+ return {
199
+ success: false, nodes: [], edges: [], elkPositions: [],
200
+ metadata: { nodeCount: 0, edgeCount: 0, layoutAlgorithm: 'ELK layered', direction, groupCount: 0 },
201
+ errors: ['No nodes or mermaid string provided.'],
202
+ };
203
+ }
204
+ // Check if AI used groups — warn if not
205
+ const groupNodes = input.nodes.filter((n) => n.isGroup);
206
+ if (groupNodes.length === 0) {
207
+ errors.push('WARNING: No group nodes provided. Best practice: wrap related nodes in group containers (isGroup:true) for visual clarity.');
208
+ }
209
+ // Extract tech/feature context for fidelity checks.
210
+ // If the caller did not pass an explicit techStack/customFeatures, derive
211
+ // them from the userPrompt via the shared prompt parser (single source).
212
+ const parsedPrompt = input.userPrompt ? parseUserPrompt(input.userPrompt) : null;
213
+ const techStack = input.techStack && input.techStack.length > 0
214
+ ? [...input.techStack]
215
+ : parsedPrompt?.detectedTechStack || [];
216
+ const customFeatures = input.customFeatures && input.customFeatures.length > 0
217
+ ? [...input.customFeatures]
218
+ : parsedPrompt?.customFeatures || [];
219
+ const architectureNodes = input.nodes.map((node, index) => {
220
+ const layer = node.layer || node.tier || 'compute';
221
+ const tier = assignTierFromLayer(layer);
222
+ const isGroup = node.isGroup === true;
223
+ return {
224
+ id: node.id || `node-${index}`,
225
+ type: isGroup ? 'groupNode' : 'systemNode',
226
+ label: node.label,
227
+ subtitle: node.subtitle || '',
228
+ layer: tier,
229
+ tier: tier,
230
+ tierColor: node.tierColor || TIER_COLORS[tier],
231
+ accentColor: node.accentColor,
232
+ groupColor: node.groupColor,
233
+ status: node.status,
234
+ shape: node.shape,
235
+ // Groups get larger default dimensions; clamp regular nodes to minimum
236
+ width: isGroup
237
+ ? Math.max(node.width || DEFAULT_GROUP_WIDTH, 300)
238
+ : Math.max(node.width || DEFAULT_NODE_WIDTH, 160),
239
+ height: isGroup
240
+ ? Math.max(node.height || DEFAULT_GROUP_HEIGHT, 200)
241
+ : Math.max(node.height || DEFAULT_NODE_HEIGHT, 60),
242
+ icon: node.icon || (isGroup ? 'layers' : 'box'),
243
+ metadata: { serviceType: node.serviceType },
244
+ isGroup,
245
+ parentId: node.parentId,
246
+ serviceType: node.serviceType,
247
+ };
248
+ });
249
+ // Validate node integrity
250
+ const nodeIdSet = new Set(architectureNodes.map(n => n.id));
251
+ const inputEdgeErrors = [];
252
+ const inputEdgeIds = new Set();
253
+ for (const edge of (input.edges || [])) {
254
+ if (edge.id && inputEdgeIds.has(edge.id)) {
255
+ inputEdgeErrors.push(`Duplicate edge id: ${edge.id}`);
256
+ }
257
+ else if (edge.id) {
258
+ inputEdgeIds.add(edge.id);
259
+ }
260
+ if (!nodeIdSet.has(edge.source)) {
261
+ inputEdgeErrors.push(`Edge ${edge.id || 'unknown'}: invalid source "${edge.source}"`);
262
+ }
263
+ if (!nodeIdSet.has(edge.target)) {
264
+ inputEdgeErrors.push(`Edge ${edge.id || 'unknown'}: invalid target "${edge.target}"`);
265
+ }
266
+ if (edge.source === edge.target) {
267
+ inputEdgeErrors.push(`Edge ${edge.id || 'unknown'}: source and target are the same`);
268
+ }
269
+ }
270
+ errors.push(...validateNodes(architectureNodes, techStack, customFeatures), ...inputEdgeErrors);
271
+ // ── Run edge topology validation BEFORE layout ────────────────────────────
272
+ const topologyErrors = validateEdgeTopology(architectureNodes, (input.edges || []).map(e => ({ source: e.source, target: e.target, label: e.label, id: e.id })));
273
+ if (topologyErrors.length > 0) {
274
+ errors.push(...topologyErrors);
275
+ // Return early with topology errors — do NOT render a broken diagram
276
+ return {
277
+ success: false,
278
+ nodes: [],
279
+ edges: [],
280
+ elkPositions: [],
281
+ metadata: { nodeCount: 0, edgeCount: 0, layoutAlgorithm: 'ELK layered', direction, groupCount: 0 },
282
+ errors,
283
+ message: `❌ Diagram rejected due to topology errors. Fix the following issues and call generate_diagram again:\n\n` +
284
+ topologyErrors.map((e, i) => `${i + 1}. ${e}`).join('\n\n') +
285
+ `\n\n📐 CORRECT FLOW: client → API Gateway → Services → Async → Data → External\n` +
286
+ ` Edges must flow LEFT→RIGHT through tiers. Never connect service/data nodes back to the client.`,
287
+ };
288
+ }
289
+ const architectureEdges = (input.edges || []).map((edge, index) => {
290
+ const commType = (edge.communicationType || 'sync');
291
+ const commStyle = COMM_COLORS[commType] || COMM_COLORS.sync;
292
+ // Use provided label; for async/stream/event, fall back to comm type description
293
+ const label = edge.label?.trim() || 'Connection';
294
+ return {
295
+ id: edge.id || `edge-${index}`,
296
+ source: edge.source,
297
+ target: edge.target,
298
+ communicationType: commType,
299
+ pathType: (edge.pathType ?? 'Smoothstep'),
300
+ label,
301
+ labelPosition: 'center',
302
+ animated: commStyle.animated,
303
+ style: {
304
+ stroke: commStyle.color,
305
+ strokeDasharray: commStyle.dash,
306
+ strokeWidth: 2.5,
307
+ },
308
+ markerEnd: 'arrowclosed',
309
+ markerStart: 'none',
310
+ };
311
+ });
312
+ const layoutResult = await runELKLayout(architectureNodes, architectureEdges, { direction });
313
+ const layoutValidation = validateLayout(layoutResult.nodes, layoutResult.edges);
314
+ if (!layoutValidation.valid) {
315
+ errors.push(...layoutValidation.errors);
316
+ }
317
+ let diagramUrl;
318
+ let message;
319
+ let sessionId;
320
+ const API_BASE = process.env.API_BASE_URL || 'http://localhost:3000';
321
+ try {
322
+ const label = input.label || input.nodes[0]?.label || 'AI Diagram';
323
+ const saveResponse = await fetchWithTimeout(`${API_BASE}/api/diagram/load`, {
324
+ method: 'POST',
325
+ headers: { 'Content-Type': 'application/json' },
326
+ body: JSON.stringify({
327
+ nodes: layoutResult.nodes,
328
+ edges: layoutResult.edges,
329
+ label,
330
+ source: 'mcp',
331
+ }),
332
+ });
333
+ if (saveResponse.ok) {
334
+ const saveData = await saveResponse.json();
335
+ const urlPath = saveData.url || `/editor?session=${saveData.sessionId}`;
336
+ diagramUrl = `${API_BASE}${urlPath}`;
337
+ sessionId = saveData.sessionId;
338
+ const shareUrl = `${API_BASE}/share/${sessionId}`;
339
+ const nodeCount = layoutResult.nodes.filter(n => !n.data?.isGroup).length;
340
+ const groupCount = layoutResult.nodes.filter(n => n.data?.isGroup).length;
341
+ // Build a context-aware success message
342
+ const techLine = techStack.length > 0
343
+ ? `\n\n🔧 **Tech Stack Applied**: ${techStack.join(', ')}`
344
+ : '';
345
+ const featuresLine = customFeatures.length > 0
346
+ ? `\n⚡ **Features Included**: ${customFeatures.join(', ')}`
347
+ : '';
348
+ const promptLine = input.userPrompt
349
+ ? `\n\n💬 **From prompt**: "${input.userPrompt.substring(0, 100)}${input.userPrompt.length > 100 ? '...' : ''}"` : '';
350
+ message = `✅ Diagram ready! Open this URL to view and edit:\n\n${diagramUrl}\n\n🔗 Shareable link:\n${shareUrl}\n\n📊 ${nodeCount} nodes, ${groupCount} groups, ${layoutResult.edges.length} edges.${techLine}${featuresLine}${promptLine}\n\n**To export**: Use session ID "${sessionId}" with the export_diagram tool.`;
351
+ }
352
+ }
353
+ catch {
354
+ message = `Diagram generated with ${layoutResult.nodes.length} nodes and ${layoutResult.edges.length} edges, but couldn't save to generate a link. Make sure Next.js is running on ${API_BASE}.`;
355
+ }
356
+ const groupCount = architectureNodes.filter(n => n.isGroup).length;
357
+ return {
358
+ success: true,
359
+ nodes: layoutResult.nodes,
360
+ edges: layoutResult.edges,
361
+ elkPositions: layoutResult.elkPositions,
362
+ metadata: {
363
+ nodeCount: layoutResult.nodes.length,
364
+ edgeCount: layoutResult.edges.length,
365
+ layoutAlgorithm: 'ELK layered',
366
+ direction,
367
+ groupCount,
368
+ },
369
+ diagramUrl,
370
+ sessionId,
371
+ shareUrl: sessionId ? `${API_BASE}/share/${sessionId}` : undefined,
372
+ embeddedDiagram: { nodes: layoutResult.nodes, edges: layoutResult.edges },
373
+ message,
374
+ errors: errors.length > 0 ? errors : undefined,
375
+ };
376
+ }
377
+ catch (error) {
378
+ return {
379
+ success: false, nodes: [], edges: [], elkPositions: [],
380
+ metadata: { nodeCount: 0, edgeCount: 0, layoutAlgorithm: 'ELK layered', direction: input.direction, groupCount: 0 },
381
+ errors: [error instanceof Error ? error.message : 'Unknown error'],
382
+ };
383
+ }
384
+ }
385
+ //# sourceMappingURL=generate-diagram.js.map