coderio 0.1.0-alpha.9 → 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.
package/dist/index.js CHANGED
@@ -11,10 +11,6 @@ var __name = (target, value) => __defProp(target, "name", { value, configurable:
11
11
  var __esm = (fn, res) => function __init() {
12
12
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
13
  };
14
- var __export = (target, all) => {
15
- for (var name in all)
16
- __defProp(target, name, { get: all[name], enumerable: true });
17
- };
18
14
  var __decoratorStart = (base) => [, , , __create(base?.[__knownSymbol("metadata")] ?? null)];
19
15
  var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
20
16
  var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
@@ -25,7 +21,7 @@ var __runInitializers = (array, flags, self, value) => {
25
21
  return value;
26
22
  };
27
23
  var __decorateElement = (array, flags, name, decorators, target, extra) => {
28
- var fn, it, done, ctx, access2, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
24
+ var fn, it, done, ctx, access, k = flags & 7, s = !!(flags & 8), p = !!(flags & 16);
29
25
  var j = k > 3 ? array.length + 1 : k ? s ? 1 : 2 : 0, key = __decoratorStrings[k + 5];
30
26
  var initializers = k > 3 && (array[j - 1] = []), extraInitializers = array[j] || (array[j] = []);
31
27
  var desc = k && (!p && !s && (target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(k < 4 ? target : { get [name]() {
@@ -37,9 +33,9 @@ var __decorateElement = (array, flags, name, decorators, target, extra) => {
37
33
  for (var i = decorators.length - 1; i >= 0; i--) {
38
34
  ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
39
35
  if (k) {
40
- ctx.static = s, ctx.private = p, access2 = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
41
- if (k ^ 3) access2.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
42
- if (k > 2) access2.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
36
+ ctx.static = s, ctx.private = p, access = ctx.access = { has: p ? (x) => __privateIn(target, x) : (x) => name in x };
37
+ if (k ^ 3) access.get = p ? (x) => (k ^ 1 ? __privateGet : __privateMethod)(x, target, k ^ 4 ? extra : desc.get) : (x) => x[name];
38
+ if (k > 2) access.set = p ? (x, y) => __privateSet(x, target, y, k ^ 4 ? extra : desc.set) : (x, y) => x[name] = y;
43
39
  }
44
40
  it = (0, decorators[i])(k ? k < 4 ? p ? extra : desc[key] : k > 4 ? void 0 : { get: desc.get, set: desc.set } : target, ctx), done._ = 1;
45
41
  if (k ^ 4 || it === void 0) __expectFn(it) && (k > 4 ? initializers.unshift(it) : k ? p ? extra = it : desc[key] = it : target = it);
@@ -54,74 +50,7 @@ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read fr
54
50
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
55
51
  var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
56
52
 
57
- // src/utils/logger.ts
58
- import chalk from "chalk";
59
- function getTimestamp() {
60
- const now = /* @__PURE__ */ new Date();
61
- const year = now.getFullYear();
62
- const month = String(now.getMonth() + 1).padStart(2, "0");
63
- const day = String(now.getDate()).padStart(2, "0");
64
- const hours = String(now.getHours()).padStart(2, "0");
65
- const minutes = String(now.getMinutes()).padStart(2, "0");
66
- const seconds = String(now.getSeconds()).padStart(2, "0");
67
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
68
- }
69
- var logger;
70
- var init_logger = __esm({
71
- "src/utils/logger.ts"() {
72
- "use strict";
73
- logger = {
74
- /**
75
- * Print standard log (alias for info logs).
76
- *
77
- * Many subsystems (e.g. validation) use `printLog()` as the default logging method.
78
- */
79
- printLog(message) {
80
- console.log(message);
81
- },
82
- /**
83
- * Print info log in blue
84
- */
85
- printInfoLog(message) {
86
- console.log(chalk.blue(`[${getTimestamp()}] [INFO] ${message}`));
87
- },
88
- /**
89
- * Print warning log in yellow
90
- */
91
- printWarnLog(message) {
92
- console.warn(chalk.yellow(`[${getTimestamp()}] [WARNING] ${message}`));
93
- },
94
- /**
95
- * Print error log in red
96
- */
97
- printErrorLog(message) {
98
- console.error(chalk.red(`[${getTimestamp()}] [ERROR] \u2716 ${message}`));
99
- },
100
- /**
101
- * Print test/debug log in gray
102
- * Only logs in development/test environments
103
- */
104
- printTestLog(message) {
105
- if (process.env.NODE_ENV === "development") {
106
- console.log(chalk.gray(`[${getTimestamp()}] [DEBUG] ${message}`));
107
- }
108
- },
109
- /**
110
- * Print success log in green
111
- */
112
- printSuccessLog(message) {
113
- console.log(chalk.green(`[${getTimestamp()}] [SUCCESS] \u2714 ${message}`));
114
- }
115
- };
116
- }
117
- });
118
-
119
53
  // src/nodes/process/structure/prompt.ts
120
- var prompt_exports = {};
121
- __export(prompt_exports, {
122
- extractDataListPrompt: () => extractDataListPrompt,
123
- generateStructurePrompt: () => generateStructurePrompt
124
- });
125
54
  var generateStructurePrompt, extractDataListPrompt;
126
55
  var init_prompt = __esm({
127
56
  "src/nodes/process/structure/prompt.ts"() {
@@ -426,47 +355,6 @@ Example Output JSON Structure (for reference only):
426
355
  }
427
356
  });
428
357
 
429
- // src/tools/position-tool/utils/fetch-thumbnail-dimensions.ts
430
- var fetch_thumbnail_dimensions_exports = {};
431
- __export(fetch_thumbnail_dimensions_exports, {
432
- fetchThumbnailDimensions: () => fetchThumbnailDimensions
433
- });
434
- import axios3 from "axios";
435
- import sharp4 from "sharp";
436
- async function fetchThumbnailDimensions(figmaThumbnailUrl) {
437
- if (!figmaThumbnailUrl) {
438
- return void 0;
439
- }
440
- try {
441
- const response = await axios3.get(figmaThumbnailUrl, {
442
- responseType: "arraybuffer",
443
- timeout: 3e4
444
- });
445
- const imageBuffer = Buffer.from(response.data);
446
- const metadata = await sharp4(imageBuffer).metadata();
447
- if (!metadata.width || !metadata.height) {
448
- return void 0;
449
- }
450
- return {
451
- width: metadata.width,
452
- height: metadata.height
453
- };
454
- } catch (error) {
455
- const errorMsg = error instanceof Error ? error.message : String(error);
456
- logger.printWarnLog(`Failed to fetch Figma thumbnail: ${errorMsg}`);
457
- return void 0;
458
- }
459
- }
460
- var init_fetch_thumbnail_dimensions = __esm({
461
- "src/tools/position-tool/utils/fetch-thumbnail-dimensions.ts"() {
462
- "use strict";
463
- init_logger();
464
- }
465
- });
466
-
467
- // src/graph.ts
468
- import { StateGraph, START, END } from "@langchain/langgraph";
469
-
470
358
  // src/types/graph-types.ts
471
359
  var GraphNode = /* @__PURE__ */ ((GraphNode2) => {
472
360
  GraphNode2["INITIAL"] = "initial";
@@ -476,20 +364,23 @@ var GraphNode = /* @__PURE__ */ ((GraphNode2) => {
476
364
  GraphNode2["CODE"] = "code";
477
365
  return GraphNode2;
478
366
  })(GraphNode || {});
479
-
480
- // src/state.ts
481
- import { Annotation, MessagesAnnotation } from "@langchain/langgraph";
482
- var GraphStateAnnotation = Annotation.Root({
483
- ...MessagesAnnotation.spec,
484
- urlInfo: Annotation(),
485
- workspace: Annotation(),
486
- figmaInfo: Annotation(),
487
- protocol: Annotation(),
488
- config: Annotation()
489
- });
490
-
491
- // src/agents/initial-agent/index.ts
492
- import { Agent } from "evoltagent";
367
+ var ValidationMode = /* @__PURE__ */ ((ValidationMode2) => {
368
+ ValidationMode2["CodeOnly"] = "codeOnly";
369
+ ValidationMode2["ReportOnly"] = "reportOnly";
370
+ ValidationMode2["Full"] = "full";
371
+ return ValidationMode2;
372
+ })(ValidationMode || {});
373
+
374
+ // src/types/figma-types.ts
375
+ var FigmaImageFormat = /* @__PURE__ */ ((FigmaImageFormat2) => {
376
+ FigmaImageFormat2["PNG"] = "png";
377
+ FigmaImageFormat2["JPG"] = "jpg";
378
+ FigmaImageFormat2["SVG"] = "svg";
379
+ FigmaImageFormat2["PDF"] = "pdf";
380
+ FigmaImageFormat2["EPS"] = "eps";
381
+ FigmaImageFormat2["WEBP"] = "webp";
382
+ return FigmaImageFormat2;
383
+ })(FigmaImageFormat || {});
493
384
 
494
385
  // src/agents/initial-agent/prompt.ts
495
386
  var INITIAL_AGENT_SYSTEM_PROMPT = `
@@ -522,6 +413,7 @@ var INITIAL_AGENT_SYSTEM_PROMPT = `
522
413
  - "dev": "pnpm exec vite"
523
414
  - "build": "pnpm exec tsc && pnpm exec vite build"
524
415
  - "preview": "pnpm exec vite preview"
416
+ * IMPORTANT: Do NOT add "coderio" as a dependency - it's only a build tool, not a runtime dependency.
525
417
  - \`vite.config.ts\`: Configure React and TailwindCSS V4 plugins. MUST include path alias configuration:
526
418
  * Add \`resolve.alias\` with \`@\` pointing to \`path.resolve(__dirname, './src')\`
527
419
  * Import \`path\` from 'node:path'
@@ -548,23 +440,46 @@ var INITIAL_AGENT_SYSTEM_PROMPT = `
548
440
  </system_instructions>
549
441
  `.trim();
550
442
 
551
- // src/agents/initial-agent/index.ts
552
- function createInitialAgent(modelConfig) {
553
- const systemTools = ["ThinkTool.execute", "FileEditor.read", "FileEditor.write"];
554
- return new Agent({
555
- name: "InitialAgent",
556
- profile: "Expert Frontend Engineer specialized in project scaffolding with React, TypeScript, and Tailwind CSS V4.",
557
- system: INITIAL_AGENT_SYSTEM_PROMPT,
558
- tools: systemTools,
559
- modelConfig,
560
- verbose: true
561
- });
443
+ // src/agents/initial-agent/instruction.ts
444
+ function initialAgentInstruction(params) {
445
+ return `
446
+ appPath: ${params.appPath}
447
+ appName: ${params.appName}
448
+
449
+ TASK:
450
+ Scaffold a clean React V18 + TS + Vite + TailwindCSS V4 + Less project.
451
+ `.trim();
562
452
  }
563
453
 
564
- // src/nodes/initial/index.ts
565
- init_logger();
566
- import fs from "fs";
567
- import path from "path";
454
+ // src/utils/url-parser.ts
455
+ var parseFigmaUrl = (url) => {
456
+ let fileId = null;
457
+ let name = "untitled";
458
+ let nodeId = null;
459
+ try {
460
+ const urlObj = new URL(decodeURIComponent(url));
461
+ const pathParts = urlObj.pathname.split("/").filter(Boolean);
462
+ if (pathParts.length >= 3) {
463
+ fileId = pathParts[pathParts.length - 2] || null;
464
+ const fileName = pathParts[pathParts.length - 1];
465
+ name = fileName ? encodeURI(fileName).toLowerCase() : "untitled";
466
+ name = name.length > 20 ? name.substring(0, 20) : name;
467
+ }
468
+ nodeId = urlObj.searchParams.get("node-id") || null;
469
+ nodeId = nodeId ? nodeId.replace(/-/g, ":") : null;
470
+ } catch {
471
+ }
472
+ if (!fileId || !nodeId) {
473
+ throw new Error("Invalid Figma URL");
474
+ }
475
+ return { fileId, name, nodeId, projectName: `${name}_${nodeId}` };
476
+ };
477
+
478
+ // src/tools/figma-tool/index.ts
479
+ import { tools as tools2 } from "evoltagent";
480
+
481
+ // src/utils/axios.ts
482
+ import axios from "axios";
568
483
 
569
484
  // src/utils/config.ts
570
485
  import { readFileSync, existsSync } from "fs";
@@ -597,13 +512,6 @@ Please create the configuration file.`);
597
512
  throw error;
598
513
  }
599
514
  }
600
- function getModelConfig() {
601
- const config = loadConfig();
602
- if (!config.model) {
603
- throw new Error("Model configuration not found in config.yaml");
604
- }
605
- return config.model;
606
- }
607
515
  function getFigmaConfig() {
608
516
  const config = loadConfig();
609
517
  if (!config.figma) {
@@ -616,71 +524,80 @@ function getDebugConfig() {
616
524
  return config.debug || { enabled: false };
617
525
  }
618
526
 
619
- // src/constants/index.ts
620
- var MAX_OUTPUT_TOKENS = 20480;
621
- var AGENT_CONTEXT_WINDOW_TOKENS = 128e3;
622
-
623
- // src/agents/initial-agent/instruction.ts
624
- function initialAgentInstruction(params) {
625
- return `
626
- appPath: ${params.appPath}
627
- appName: ${params.appName}
527
+ // src/utils/file.ts
528
+ import fs from "fs";
529
+ import path from "path";
628
530
 
629
- TASK:
630
- Scaffold a clean React V18 + TS + Vite + TailwindCSS V4 + Less project.
631
- `.trim();
531
+ // src/utils/logger.ts
532
+ import chalk from "chalk";
533
+ function getTimestamp() {
534
+ const now = /* @__PURE__ */ new Date();
535
+ const year = now.getFullYear();
536
+ const month = String(now.getMonth() + 1).padStart(2, "0");
537
+ const day = String(now.getDate()).padStart(2, "0");
538
+ const hours = String(now.getHours()).padStart(2, "0");
539
+ const minutes = String(now.getMinutes()).padStart(2, "0");
540
+ const seconds = String(now.getSeconds()).padStart(2, "0");
541
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
632
542
  }
633
-
634
- // src/nodes/initial/index.ts
635
- var initialProject = async (state) => {
636
- logger.printInfoLog("Initializing project...");
637
- const envConfig = getModelConfig();
638
- const modelConfig = {
639
- ...envConfig,
640
- contextWindowTokens: AGENT_CONTEXT_WINDOW_TOKENS,
641
- maxOutputTokens: MAX_OUTPUT_TOKENS
642
- };
643
- const appPath = state.workspace.app;
644
- const appName = state.urlInfo.name || "";
645
- if (!appPath) {
646
- throw new Error("Workspace application path is not defined.");
647
- }
648
- const initialAgent = createInitialAgent(modelConfig);
649
- const result = await initialAgent.run(initialAgentInstruction({ appPath, appName }));
650
- const essentialFiles = ["package.json", "src", "vite.config.ts", "tsconfig.json", "index.html", "src/main.tsx", "src/App.tsx"];
651
- for (const file of essentialFiles) {
652
- const fullPath = path.join(appPath, file);
653
- if (!fs.existsSync(fullPath)) {
654
- throw new Error(`Critical file/directory missing: "${file}". The project scaffolding may have failed.`);
543
+ var logger = {
544
+ /**
545
+ * Print standard log (alias for info logs).
546
+ *
547
+ * Many subsystems (e.g. validation) use `printLog()` as the default logging method.
548
+ */
549
+ printLog(message) {
550
+ console.log(message);
551
+ },
552
+ /**
553
+ * Print info log in blue
554
+ */
555
+ printInfoLog(message) {
556
+ console.log(chalk.blue(`[${getTimestamp()}] [INFO] ${message}`));
557
+ },
558
+ /**
559
+ * Print warning log in yellow
560
+ */
561
+ printWarnLog(message) {
562
+ console.warn(chalk.yellow(`[${getTimestamp()}] [WARNING] ${message}`));
563
+ },
564
+ /**
565
+ * Print error log in red
566
+ */
567
+ printErrorLog(message) {
568
+ console.error(chalk.red(`[${getTimestamp()}] [ERROR] \u2716 ${message}`));
569
+ },
570
+ /**
571
+ * Print test/debug log in gray
572
+ * Only logs in development/test environments
573
+ */
574
+ printTestLog(message) {
575
+ if (process.env.NODE_ENV === "development") {
576
+ console.log(chalk.gray(`[${getTimestamp()}] [DEBUG] ${message}`));
655
577
  }
578
+ },
579
+ /**
580
+ * Print success log in green
581
+ */
582
+ printSuccessLog(message) {
583
+ console.log(chalk.green(`[${getTimestamp()}] [SUCCESS] \u2714 ${message}`));
656
584
  }
657
- logger.printSuccessLog(result);
658
- return {};
659
585
  };
660
586
 
661
- // src/tools/figma-tool/index.ts
662
- import { tools as tools2 } from "evoltagent";
663
-
664
- // src/utils/axios.ts
665
- import axios from "axios";
666
-
667
587
  // src/utils/file.ts
668
- init_logger();
669
- import fs2 from "fs";
670
- import path2 from "path";
671
588
  var writeFile = (folderPath, filePath, content) => {
672
589
  if (!folderPath || !filePath || !content) {
673
590
  return;
674
591
  }
675
- if (!fs2.existsSync(folderPath)) {
676
- fs2.mkdirSync(folderPath, { recursive: true });
592
+ if (!fs.existsSync(folderPath)) {
593
+ fs.mkdirSync(folderPath, { recursive: true });
677
594
  }
678
- fs2.writeFileSync(path2.join(folderPath, filePath), content);
595
+ fs.writeFileSync(path.join(folderPath, filePath), content);
679
596
  };
680
597
  function createFiles({ files, filePath }) {
681
598
  try {
682
599
  for (const file of files) {
683
- const dirPath = path2.dirname(filePath);
600
+ const dirPath = path.dirname(filePath);
684
601
  writeFile(dirPath, file.filename, file.content);
685
602
  }
686
603
  } catch (error) {
@@ -690,9 +607,8 @@ function createFiles({ files, filePath }) {
690
607
  }
691
608
 
692
609
  // src/utils/workspace.ts
693
- init_logger();
694
- import path3 from "path";
695
- import fs3 from "fs";
610
+ import path2 from "path";
611
+ import fs2 from "fs";
696
612
  var Workspace = class {
697
613
  path = null;
698
614
  initWorkspace(subPath, rootPath, appName) {
@@ -700,21 +616,21 @@ var Workspace = class {
700
616
  return this.path;
701
617
  }
702
618
  const root = rootPath || (process.env.CODERIO_CLI_USER_CWD ?? process.cwd());
703
- const coderioRoot = path3.join(root, "coderio");
704
- const finalRoot = path3.resolve(coderioRoot, subPath);
619
+ const coderioRoot = path2.join(root, "coderio");
620
+ const finalRoot = path2.resolve(coderioRoot, subPath);
705
621
  const app = appName || "my-app";
706
- const absoluteRoot = path3.resolve(finalRoot);
707
- const processDir = path3.join(absoluteRoot, "process");
708
- const checkpointDir = path3.join(absoluteRoot, "checkpoint");
709
- const debugDir = path3.join(absoluteRoot, "debug");
622
+ const absoluteRoot = path2.resolve(finalRoot);
623
+ const processDir = path2.join(absoluteRoot, "process");
624
+ const checkpointDir = path2.join(absoluteRoot, "checkpoint");
625
+ const debugDir = path2.join(absoluteRoot, "debug");
710
626
  this.path = {
711
627
  root: absoluteRoot,
712
- app: path3.join(absoluteRoot, app),
628
+ app: path2.join(absoluteRoot, app),
713
629
  process: processDir,
714
630
  debug: debugDir,
715
- reports: path3.join(absoluteRoot, "reports.html"),
716
- db: path3.join(checkpointDir, "coderio-cli.db"),
717
- checkpoint: path3.join(checkpointDir, "checkpoint.json")
631
+ reports: path2.join(absoluteRoot, "reports.html"),
632
+ db: path2.join(checkpointDir, "coderio-cli.db"),
633
+ checkpoint: path2.join(checkpointDir, "checkpoint.json")
718
634
  };
719
635
  return this.path;
720
636
  }
@@ -723,11 +639,11 @@ var Workspace = class {
723
639
  */
724
640
  deleteWorkspace(workspace) {
725
641
  try {
726
- if (fs3.existsSync(workspace.root)) {
727
- const entries = fs3.readdirSync(workspace.root);
642
+ if (fs2.existsSync(workspace.root)) {
643
+ const entries = fs2.readdirSync(workspace.root);
728
644
  for (const entry of entries) {
729
- const fullPath = path3.join(workspace.root, entry);
730
- fs3.rmSync(fullPath, { recursive: true, force: true });
645
+ const fullPath = path2.join(workspace.root, entry);
646
+ fs2.rmSync(fullPath, { recursive: true, force: true });
731
647
  }
732
648
  }
733
649
  } catch (error) {
@@ -742,7 +658,7 @@ var Workspace = class {
742
658
  * @returns The absolute path to the source code directory
743
659
  */
744
660
  resolveAppSrc(paths, srcSubPath) {
745
- return path3.join(paths.app, "src", srcSubPath);
661
+ return path2.join(paths.app, "src", srcSubPath);
746
662
  }
747
663
  /**
748
664
  * Resolve component alias path to absolute filesystem path.
@@ -797,7 +713,6 @@ async function get(url, config) {
797
713
  }
798
714
 
799
715
  // src/tools/figma-tool/figma.ts
800
- init_logger();
801
716
  var fetchFigmaNode = async (fileId, nodeId, token) => {
802
717
  const url = `https://api.figma.com/v1/files/${fileId}/nodes?ids=${nodeId}`;
803
718
  try {
@@ -851,8 +766,8 @@ var checkBorder = (node) => {
851
766
  };
852
767
 
853
768
  // src/tools/figma-tool/images.ts
854
- import fs4 from "fs";
855
- import path4 from "path";
769
+ import fs3 from "fs";
770
+ import path3 from "path";
856
771
  import axios2 from "axios";
857
772
 
858
773
  // src/utils/promise-pool.ts
@@ -894,7 +809,6 @@ var BASE_RETRY_DELAY_MS = 1e3;
894
809
  var DEFAULT_DOWNLOAD_CONCURRENCY = 5;
895
810
 
896
811
  // src/tools/figma-tool/images.ts
897
- init_logger();
898
812
  var fetchImages = async (nodes, fileId, token) => {
899
813
  if (!fileId || !nodes?.length) {
900
814
  return [];
@@ -1072,11 +986,11 @@ async function downloadImage(url, filename, imageDir, base64) {
1072
986
  if (!imageDir || !filename) {
1073
987
  return "";
1074
988
  }
1075
- if (!fs4.existsSync(imageDir)) {
1076
- fs4.mkdirSync(imageDir, { recursive: true });
989
+ if (!fs3.existsSync(imageDir)) {
990
+ fs3.mkdirSync(imageDir, { recursive: true });
1077
991
  }
1078
- const filepath = path4.join(imageDir, filename);
1079
- fs4.writeFileSync(filepath, Buffer.from(response.data));
992
+ const filepath = path3.join(imageDir, filename);
993
+ fs3.writeFileSync(filepath, Buffer.from(response.data));
1080
994
  return filepath;
1081
995
  }
1082
996
  } catch (error) {
@@ -1535,21 +1449,21 @@ var FigmaTool = class {
1535
1449
  if (!fileId || !nodeId || !token) {
1536
1450
  return void 0;
1537
1451
  }
1538
- const document2 = await fetchFigmaNode(fileId, nodeId, token);
1539
- if (!document2 || !document2?.children?.length) {
1452
+ const document = await fetchFigmaNode(fileId, nodeId, token);
1453
+ if (!document || !document?.children?.length) {
1540
1454
  return void 0;
1541
1455
  }
1542
1456
  const images = await fetchFigmaImages(fileId, nodeId, token);
1543
1457
  const thumbnail = images?.[nodeId] || "";
1544
- document2.thumbnailUrl = thumbnail;
1545
- const cleanedDocument = cleanFigma(document2);
1458
+ document.thumbnailUrl = thumbnail;
1459
+ const cleanedDocument = cleanFigma(document);
1546
1460
  return cleanedDocument;
1547
1461
  }
1548
- async downloadImages(fileId, token, imageDir, document2) {
1462
+ async downloadImages(fileId, token, imageDir, document) {
1549
1463
  if (!fileId) {
1550
1464
  return { successCount: 0, failCount: 0, imageNodesMap: /* @__PURE__ */ new Map() };
1551
1465
  }
1552
- const imageNodes = findImageNodes(document2?.children || [], document2?.absoluteBoundingBox);
1466
+ const imageNodes = findImageNodes(document?.children || [], document?.absoluteBoundingBox);
1553
1467
  const fetchedImages = await fetchImages(imageNodes, fileId, token);
1554
1468
  if (!fetchedImages.length) {
1555
1469
  return { successCount: 0, failCount: 0, imageNodesMap: /* @__PURE__ */ new Map() };
@@ -1596,101 +1510,17 @@ FigmaTool = __decorateElement(_init2, 0, "FigmaTool", _FigmaTool_decorators, Fig
1596
1510
  __runInitializers(_init2, 1, FigmaTool);
1597
1511
  var figmaTool = new FigmaTool();
1598
1512
 
1599
- // src/nodes/process/index.ts
1600
- init_logger();
1601
-
1602
1513
  // src/utils/call-model.ts
1603
- init_logger();
1604
1514
  import { HumanMessage } from "@langchain/core/messages";
1605
1515
  import { ChatOpenAI } from "@langchain/openai";
1606
- function validateModelConfig(config) {
1607
- if (!config || !config.provider || !config.model || !config.baseUrl || !config.apiKey) {
1608
- throw new Error(
1609
- `Invalid model configuration. Required fields: provider, model, baseUrl, apiKey. Please check your config.yaml file.`
1610
- );
1611
- }
1612
- }
1613
- async function callModel(options) {
1614
- const { question, imageUrls, streaming = false, responseFormat, maxTokens } = options;
1615
- if (!question || !question.trim()) {
1616
- throw new Error("Question must be a non-empty string");
1617
- }
1618
- let config;
1619
- try {
1620
- config = getModelConfig();
1621
- validateModelConfig(config);
1622
- } catch (error) {
1623
- if (error instanceof Error) {
1624
- logger.printErrorLog(`Configuration error: ${error.message}`);
1625
- }
1626
- throw error;
1627
- }
1628
- try {
1629
- const requestConfig = {
1630
- modelName: config.model,
1631
- apiKey: config.apiKey,
1632
- configuration: {
1633
- baseURL: config.baseUrl
1634
- },
1635
- ...maxTokens && { maxTokens },
1636
- temperature: 0.1,
1637
- streaming,
1638
- ...streaming && {
1639
- streamingOptions: {
1640
- includeUsage: true
1641
- }
1642
- },
1643
- ...!streaming && { streamUsage: true },
1644
- ...responseFormat && { modelKwargs: { response_format: responseFormat } }
1645
- };
1646
- const agentModel = new ChatOpenAI(requestConfig);
1647
- const contentParts = [];
1648
- contentParts.push({ type: "text", text: question });
1649
- if (imageUrls) {
1650
- const urls = Array.isArray(imageUrls) ? imageUrls : [imageUrls];
1651
- for (const url of urls) {
1652
- if (url && typeof url === "string" && url.trim()) {
1653
- contentParts.push({ type: "image_url", image_url: { url: url.trim() } });
1654
- }
1655
- }
1656
- }
1657
- const userMessage = new HumanMessage({
1658
- content: contentParts.length > 1 ? contentParts : question
1659
- });
1660
- const message = await agentModel.invoke([userMessage]);
1661
- if (!message.text) {
1662
- throw new Error("Model returned empty response");
1663
- }
1664
- const debugConfig = getDebugConfig();
1665
- const isDebugEnabled = debugConfig.enabled;
1666
- if (isDebugEnabled) {
1667
- const debugContent = [
1668
- "------------config------------",
1669
- JSON.stringify(requestConfig, null, 2),
1670
- "------------request------------",
1671
- JSON.stringify(contentParts, null, 2),
1672
- "------------response------------",
1673
- JSON.stringify(message.text, null, 2)
1674
- ].join("\n");
1675
- writeFile(workspaceManager.path?.debug ?? "", `model_${(/* @__PURE__ */ new Date()).toISOString()}.md`, debugContent);
1676
- }
1677
- return message.text;
1678
- } catch (error) {
1679
- if (error instanceof Error) {
1680
- logger.printErrorLog(`[${config.model}] Error details: ${error.message}`);
1681
- if (error.stack) {
1682
- logger.printTestLog(`[${config.model}] Stack trace: ${error.stack}`);
1683
- }
1684
- }
1685
- throw new Error(`${config.model} model request failed: ${error instanceof Error ? error.message : "Unknown error"}`);
1686
- }
1687
- }
1688
1516
 
1689
1517
  // src/nodes/process/structure/index.ts
1690
- init_logger();
1691
1518
  init_prompt();
1692
1519
 
1693
1520
  // src/utils/naming.ts
1521
+ function toPascalCase(str) {
1522
+ return str.replace(/[^a-zA-Z0-9]+/g, " ").trim().split(/\s+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
1523
+ }
1694
1524
  function toKebabCase(str) {
1695
1525
  return str.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[^a-zA-Z0-9]+/g, "-").toLowerCase().replace(/^-+|-+$/g, "");
1696
1526
  }
@@ -1732,7 +1562,6 @@ function extractFiles(content) {
1732
1562
  }
1733
1563
 
1734
1564
  // src/nodes/process/structure/utils.ts
1735
- init_logger();
1736
1565
  function simplifyFigmaNodeForContent(node) {
1737
1566
  const simple = {
1738
1567
  id: node.id,
@@ -1825,7 +1654,7 @@ function extractHierarchicalNodesByIds(tree, idList, options) {
1825
1654
  }
1826
1655
  return false;
1827
1656
  };
1828
- const processNode2 = (node) => {
1657
+ const processNode = (node) => {
1829
1658
  if (!hasDescendantInList(node)) {
1830
1659
  return [];
1831
1660
  }
@@ -1835,7 +1664,7 @@ function extractHierarchicalNodesByIds(tree, idList, options) {
1835
1664
  const filteredChildren = [];
1836
1665
  for (const child of node.children) {
1837
1666
  if (typeof child === "object" && child !== null && "id" in child) {
1838
- const processedChildren = processNode2(child);
1667
+ const processedChildren = processNode(child);
1839
1668
  filteredChildren.push(...processedChildren);
1840
1669
  }
1841
1670
  }
@@ -1849,7 +1678,7 @@ function extractHierarchicalNodesByIds(tree, idList, options) {
1849
1678
  if (node.children && Array.isArray(node.children)) {
1850
1679
  for (const child of node.children) {
1851
1680
  if (typeof child === "object" && child !== null && "id" in child) {
1852
- const processedChildren = processNode2(child);
1681
+ const processedChildren = processNode(child);
1853
1682
  matchingDescendants.push(...processedChildren);
1854
1683
  }
1855
1684
  }
@@ -1859,7 +1688,7 @@ function extractHierarchicalNodesByIds(tree, idList, options) {
1859
1688
  };
1860
1689
  const nodeArray = Array.isArray(tree) ? tree : [tree];
1861
1690
  for (const node of nodeArray) {
1862
- const processedNodes = processNode2(node);
1691
+ const processedNodes = processNode(node);
1863
1692
  result.push(...processedNodes);
1864
1693
  }
1865
1694
  return result;
@@ -1929,8 +1758,8 @@ function postProcessStructure(structure, frames) {
1929
1758
  traverse(node, void 0, 0);
1930
1759
  });
1931
1760
  }
1932
- async function populateComponentProps(node, frames, thumbnailUrl) {
1933
- if (!node || !node.children || node.children.length === 0) return;
1761
+ function extractComponentGroups(node) {
1762
+ if (!node || !node.children || node.children.length === 0) return /* @__PURE__ */ new Map();
1934
1763
  const componentGroups = /* @__PURE__ */ new Map();
1935
1764
  const validChildren = node.children.filter((c) => c && c.data);
1936
1765
  validChildren.forEach((child) => {
@@ -1942,132 +1771,47 @@ async function populateComponentProps(node, frames, thumbnailUrl) {
1942
1771
  componentGroups.get(name).push(child);
1943
1772
  }
1944
1773
  });
1945
- for (const [compName, group] of componentGroups) {
1946
- if (group.length === 0) continue;
1947
- const isList = group.length > 1;
1948
- const allElements = group.flatMap((g) => g.data.elements || []);
1949
- const simplifiedNodes = allElements.filter((n) => typeof n === "object" && n !== null).map((n) => simplifyFigmaNodeForContent(n));
1950
- const figmaDataJson = JSON.stringify(simplifiedNodes);
1951
- const containerName = node.data.name || "Container";
1952
- try {
1953
- const { extractDataListPrompt: extractDataListPrompt2 } = await Promise.resolve().then(() => (init_prompt(), prompt_exports));
1954
- const prompt = extractDataListPrompt2({
1955
- containerName,
1956
- childComponentName: compName,
1957
- figmaData: figmaDataJson
1958
- });
1959
- const result = await callModel({
1960
- question: prompt,
1961
- imageUrls: thumbnailUrl,
1962
- responseFormat: { type: "json_object" }
1774
+ return componentGroups;
1775
+ }
1776
+ function applyPropsAndStateToProtocol(parsed, node, compName, group, isList) {
1777
+ if (parsed && parsed.state && Array.isArray(parsed.state)) {
1778
+ if (isList) {
1779
+ if (!node.data.states) {
1780
+ node.data.states = [];
1781
+ }
1782
+ node.data.states.push({
1783
+ state: parsed.state,
1784
+ componentName: compName,
1785
+ componentPath: group[0]?.data.componentPath || ""
1963
1786
  });
1964
- const json = extractJSON(result);
1965
- const parsed = JSON.parse(json);
1966
- if (parsed && parsed.state && Array.isArray(parsed.state)) {
1967
- if (isList) {
1968
- if (!node.data.states) {
1969
- node.data.states = [];
1970
- }
1971
- node.data.states.push({
1972
- state: parsed.state,
1973
- componentName: compName,
1974
- componentPath: group[0]?.data.componentPath || ""
1975
- });
1976
- const originalChildren = node.children || [];
1977
- const newChildren = [];
1978
- const processedComponentNames = /* @__PURE__ */ new Set();
1979
- for (const child of originalChildren) {
1980
- const childName = child.data.componentName;
1981
- if (childName === compName) {
1982
- if (!processedComponentNames.has(childName)) {
1983
- child.data.name = childName;
1984
- child.id = childName;
1985
- const cleanKebabName = toKebabCase(childName);
1986
- child.data.kebabName = cleanKebabName;
1987
- delete child.data.path;
1988
- if (parsed.props && Array.isArray(parsed.props)) {
1989
- child.data.props = parsed.props;
1990
- }
1991
- newChildren.push(child);
1992
- processedComponentNames.add(childName);
1993
- }
1994
- } else {
1995
- newChildren.push(child);
1787
+ const originalChildren = node.children || [];
1788
+ const newChildren = [];
1789
+ const processedComponentNames = /* @__PURE__ */ new Set();
1790
+ for (const child of originalChildren) {
1791
+ const childName = child.data.componentName;
1792
+ if (childName === compName) {
1793
+ if (!processedComponentNames.has(childName)) {
1794
+ child.data.name = childName;
1795
+ child.id = childName;
1796
+ const cleanKebabName = toKebabCase(childName);
1797
+ child.data.kebabName = cleanKebabName;
1798
+ delete child.data.path;
1799
+ if (parsed.props && Array.isArray(parsed.props)) {
1800
+ child.data.props = parsed.props;
1996
1801
  }
1802
+ newChildren.push(child);
1803
+ processedComponentNames.add(childName);
1997
1804
  }
1998
- node.children = newChildren;
1805
+ } else {
1806
+ newChildren.push(child);
1999
1807
  }
2000
1808
  }
2001
- } catch (e) {
2002
- logger.printErrorLog(
2003
- `Failed to extract data list for ${compName} in ${containerName}: ${e instanceof Error ? e.message : String(e)}`
2004
- );
1809
+ node.children = newChildren;
2005
1810
  }
2006
1811
  }
2007
- for (const child of node.children) {
2008
- await populateComponentProps(child, frames, thumbnailUrl);
2009
- }
2010
1812
  }
2011
1813
 
2012
- // src/nodes/process/structure/index.ts
2013
- var generateStructure = async (figma) => {
2014
- const frames = figma.frames || figma.children;
2015
- const imageWidth = figma.absoluteBoundingBox?.width;
2016
- const thumbnailUrl = figma.thumbnailUrl;
2017
- if (!frames || frames.length === 0) {
2018
- logger.printErrorLog("No processed frames found in state");
2019
- throw new Error("No processed frames found");
2020
- }
2021
- logger.printInfoLog("Starting structure analysis...");
2022
- try {
2023
- const positions = extractNodePositionsHierarchical(frames);
2024
- const positionsJson = JSON.stringify(positions);
2025
- const prompt = generateStructurePrompt({
2026
- positions: positionsJson,
2027
- width: imageWidth ? String(imageWidth) : "1440"
2028
- });
2029
- logger.printInfoLog("Calling AI model to generate component structure...");
2030
- const structureResult = await callModel({
2031
- question: prompt,
2032
- imageUrls: thumbnailUrl,
2033
- responseFormat: { type: "json_object" },
2034
- maxTokens: 20240
2035
- });
2036
- const jsonContent = extractJSON(structureResult);
2037
- const parsedStructure = JSON.parse(jsonContent);
2038
- logger.printInfoLog("Processing structure tree...");
2039
- postProcessStructure(parsedStructure, frames);
2040
- const protocol = Array.isArray(parsedStructure) ? parsedStructure[0] : parsedStructure;
2041
- if (frames && protocol) {
2042
- logger.printInfoLog("Extracting component properties and states...");
2043
- await populateComponentProps(protocol, frames, thumbnailUrl);
2044
- }
2045
- logger.printSuccessLog("Component structure generated successfully");
2046
- return protocol;
2047
- } catch (error) {
2048
- const errorMessage = error instanceof Error ? error.message : String(error);
2049
- logger.printErrorLog(`Error generating component structure: ${errorMessage}`);
2050
- throw new Error(`Failed to parse component structure: ${errorMessage}`);
2051
- }
2052
- };
2053
-
2054
1814
  // src/nodes/process/index.ts
2055
- var generateProtocol = async (state) => {
2056
- const assetsDir = workspaceManager.resolveAppSrc(state.workspace, "assets");
2057
- const processDir = state.workspace.process;
2058
- const { document: document2, imageNodesMap } = await executeFigmaAndImagesActions(state.urlInfo, assetsDir, processDir);
2059
- const simplifiedDocument = figmaTool.simplifyImageNodes(document2, imageNodesMap);
2060
- const processedStyleDocument = figmaTool.processedStyle(simplifiedDocument);
2061
- const protocol = await generateStructure(processedStyleDocument);
2062
- writeFile(state.workspace.process, "protocol.json", JSON.stringify(protocol, null, 2));
2063
- logger.printInfoLog(`Please check the output in the workspace: ${state.workspace.process}`);
2064
- return {
2065
- protocol,
2066
- figmaInfo: {
2067
- thumbnail: processedStyleDocument?.thumbnailUrl || document2?.thumbnailUrl || ""
2068
- }
2069
- };
2070
- };
2071
1815
  var executeFigmaAndImagesActions = async (urlInfo, assetsDir, processDir) => {
2072
1816
  const { fileId, nodeId } = urlInfo;
2073
1817
  if (!fileId || !nodeId) {
@@ -2077,13 +1821,13 @@ var executeFigmaAndImagesActions = async (urlInfo, assetsDir, processDir) => {
2077
1821
  if (!token) {
2078
1822
  throw new Error("Figma API token is required");
2079
1823
  }
2080
- const document2 = await figmaTool.fetchAndClean(fileId, nodeId, token);
2081
- if (!document2) {
1824
+ const document = await figmaTool.fetchAndClean(fileId, nodeId, token);
1825
+ if (!document) {
2082
1826
  throw new Error("Failed to fetch and clean Figma document");
2083
1827
  }
2084
- writeFile(processDir, "figma.json", JSON.stringify(document2, null, 2));
1828
+ writeFile(processDir, "figma.json", JSON.stringify(document, null, 2));
2085
1829
  logger.printSuccessLog(`Figma document fetched and cleaned successfully`);
2086
- const downloadResult = await figmaTool.downloadImages(fileId, token, assetsDir, document2);
1830
+ const downloadResult = await figmaTool.downloadImages(fileId, token, assetsDir, document);
2087
1831
  const { successCount, failCount, imageNodesMap } = downloadResult;
2088
1832
  if (successCount) {
2089
1833
  logger.printSuccessLog(`Downloaded ${successCount} images`);
@@ -2094,3525 +1838,65 @@ var executeFigmaAndImagesActions = async (urlInfo, assetsDir, processDir) => {
2094
1838
  const resultsArray = Array.from(imageNodesMap.values());
2095
1839
  writeFile(processDir, "images.json", JSON.stringify(resultsArray, null, 2));
2096
1840
  return {
2097
- document: document2,
1841
+ document,
2098
1842
  imageNodesMap
2099
1843
  };
2100
1844
  };
2101
1845
 
2102
- // src/nodes/validation/index.ts
2103
- import fs8 from "fs";
2104
- import path11 from "path";
2105
-
2106
- // src/nodes/validation/constants.ts
2107
- var TARGET_MAE = 3;
2108
- var POSITION_THRESHOLD = 1;
2109
- var DEFAULT_TIMEOUT = 3e4;
2110
- var METRIC_DECIMAL_PLACES = 2;
2111
- var DEFAULT_VIEWPORT = { width: 1440, height: 900 };
2112
- var MAX_ITERATIONS = 3;
2113
- var HEADLESS = true;
2114
- var SELECTOR_WAIT_TIMEOUT = 5e3;
2115
- var DEFAULT_VALIDATION_LOOP_CONFIG = {
2116
- maxIterations: MAX_ITERATIONS,
2117
- targetMae: TARGET_MAE,
2118
- positionThreshold: POSITION_THRESHOLD,
2119
- browserTimeout: DEFAULT_TIMEOUT,
2120
- defaultViewportWidth: DEFAULT_VIEWPORT.width,
2121
- defaultViewportHeight: DEFAULT_VIEWPORT.height,
2122
- headless: HEADLESS
2123
- };
2124
- var DEFAULT_PORT = 5173;
2125
- function buildDevServerUrl(port) {
2126
- return `http://localhost:${port}`;
2127
- }
2128
-
2129
- // src/nodes/validation/index.ts
2130
- init_logger();
2131
-
2132
- // src/nodes/validation/core/validation-loop.ts
2133
- import * as fs7 from "fs";
2134
- import * as path10 from "path";
2135
- init_logger();
2136
-
2137
- // src/agents/commit-agent/index.ts
2138
- import { Agent as Agent2 } from "evoltagent";
1846
+ // src/skill-api.ts
1847
+ init_prompt();
2139
1848
 
2140
- // src/agents/commit-agent/prompt.ts
2141
- var COMMIT_AGENT_SYSTEM_PROMPT = `You are a Git automation agent for CodeRio validation workflow.
1849
+ // src/nodes/code/prompt.ts
1850
+ var STYLING_GUIDELINES = `
1851
+ - **Style Consistency**: Implement styles using the technical stack and libraries listed in <styling>.
1852
+ - **Strict Restriction**: Absolutely ONLY use the technical stack and libraries listed in <styling>. Do NOT use any other styling methods, libraries, or frameworks (e.g., if clsx is not listed, do not use clsx).
1853
+ - **Default Styling**: If <styling> is empty or does not contain specific libraries, DEFAULT to standard vanilla CSS.
1854
+
1855
+ - **Tailwind CSS + CSS Modules (CRITICAL)**:
1856
+ - If the stack includes BOTH Tailwind and CSS Modules (Less/SCSS), use them correctly:
1857
+ 1. **Tailwind utilities**: Use DIRECTLY in JSX className (e.g., \`className="flex items-center gap-4"\`)
1858
+ 2. **CSS Modules**: Use ONLY for complex styles that can't be expressed with Tailwind utilities (e.g., gradients, animations, pseudo-elements)
1859
+ 3. **NEVER use \`@apply\` in CSS Module files** - it's a Tailwind-specific directive that doesn't work in Less/SCSS
1860
+ 4. Example correct usage:
1861
+ TSX: \`<div className={\`flex \${styles.customGradient}\`}>\`
1862
+ Less: \`.customGradient { background: linear-gradient(...); }\`
1863
+
1864
+ - **CSS Modules Only**: If the tech stack specifies CSS Modules without Tailwind:
1865
+ 1. Create a corresponding style file (e.g., \`index.module.less\`, \`index.module.scss\`, or \`index.module.css\`)
1866
+ 2. Import it as \`import styles from './index.module.[ext]';\` in the TSX
1867
+ 3. Define all styles in the style file using standard CSS/Less/SCSS syntax
1868
+ 4. Use \`styles.className\` in JSX`;
1869
+ var ASSETS_HANDLING = `
1870
+ - **CRITICAL**: For any image URL starting with \`@/assets\`, you MUST import it at the top of the file.
1871
+ - **Asset Name Matching**:
1872
+ - Check the \`<available_assets>\` list for actual filenames in the project.
1873
+ - Asset filenames follow the pattern: \`kebab-case-name-id1-id2.ext\` (e.g., "Star 2.svg" \u2192 "star-2-1-2861.svg")
1874
+ - Match the base name (ignoring spaces, case, and ID suffix): "@/assets/arXiv.svg" \u2192 look for "arxiv-*.svg" in the list
1875
+ - Use the EXACT filename from the available assets list in your import.
1876
+ - Example: If available_assets contains "arxiv-1-2956.svg", use:
1877
+ \`import ArXivIcon from '@/assets/arxiv-1-2956.svg';\`
1878
+ - **Usage**: \`<img src={ArXivIcon} />\`, do not use backgroundImage property.
1879
+ - **NEVER** use the string path directly in JSX or styles.`;
1880
+ var DOM_IDS_REQUIREMENT = `
1881
+ - Assign \`id\` attributes to the main container and any internal elements, matching \`frame_details\`.`;
1882
+ var REACT_IMPORT_RULE = `
1883
+ - Do **NOT** include \`import React from 'react';\` at the top of the file.`;
1884
+ var FILE_NAMING_CONVENTION = `
1885
+ - ALWAYS name the main component file \`index.tsx\`.
1886
+ - ALWAYS name the style file (if applicable) \`index.module.[css|less|scss]\`.
1887
+ - NEVER use PascalCase or other names for filenames (e.g., DO NOT use \`MainFrame.tsx\` or \`Button.tsx\`).`;
1888
+ var OUTPUT_FORMAT = `
1889
+ <output_format>
1890
+ If only one file (TSX) is needed:
1891
+ \`\`\`tsx
1892
+ // code...
1893
+ \`\`\`
2142
1894
 
2143
- Input format:
2144
- - appPath: /absolute/path/to/app
2145
- - iteration: number (optional; undefined means initial commit)
2146
-
2147
- <workflow>
2148
- 1. Validate repository:
2149
- - Run GitTool.status(cwd=appPath)
2150
- - If not a git repository, run GitTool.init(cwd=appPath), then re-run status
2151
-
2152
- 2. Check for changes:
2153
- - Run GitTool.diff(cwd=appPath) to see unstaged changes
2154
- - If working tree is clean (no changes):
2155
- * Stop and return: "No changes to commit"
2156
- * DO NOT create an empty commit
2157
-
2158
- 3. Stage all changes:
2159
- - Run GitTool.add(files=".", cwd=appPath)
2160
-
2161
- 4. Analyze changes and generate commit message:
2162
- Run GitTool.diff(cwd=appPath) after staging to analyze what changed.
2163
-
2164
- Generate a conventional commit message following these rules:
2165
-
2166
- **Message format:**
2167
- - If iteration is undefined: "feat: [Initial] first commit"
2168
- - If iteration is defined: "fix: [Iteration N] <description>"
2169
-
2170
- **Description guidelines:**
2171
- Analyze the diff to determine what type of changes were made:
2172
-
2173
- a) Component layout/style fixes:
2174
- - Look for changes in component files (src/components/*.tsx, etc.)
2175
- - Extract component names from file paths (e.g., src/components/Header.tsx \u2192 Header)
2176
- - Format: "fix misaligned components ComponentA, ComponentB"
2177
- - List up to 3 components, use "and N more" if more than 3
2178
- - Example: "fix: [Iteration 1] fix misaligned components Header, Footer, and 2 more"
2179
-
2180
- b) Build/compilation error fixes:
2181
- - Look for package.json changes \u2192 "resolve dependency issues"
2182
- - Look for import/export errors \u2192 "resolve import errors"
2183
- - Look for TypeScript errors \u2192 "resolve type errors"
2184
- - Example: "fix: [Iteration 2] resolve build error"
2185
-
2186
- c) Mixed changes (both component fixes AND build fixes):
2187
- - Prioritize component fixes in the message
2188
- - Example: "fix: [Iteration 1] fix misaligned components xxx and resolve yyy error"
2189
-
2190
- 5. Create commit:
2191
- - Use temporary identity (do NOT modify global git config):
2192
- * user.name = "CodeRio"
2193
- * user.email = ""
2194
- - Run GitTool.commit(message, cwd=appPath, config={...})
2195
- </workflow>
2196
-
2197
- <example>
2198
- Example tool call format:
2199
-
2200
- GitTool.commit({
2201
- "message": "feat: [Initial] generate webpage",
2202
- "cwd": "/absolute/path/to/app",
2203
- "config": {"user.name": "CodeRio", "user.email": ""}
2204
- })
2205
-
2206
- IMPORTANT: The config parameter must be a JSON object with keys like "user.name" and "user.email".
2207
- DO NOT use XML-style tags like <user.name>CodeRio</user.name>.
2208
- </example>
2209
-
2210
- <output>
2211
- Respond with a short summary in <TaskCompletion> tags.
2212
- Include whether a commit was created and the final commit message.
2213
- </output>`;
2214
-
2215
- // src/agents/commit-agent/index.ts
2216
- function createCommitAgent() {
2217
- const modelConfig = {
2218
- ...getModelConfig(),
2219
- contextWindowTokens: AGENT_CONTEXT_WINDOW_TOKENS,
2220
- maxOutputTokens: MAX_OUTPUT_TOKENS
2221
- };
2222
- return new Agent2({
2223
- name: "CommitAgent",
2224
- profile: "A helpful assistant that commits local changes in a repository.",
2225
- system: COMMIT_AGENT_SYSTEM_PROMPT,
2226
- tools: ["GitTool.init", "GitTool.status", "GitTool.add", "GitTool.diff", "GitTool.commit"],
2227
- modelConfig,
2228
- verbose: 1,
2229
- toolcallManagerPoolSize: 1
2230
- });
2231
- }
2232
-
2233
- // src/agents/commit-agent/instruction.ts
2234
- function formatGitCommitInstruction(params) {
2235
- const { appPath, iteration } = params;
2236
- return `appPath: ${appPath}
2237
- iteration: ${iteration ?? "undefined"}
2238
-
2239
- TASK:
2240
- Check for changes and commit if there are any modifications in the app directory.`;
2241
- }
2242
-
2243
- // src/nodes/validation/commit/index.ts
2244
- init_logger();
2245
- import path5 from "path";
2246
- async function commit(options) {
2247
- try {
2248
- if (!options?.appPath) {
2249
- throw new Error("commit() requires options.appPath");
2250
- }
2251
- const appPath = path5.resolve(options.appPath);
2252
- const agent = createCommitAgent();
2253
- const instruction = formatGitCommitInstruction({
2254
- appPath,
2255
- iteration: options.iteration
2256
- });
2257
- await agent.run(instruction);
2258
- logger.printSuccessLog("Commit completed!");
2259
- return {
2260
- success: true,
2261
- message: "Commit workflow completed"
2262
- };
2263
- } catch (error) {
2264
- const errorMessage = error instanceof Error ? error.message : String(error);
2265
- logger.printErrorLog(`Commit failed: ${errorMessage}`);
2266
- return {
2267
- success: false,
2268
- message: errorMessage
2269
- };
2270
- }
2271
- }
2272
-
2273
- // src/agents/judger-agent/index.ts
2274
- import { Agent as Agent3 } from "evoltagent";
2275
-
2276
- // src/tools/history-tool/index.ts
2277
- import { tools as tools3 } from "evoltagent";
2278
- var _HistoryTool_decorators, _init3;
2279
- _HistoryTool_decorators = [tools3({
2280
- getComponentHistory: {
2281
- description: "Get position and fix history for a component. Shows how the component's position and error evolved across iterations, and what fixes were applied. Helps identify if previous fixes helped or hurt",
2282
- params: [{ name: "componentId", type: "string", description: "Component ID to get history for" }],
2283
- returns: { type: "string", description: "Formatted string with complete history of positions, errors, and fixes" },
2284
- examples: [
2285
- `<HistoryTool.getComponentHistory>
2286
- <componentId>HeroSection</componentId>
2287
- </HistoryTool.getComponentHistory>`
2288
- ]
2289
- },
2290
- getIterationSummary: {
2291
- description: "Get summary of what was changed in a specific iteration. Shows all components that were fixed in that iteration and what fixes were applied. Useful for understanding what went wrong in a previous iteration",
2292
- params: [{ name: "iteration", type: "number", description: "Iteration number (1-indexed)" }],
2293
- returns: { type: "string", description: "Formatted string with summary of changes in that iteration" },
2294
- examples: [
2295
- `<HistoryTool.getIterationSummary>
2296
- <iteration>2</iteration>
2297
- </HistoryTool.getIterationSummary>`
2298
- ]
2299
- }
2300
- })];
2301
- var HistoryTool = class {
2302
- _history = {};
2303
- /**
2304
- * Initialize with component history from previous iterations.
2305
- *
2306
- * @param history - Dict mapping component_id to list of iteration records
2307
- */
2308
- setContext(history) {
2309
- this._history = history;
2310
- }
2311
- /**
2312
- * Get position and fix history for a component.
2313
- *
2314
- * Shows how the component's position and error evolved across iterations,
2315
- * and what fixes were applied. Helps identify if previous fixes helped or hurt.
2316
- *
2317
- * @param componentId - Component ID to get history for
2318
- * @returns Formatted string with complete history of positions, errors, and fixes
2319
- */
2320
- getComponentHistory(componentId) {
2321
- if (!this._history || Object.keys(this._history).length === 0) {
2322
- return "No history available (this is iteration 1)";
2323
- }
2324
- if (!(componentId in this._history)) {
2325
- return `No history found for component '${componentId}' (first time misaligned)`;
2326
- }
2327
- const historyEntries = this._history[componentId];
2328
- if (!historyEntries || historyEntries.length === 0) {
2329
- return `Empty history for component '${componentId}'`;
2330
- }
2331
- const lines = [`History for ${componentId}:`, ""];
2332
- for (let i = 0; i < historyEntries.length; i++) {
2333
- const entry = historyEntries[i];
2334
- if (!entry) continue;
2335
- const iteration = entry.iteration ?? "?";
2336
- const position = entry.position ?? [0, 0];
2337
- const error = entry.error ?? [0, 0];
2338
- const fixApplied = entry.fixApplied ? entry.fixApplied.join("\n ") : "None";
2339
- lines.push(`Iteration ${iteration}:`);
2340
- lines.push(` Position: (${position[0].toFixed(1)}, ${position[1].toFixed(1)}) px`);
2341
- lines.push(` Error: (${error[0].toFixed(1)}, ${error[1].toFixed(1)}) px`);
2342
- lines.push(` Fix Applied:
2343
- ${fixApplied}`);
2344
- if (i > 0) {
2345
- const prevEntry = historyEntries[i - 1];
2346
- if (prevEntry) {
2347
- const prevErrorMagnitude = prevEntry.error[0] + prevEntry.error[1];
2348
- const currErrorMagnitude = error[0] + error[1];
2349
- if (currErrorMagnitude > prevErrorMagnitude) {
2350
- lines.push(
2351
- ` Warning: REGRESSION: Error increased from ${prevErrorMagnitude.toFixed(1)}px to ${currErrorMagnitude.toFixed(1)}px`
2352
- );
2353
- }
2354
- }
2355
- }
2356
- lines.push("");
2357
- }
2358
- return lines.join("\n");
2359
- }
2360
- /**
2361
- * Get summary of what was changed in a specific iteration.
2362
- *
2363
- * Shows all components that were fixed in that iteration and what fixes
2364
- * were applied. Useful for understanding what went wrong in a previous iteration.
2365
- *
2366
- * @param iteration - Iteration number (1-indexed)
2367
- * @returns Formatted string with summary of changes in that iteration
2368
- */
2369
- getIterationSummary(iteration) {
2370
- if (!this._history || Object.keys(this._history).length === 0) {
2371
- return `No history available (iteration ${iteration} has not completed yet)`;
2372
- }
2373
- const changes = [];
2374
- for (const [componentId, entries] of Object.entries(this._history)) {
2375
- for (const entry of entries) {
2376
- if (entry.iteration === iteration) {
2377
- changes.push({
2378
- componentId,
2379
- position: entry.position ?? [0, 0],
2380
- error: entry.error ?? [0, 0],
2381
- fixApplied: entry.fixApplied ?? ["None"]
2382
- });
2383
- break;
2384
- }
2385
- }
2386
- }
2387
- if (changes.length === 0) {
2388
- return `No changes recorded for iteration ${iteration}`;
2389
- }
2390
- const lines = [`Iteration ${iteration} Summary:`, `Total components modified: ${changes.length}`, ""];
2391
- for (const change of changes) {
2392
- lines.push(`${change.componentId}:`);
2393
- lines.push(` Error: (${change.error[0].toFixed(1)}, ${change.error[1].toFixed(1)}) px`);
2394
- lines.push(` Fix:
2395
- ${change.fixApplied.join("\n ")}`);
2396
- lines.push("");
2397
- }
2398
- return lines.join("\n");
2399
- }
2400
- };
2401
- _init3 = __decoratorStart(null);
2402
- HistoryTool = __decorateElement(_init3, 0, "HistoryTool", _HistoryTool_decorators, HistoryTool);
2403
- __runInitializers(_init3, 1, HistoryTool);
2404
-
2405
- // src/tools/hierarchy-tool/index.ts
2406
- import { tools as tools4 } from "evoltagent";
2407
-
2408
- // src/nodes/validation/utils/tree/tree-traversal.ts
2409
- function getNodeId(node) {
2410
- return node?.componentId || node?.id || void 0;
2411
- }
2412
- function findInTree(data, targetId, idKeys = ["id", "componentId"]) {
2413
- if (data === null || data === void 0) {
2414
- return void 0;
2415
- }
2416
- if (typeof data === "object" && !Array.isArray(data)) {
2417
- const dict = data;
2418
- for (const idKey of idKeys) {
2419
- if (dict[idKey] === targetId) {
2420
- return dict;
2421
- }
2422
- }
2423
- for (const value of Object.values(dict)) {
2424
- const result = findInTree(value, targetId, idKeys);
2425
- if (result) {
2426
- return result;
2427
- }
2428
- }
2429
- } else if (Array.isArray(data)) {
2430
- for (const item of data) {
2431
- const result = findInTree(item, targetId, idKeys);
2432
- if (result) {
2433
- return result;
2434
- }
2435
- }
2436
- }
2437
- return void 0;
2438
- }
2439
-
2440
- // src/tools/hierarchy-tool/index.ts
2441
- var _HierarchyTool_decorators, _init4;
2442
- _HierarchyTool_decorators = [tools4({
2443
- lookup: {
2444
- description: "Get file path, parent, siblings, and children for a component. Use this to find the correct file path before reading component files.",
2445
- params: [{ name: "componentId", type: "string", description: "Component ID to look up" }],
2446
- returns: { type: "string", description: "Formatted string with file path, parent (with its file path), siblings, and children" },
2447
- examples: [
2448
- `<HierarchyTool.lookup>
2449
- <componentId>HeroSection</componentId>
2450
- </HierarchyTool.lookup>`
2451
- ]
2452
- },
2453
- getSharedInstances: {
2454
- description: "Find all component instances using a specific file path. Useful for identifying which components share the same implementation file (e.g., StartButton and ApiButton both using button/index.tsx). ALWAYS check this before editing a shared file.",
2455
- params: [{ name: "filePath", type: "string", description: "Filesystem path to search for (can be relative or absolute)" }],
2456
- returns: { type: "string", description: "Formatted string with all component instances using the file" },
2457
- examples: [
2458
- `<HierarchyTool.getSharedInstances>
2459
- <filePath>button/index.tsx</filePath>
2460
- </HierarchyTool.getSharedInstances>`
2461
- ]
2462
- }
2463
- })];
2464
- var HierarchyTool = class {
2465
- _structureTree = {};
2466
- _componentPaths = {};
2467
- constructor() {
2468
- this._structureTree = {};
2469
- this._componentPaths = {};
2470
- }
2471
- /**
2472
- * Initialize with page structure and path mapping.
2473
- */
2474
- setContext(structureTree, componentPaths) {
2475
- this._structureTree = structureTree;
2476
- this._componentPaths = componentPaths;
2477
- }
2478
- lookup(componentId) {
2479
- const node = findInTree(this._structureTree, componentId);
2480
- if (!node) {
2481
- return `Component '${componentId}' not found in structure tree`;
2482
- }
2483
- const parentInfo = this._findParent(this._structureTree, componentId);
2484
- const parentStr = parentInfo ? `Parent: ${parentInfo.id}` : "Parent: None (root component)";
2485
- const parentPathStr = parentInfo && this._componentPaths[parentInfo.id] ? `Parent File: ${this._componentPaths[parentInfo.id]}` : "";
2486
- let siblings = [];
2487
- if (parentInfo) {
2488
- const parentNode = findInTree(this._structureTree, parentInfo.id);
2489
- if (parentNode) {
2490
- const children2 = parentNode.children || [];
2491
- siblings = children2.map((child) => getNodeId(child)).filter((id) => id !== void 0 && id !== componentId);
2492
- }
2493
- }
2494
- const siblingsStr = siblings.length > 0 ? `Siblings: ${siblings.join(", ")}` : "Siblings: None";
2495
- const nodeChildren = node.children || [];
2496
- const children = nodeChildren.map((child) => getNodeId(child)).filter((id) => id !== void 0);
2497
- const childrenStr = children.length > 0 ? `Children: ${children.join(", ")}` : "Children: None";
2498
- const filePath = this._componentPaths[componentId];
2499
- const fileStr = filePath ? `File: ${filePath}` : "";
2500
- const lines = [`Component: ${componentId}`, fileStr, parentStr, parentPathStr, siblingsStr, childrenStr].filter(Boolean);
2501
- return lines.join("\n");
2502
- }
2503
- getSharedInstances(filePath) {
2504
- const instances = [];
2505
- for (const [compId, compPath] of Object.entries(this._componentPaths)) {
2506
- if (compPath.includes(filePath) || compPath.endsWith(filePath)) {
2507
- instances.push(compId);
2508
- }
2509
- }
2510
- if (instances.length === 0) {
2511
- return `No components found using file: ${filePath}`;
2512
- }
2513
- const instancesList = instances.map((inst) => ` - ${inst}`).join("\n");
2514
- return `Components using ${filePath}:
2515
- ${instancesList}
2516
-
2517
- Warning: Changes to this file will affect ALL ${instances.length} instance(s)`;
2518
- }
2519
- _findParent(node, targetId, parent = null) {
2520
- const nodeId = getNodeId(node);
2521
- if (nodeId === targetId) {
2522
- if (parent) {
2523
- const parentId = getNodeId(parent);
2524
- if (parentId) {
2525
- return { id: parentId };
2526
- }
2527
- }
2528
- return void 0;
2529
- }
2530
- const children = node.children || [];
2531
- for (const child of children) {
2532
- const result = this._findParent(child, targetId, node);
2533
- if (result !== void 0) {
2534
- return result;
2535
- }
2536
- }
2537
- return void 0;
2538
- }
2539
- };
2540
- _init4 = __decoratorStart(null);
2541
- HierarchyTool = __decorateElement(_init4, 0, "HierarchyTool", _HierarchyTool_decorators, HierarchyTool);
2542
- __runInitializers(_init4, 1, HierarchyTool);
2543
-
2544
- // src/agents/judger-agent/prompt.ts
2545
- var JUDGER_PROMPT = `You are a React Layout Diagnosis Expert. Analyze position misalignments and provide precise fix instructions.
2546
-
2547
- <workflow>
2548
- 1. Check HistoryTool.getComponentHistory - avoid repeating failed fixes
2549
- 2. Use HierarchyTool.lookup to get component file path, parent, siblings, and children
2550
- 3. Use FileEditor.read to examine code, FileEditor.find to locate patterns
2551
- 4. For parent fixes: HierarchyTool.lookup returns parent file path - use it directly
2552
- 5. Before editing shared files: Use HierarchyTool.getSharedInstances to check impact
2553
- 6. If image provided: previous iteration
2554
- 7. Respond with JSON diagnosis wrapped in \`\`\`json\`\`\`
2555
- </workflow>
2556
-
2557
- <error_types>
2558
- - pixel_misalignment: Wrong spacing values (gap-2 vs gap-4, mt-[20px] vs mt-[50px])
2559
- - positioning_strategy: Wrong layout method (absolute vs flex, wrong parent)
2560
- - parent_spacing: Parent gap/padding causes child misalignment
2561
- - sibling_cascade: Sibling margin shifts others (fix topmost only)
2562
- - none: Position already correct
2563
- </error_types>
2564
-
2565
- <rules>
2566
- CRITICAL CONSTRAINTS:
2567
- 1. Fix POSITION only (x,y) - validation does NOT measure dimensions
2568
- 2. FORBIDDEN: w-*, h-*, max-w-*, max-h-*, min-w-*, min-h-*, aspect-*, flex-row, flex-col
2569
- 3. ALLOWED: mt-*, mb-*, ml-*, mr-*, pt-*, pb-*, pl-*, pr-*, gap-*, space-*, top-*, left-*, right-*, bottom-*, translate-*
2570
-
2571
- FLEX LAYOUT (prevents MAE oscillation):
2572
- - Adding mt-[X] to component A shifts ALL siblings below by X pixels (additive effect)
2573
- - For sibling components: Fix ONLY the topmost misaligned one per iteration (topmost = first in DOM order)
2574
- - For other siblings: Set refineInstructions to [] with rootCause: "Fix deferred - waiting for topmost sibling fix to propagate"
2575
- - Prefer parent gap/padding over child margins
2576
- - If component oscillates "too high" \u2194 "too low" \u2192 fix parent or topmost sibling instead
2577
-
2578
- SPECIAL CASES:
2579
- - Already correct: errorType="none", refineInstructions=[]
2580
- - Wrong dimensions/flex-direction: Note in rootCause but do NOT fix
2581
- - Wrong X (x=0 vs x=320): Check parent centering, do NOT change w-full to w-[640px]
2582
- </rules>
2583
-
2584
- <output_format>
2585
- Return JSON with camelCase fields: errorType, rootCause, visualEvidence, codeEvidence, refineInstructions, toolsUsed
2586
-
2587
- refineInstructions format: "In [FULL_PATH] line [N], change '[OLD_CODE]' to '[NEW_CODE]'"
2588
- - Use EXACT code strings from FileEditor.read (copy-paste)
2589
- - OLD_CODE \u2260 NEW_CODE (never "keep unchanged")
2590
- - Check HierarchyTool.getSharedInstances before editing shared files
2591
-
2592
- Wrap output:
2593
- <TaskCompletion>
2594
- \`\`\`json
2595
- {your json here}
2596
- \`\`\`
2597
- </TaskCompletion>
2598
- </output_format>
2599
-
2600
- <example>
2601
- <TaskCompletion>
2602
- \`\`\`json
2603
- {
2604
- "errorType": "pixel_misalignment",
2605
- "rootCause": "Gap is 8px but should be 16px",
2606
- "visualEvidence": "Elements too close in screenshot",
2607
- "codeEvidence": "Line 23: className='flex gap-2'",
2608
- "refineInstructions": ["In /path/Card.tsx line 23, change 'gap-2' to 'gap-4'"],
2609
- "toolsUsed": ["FileEditor.read", "HistoryTool.getComponentHistory"]
2610
- }
2611
- \`\`\`
2612
- </TaskCompletion>
2613
- </example>
2614
- `;
2615
-
2616
- // src/agents/judger-agent/utils.ts
2617
- init_logger();
2618
- import { SystemToolStore, FunctionCallingStore } from "evoltagent";
2619
- function parseJudgerResult(response) {
2620
- if (!response || response.trim().length === 0) {
2621
- logger.printInfoLog("Judger agent returned empty response, skipping refinement for this iteration");
2622
- return Promise.resolve({
2623
- errorType: "pixel_misalignment",
2624
- rootCause: "Agent analysis unavailable, do not apply any edits",
2625
- visualEvidence: "N/A",
2626
- codeEvidence: "N/A",
2627
- refineInstructions: [],
2628
- toolsUsed: []
2629
- });
2630
- }
2631
- const jsonMatch = response.match(/```json\n([\s\S]*?)\n```/);
2632
- if (!jsonMatch) {
2633
- logger.printInfoLog("Judger agent response missing JSON block, skipping refinement for this iteration");
2634
- return Promise.resolve({
2635
- errorType: "empty_response",
2636
- rootCause: "Agent analysis unavailable, do not apply any edits",
2637
- visualEvidence: "N/A",
2638
- codeEvidence: "N/A",
2639
- refineInstructions: [],
2640
- toolsUsed: []
2641
- });
2642
- }
2643
- const jsonStr = jsonMatch[1];
2644
- if (!jsonStr || jsonStr.trim().length === 0) {
2645
- logger.printInfoLog("Judger agent response has empty JSON, skipping refinement for this iteration");
2646
- return Promise.resolve({
2647
- errorType: "empty_response",
2648
- rootCause: "Agent analysis unavailable, do not apply any edits",
2649
- visualEvidence: "N/A",
2650
- codeEvidence: "N/A",
2651
- refineInstructions: [],
2652
- toolsUsed: []
2653
- });
2654
- }
2655
- try {
2656
- return Promise.resolve(JSON.parse(jsonStr));
2657
- } catch (error) {
2658
- logger.printInfoLog(`Judger agent response JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
2659
- return Promise.resolve({
2660
- errorType: "empty_response",
2661
- rootCause: "Agent analysis unavailable",
2662
- visualEvidence: "N/A",
2663
- codeEvidence: "N/A",
2664
- refineInstructions: [],
2665
- toolsUsed: []
2666
- });
2667
- }
2668
- }
2669
- function updateToolContext(toolInstance, toolName, methodNames) {
2670
- const updatedNames = [];
2671
- for (const methodName of methodNames) {
2672
- const raw = toolInstance[methodName];
2673
- if (!raw) {
2674
- continue;
2675
- }
2676
- const method = raw.bind(toolInstance);
2677
- const fullName = `${toolName}.${methodName}`;
2678
- const systemTool = SystemToolStore.getTool(fullName);
2679
- if (systemTool) {
2680
- systemTool.execute = method;
2681
- updatedNames.push(fullName);
2682
- }
2683
- const functionCallingName = `${toolName}-${methodName}`;
2684
- const userTool = FunctionCallingStore.getTool(functionCallingName);
2685
- if (userTool) {
2686
- userTool.execute = method;
2687
- }
2688
- }
2689
- return updatedNames;
2690
- }
2691
-
2692
- // src/agents/judger-agent/index.ts
2693
- function createJudgerAgent(options) {
2694
- const { workspaceDir, structureTree, componentPaths, history } = options;
2695
- let systemPrompt = JUDGER_PROMPT;
2696
- if (workspaceDir) {
2697
- systemPrompt += `
2698
-
2699
- WORKSPACE: ${workspaceDir}
2700
- Only access files within this workspace.`;
2701
- }
2702
- const systemTools = ["FileEditor.read", "FileEditor.find"];
2703
- if (structureTree) {
2704
- const hierarchyTool = new HierarchyTool();
2705
- hierarchyTool.setContext(structureTree, componentPaths || {});
2706
- const hierarchyToolNames = updateToolContext(
2707
- hierarchyTool,
2708
- "HierarchyTool",
2709
- ["lookup", "getSharedInstances"]
2710
- );
2711
- systemTools.push(...hierarchyToolNames);
2712
- }
2713
- const historyTool = new HistoryTool();
2714
- historyTool.setContext(history || {});
2715
- const historyToolNames = updateToolContext(
2716
- historyTool,
2717
- "HistoryTool",
2718
- ["getComponentHistory", "getIterationSummary"]
2719
- );
2720
- systemTools.push(...historyToolNames);
2721
- const modelConfig = {
2722
- ...getModelConfig(),
2723
- contextWindowTokens: AGENT_CONTEXT_WINDOW_TOKENS,
2724
- maxOutputTokens: MAX_OUTPUT_TOKENS,
2725
- stream: true
2726
- };
2727
- return new Agent3({
2728
- name: "JudgerAgent",
2729
- profile: "Layout diagnosis specialist",
2730
- system: systemPrompt,
2731
- tools: systemTools,
2732
- modelConfig,
2733
- postProcessor: parseJudgerResult,
2734
- verbose: 2
2735
- });
2736
- }
2737
-
2738
- // src/agents/judger-agent/instruction.ts
2739
- function formatJudgerInstruction(component, figmaMetadata, componentPaths) {
2740
- const vr = component.validationReport;
2741
- const validationReportFormatted = `Current Position: (${vr.currentPosition[0].toFixed(1)}, ${vr.currentPosition[1].toFixed(1)}) px
2742
- Target Position: (${vr.targetPosition[0].toFixed(1)}, ${vr.targetPosition[1].toFixed(1)}) px
2743
- Absolute Error: (${vr.absoluteError[0].toFixed(1)}, ${vr.absoluteError[1].toFixed(1)}) px`;
2744
- const padding = figmaMetadata.padding;
2745
- const layoutMode = typeof figmaMetadata.layoutMode === "string" ? figmaMetadata.layoutMode : "NONE";
2746
- const itemSpacing = typeof figmaMetadata.itemSpacing === "number" ? figmaMetadata.itemSpacing : 0;
2747
- const primaryAxisAlignItems = typeof figmaMetadata.primaryAxisAlignItems === "string" ? figmaMetadata.primaryAxisAlignItems : "N/A";
2748
- const counterAxisAlignItems = typeof figmaMetadata.counterAxisAlignItems === "string" ? figmaMetadata.counterAxisAlignItems : "N/A";
2749
- const resolvedPath = componentPaths?.[component.componentId];
2750
- if (!resolvedPath) {
2751
- throw new Error(`Component ${component.componentId} not found in componentPaths mapping`);
2752
- }
2753
- return `Component ID: ${component.componentId}
2754
- Element IDs: ${JSON.stringify(component.elementIds)}
2755
- File: ${resolvedPath}
2756
-
2757
- Validation Report:
2758
- ${validationReportFormatted}
2759
-
2760
- Figma Layout:
2761
- - Mode: ${layoutMode}
2762
- - Item Spacing: ${itemSpacing}px
2763
- - Padding: ${padding?.left ?? 0}px (left) ${padding?.top ?? 0}px (top) ${padding?.right ?? 0}px (right) ${padding?.bottom ?? 0}px (bottom)
2764
- - Primary Axis Alignment: ${primaryAxisAlignItems}
2765
- - Counter Axis Alignment: ${counterAxisAlignItems}
2766
-
2767
- TASK:
2768
- 1. Read component file at: ${resolvedPath}
2769
- 2. Identify the layout error by comparing code with Figma metadata
2770
- 3. Locate exact code patterns with FileEditor.find
2771
- 4. Output JSON diagnosis with precise refine_instructions
2772
-
2773
- IMPORTANT:
2774
- - Use FileEditor.read("${resolvedPath}") to read the component code
2775
- - The path is an absolute filesystem path (e.g., /workspace/src/components/button/index.tsx)
2776
- - When using HierarchyTool.lookup or related tools, use the Component ID: ${component.componentId}
2777
-
2778
- Remember: Use exact code strings and line numbers in refine_instructions.
2779
- `;
2780
- }
2781
-
2782
- // src/agents/refiner-agent/index.ts
2783
- import { Agent as Agent4 } from "evoltagent";
2784
-
2785
- // src/agents/refiner-agent/prompt.ts
2786
- var REFINER_PROMPT = `You are a React Code Editor. Apply fixes from judger's diagnosis.
2787
-
2788
- <workflow>
2789
- 1. For each refineInstruction, parse format: "In [path] line [N], change '[old]' to '[new]'"
2790
- 2. VALIDATE before executing:
2791
- - Skip if instruction says "keep unchanged", "no change needed", or lacks specific code
2792
- - Skip if OLD and NEW are identical
2793
- - Skip if OLD or NEW is empty
2794
- - Skip if instruction is vague like "apply fix in parent" without specific code
2795
- 3. Read file with FileEditor.read to verify the OLD pattern exists
2796
- 4. Use FileEditor.findAndReplace(path, pattern=old, replacement=new)
2797
- - IMPORTANT: Escape special regex chars in pattern: [ ] ( ) . * + ? $ ^ | \\
2798
- - Example: "mt-[20px]" \u2192 pattern="mt-\\[20px\\]"
2799
- 5. Report results
2800
- </workflow>
2801
-
2802
- <validation_rules>
2803
- SKIP instructions that:
2804
- - Contain "keep unchanged" or "no change needed"
2805
- - Don't have both '[old]' and '[new]' quoted strings
2806
- - Have identical old and new values
2807
- - Are vague suggestions without specific code
2808
-
2809
- ALWAYS escape regex special characters in the pattern parameter:
2810
- - Brackets: [ \u2192 \\[, ] \u2192 \\]
2811
- - Parentheses: ( \u2192 \\(, ) \u2192 \\)
2812
- - Other: . * + ? $ ^ | \u2192 prefix with \\
2813
- </validation_rules>
2814
-
2815
- <output>
2816
- After edits, respond with a summary:
2817
- - "Successfully applied: [description]" (per success)
2818
- - "Failed to apply: [description]" (per failure)
2819
- - "Skipped: [description]" (for invalid instructions)
2820
-
2821
- Wrap summary in <TaskCompletion> tags:
2822
- <TaskCompletion>
2823
- [Your summary here]
2824
- </TaskCompletion>
2825
- </output>
2826
- `;
2827
-
2828
- // src/agents/refiner-agent/utils.ts
2829
- function parseRefinerResult(response) {
2830
- const fullResponse = response.trim();
2831
- const lines = fullResponse.split("\n");
2832
- const summaryLines = [];
2833
- for (const line of lines) {
2834
- const trimmedLine = line.trim();
2835
- if (trimmedLine.startsWith("Successfully applied:") || trimmedLine.startsWith("Failed to apply:") || trimmedLine.startsWith("Skipped:")) {
2836
- summaryLines.push(trimmedLine);
2837
- }
2838
- }
2839
- const summary = summaryLines.length > 0 ? summaryLines : [fullResponse];
2840
- const summaryText = summary.join(" ").toLowerCase();
2841
- const successCount = (summaryText.match(/successfully applied:/g) || []).length;
2842
- const failedCount = (summaryText.match(/failed to apply:/g) || []).length;
2843
- return Promise.resolve({
2844
- success: successCount > 0 && failedCount === 0,
2845
- summary,
2846
- editsApplied: successCount,
2847
- error: failedCount > 0 ? `${failedCount} edit(s) failed` : void 0
2848
- });
2849
- }
2850
-
2851
- // src/agents/refiner-agent/instruction.ts
2852
- function formatRefinerInstruction(component, diagnosis, componentPaths) {
2853
- const resolvedPath = componentPaths?.[component.componentId];
2854
- if (!resolvedPath) {
2855
- throw new Error(`Component ${component.componentId} not found in componentPaths mapping`);
2856
- }
2857
- const refineInstructionsList = (diagnosis.refineInstructions || []).map((instr, i) => `${i + 1}. ${instr}`).join("\n");
2858
- return `Component ID: ${component.componentId}
2859
- Element IDs: ${JSON.stringify(component.elementIds)}
2860
- File: ${resolvedPath}
2861
-
2862
- Diagnosis from judger:
2863
- ${JSON.stringify(diagnosis, null, 2)}
2864
-
2865
- TASK:
2866
- Apply each refineInstruction:
2867
- ${refineInstructionsList}
2868
-
2869
- For each instruction:
2870
- 1. Parse the old_code and new_code
2871
- 2. Use FileEditor.findAndReplace to apply the change
2872
- 3. Report success or failure
2873
-
2874
- IMPORTANT:
2875
- - The file path is an absolute filesystem path (e.g., /workspace/src/components/button/index.tsx)
2876
- - Use this exact path with FileEditor tools
2877
- `;
2878
- }
2879
-
2880
- // src/agents/refiner-agent/index.ts
2881
- function createRefinerAgent(workspaceDir) {
2882
- let systemPrompt = REFINER_PROMPT;
2883
- if (workspaceDir) {
2884
- systemPrompt += `
2885
-
2886
- WORKSPACE: ${workspaceDir}
2887
- Only modify files within this workspace.`;
2888
- }
2889
- const modelConfig = {
2890
- ...getModelConfig(),
2891
- contextWindowTokens: AGENT_CONTEXT_WINDOW_TOKENS,
2892
- maxOutputTokens: MAX_OUTPUT_TOKENS,
2893
- stream: true
2894
- };
2895
- return new Agent4({
2896
- name: "RefinerAgent",
2897
- profile: "Code editor specialist",
2898
- system: systemPrompt,
2899
- tools: ["FileEditor.read", "FileEditor.find", "FileEditor.findAndReplace"],
2900
- modelConfig,
2901
- postProcessor: parseRefinerResult,
2902
- verbose: 2
2903
- });
2904
- }
2905
-
2906
- // src/agents/launch-agent/index.ts
2907
- import { Agent as Agent5 } from "evoltagent";
2908
-
2909
- // src/agents/launch-agent/prompt.ts
2910
- var LAUNCH_AGENT_PROMPT = `
2911
- <task_goal>
2912
- Prepare project for validation: install dependencies (if needed), build, fix errors, start dev server.
2913
- Return server metadata for validation loop to use.
2914
- </task_goal>
2915
-
2916
- <workflow>
2917
- Follow steps IN ORDER. Repeat Step 2-3 until build succeeds, then proceed to Step 4.
2918
-
2919
- Step 1: Install Dependencies (Conditional)
2920
- - Check instruction for skipInstall directive
2921
- - If "Skip Step 1" appears in instruction: Skip to Step 2
2922
- - Otherwise: Execute "pnpm i" in appPath
2923
- - Parameters: CommandLineTool.execute(command="pnpm i", cwd=appPath, timeoutMs=180000)
2924
- - Verify exitCode === 0 before proceeding
2925
- - If installation fails, analyze the error output and fix issues before proceeding
2926
-
2927
- Step 2: Build Project
2928
- - Execute: CommandLineTool.execute(command="npm run build", cwd=appPath, timeoutMs=180000)
2929
- - Captures all compilation errors in output
2930
-
2931
- Step 3: Fix Compilation Errors (If exitCode !== 0)
2932
- - Analyze error messages in 'error' and 'output' fields
2933
- - Use FileEditor.read to examine problematic files
2934
- - Use FileEditor.write to fix ONLY the specific broken lines
2935
- - CRITICAL: Do NOT delete or rewrite entire files. Do NOT simplify complex CSS or logic.
2936
- - Return to Step 2 and rebuild
2937
-
2938
- Step 4: Start Dev Server & Check Runtime Errors
2939
- - Only proceed here when build is successful
2940
- - Execute: LaunchTool.startDevServer(appPath=appPath, runCommand="npm run dev", timeoutMs=60000)
2941
- - Returns JSON: { success, url, port, serverKey, outputTail }
2942
- - Parse and check outputTail for runtime error patterns EVEN IF success=true:
2943
- * "Module not found", "Cannot find module"
2944
- * "SyntaxError", "TypeError", "ReferenceError"
2945
- * "Failed to compile", "Unhandled Runtime Error"
2946
- - If errors found: Fix using FileEditor, then restart Step 2
2947
- * CRITICAL: Do NOT delete or rewrite entire files. Do NOT simplify complex CSS or logic.
2948
- - If clean: Store serverKey, url, port and proceed to Step 5
2949
-
2950
- Step 5: Return Server Metadata
2951
- - Format final response as:
2952
- <TaskCompletion>
2953
- \`\`\`json
2954
- {
2955
- "success": true,
2956
- "serverKey": "launch:...",
2957
- "url": "http://localhost:PORT",
2958
- "port": PORT,
2959
- "message": "Dev server started successfully"
2960
- }
2961
- \`\`\`
2962
- </TaskCompletion>
2963
- - On failure before server start:
2964
- <TaskCompletion>
2965
- \`\`\`json
2966
- { "success": false, "error": "Description" }
2967
- \`\`\`
2968
- </TaskCompletion>
2969
- </workflow>
2970
-
2971
- <principles>
2972
- 1. STRICT CONTENT PRESERVATION (MANDATORY):
2973
- - NEVER modify CSS/Style content (colors, layouts, animations, etc.). If a CSS error exists, only fix the import/path or a specific syntax typo.
2974
- - NEVER modify or delete Image assets or their references.
2975
- - NEVER modify the DOM structure or JSX layout (adding/removing tags, changing classNames).
2976
- - NEVER replace file content with "template" or "placeholder" code.
2977
- 2. ALLOWED CHANGES ONLY:
2978
- - Fixing 'module not found' by installing missing packages or correcting import paths.
2979
- - Fixing TypeScript/JavaScript syntax errors that prevent execution.
2980
- - Fixing configuration issues (e.g., tailwind.config.js, tsconfig.json).
2981
- 3. BUILD FIRST: Dev server must not start until build succeeds (exitCode === 0).
2982
- 4. PORT CONSISTENCY: Always use LaunchTool.startDevServer() (workspace-preferred port, same port across runs).
2983
- 5. RUNTIME VALIDATION: Check outputTail in Step 4 for runtime errors even if success=true.
2984
- </principles>
2985
-
2986
- <available_tools>
2987
- - FileEditor.read: Read file contents for analysis
2988
- - FileEditor.write: Write file contents to fix errors
2989
- - CommandLineTool.execute: Execute commands (pnpm i, npm run build only)
2990
- - LaunchTool.startDevServer: Start dev server (returns JSON with serverKey/url/port/outputTail)
2991
- - ThinkTool.execute: Think through complex problems
2992
- </available_tools>
2993
- `;
2994
-
2995
- // src/agents/launch-agent/utils.ts
2996
- init_logger();
2997
- function parseLaunchResult(response) {
2998
- if (!response || response.trim().length === 0) {
2999
- logger.printInfoLog("Launch agent returned empty response");
3000
- return Promise.resolve({
3001
- success: false,
3002
- error: "Agent returned empty response"
3003
- });
3004
- }
3005
- const jsonMatch = response.match(/```json\n([\s\S]*?)\n```/);
3006
- if (!jsonMatch) {
3007
- logger.printInfoLog("Launch agent response missing JSON block");
3008
- return Promise.resolve({
3009
- success: false,
3010
- error: "No JSON code block found in agent response"
3011
- });
3012
- }
3013
- const jsonStr = jsonMatch[1];
3014
- if (!jsonStr || jsonStr.trim().length === 0) {
3015
- logger.printInfoLog("Launch agent response has empty JSON");
3016
- return Promise.resolve({
3017
- success: false,
3018
- error: "Empty JSON content in code block"
3019
- });
3020
- }
3021
- try {
3022
- const parsed = JSON.parse(jsonStr);
3023
- if (parsed.success === true && (!parsed.serverKey || !parsed.url || !parsed.port)) {
3024
- logger.printInfoLog("Launch agent success response missing required fields");
3025
- return Promise.resolve({
3026
- success: false,
3027
- error: "Missing required fields (serverKey, url, or port)"
3028
- });
3029
- }
3030
- return Promise.resolve(parsed);
3031
- } catch (error) {
3032
- logger.printInfoLog(`Launch agent response JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
3033
- return Promise.resolve({
3034
- success: false,
3035
- error: error instanceof Error ? error.message : String(error)
3036
- });
3037
- }
3038
- }
3039
-
3040
- // src/agents/launch-agent/instruction.ts
3041
- function launchAgentInstruction(params) {
3042
- const skipNote = params.skipDependencyInstall ? "\n\nNOTE: Skip Step 1 (Install Dependencies). Dependencies already installed." : "";
3043
- return `appPath: ${params.appPath}${skipNote}
3044
-
3045
- TASK:
3046
- Install dependencies, compile the project, fix any compilation errors, start the development server, and fix any runtime errors. The goal is to have a fully working, error-free project.`;
3047
- }
3048
-
3049
- // src/agents/launch-agent/index.ts
3050
- function createLaunchAgent() {
3051
- const modelConfig = {
3052
- ...getModelConfig(),
3053
- contextWindowTokens: AGENT_CONTEXT_WINDOW_TOKENS,
3054
- maxOutputTokens: MAX_OUTPUT_TOKENS
3055
- };
3056
- return new Agent5({
3057
- name: "LaunchAgent",
3058
- profile: "Expert DevOps and Frontend Engineer specialist in launching projects and troubleshooting build/runtime issues.",
3059
- system: LAUNCH_AGENT_PROMPT,
3060
- tools: [
3061
- "CommandLineTool.execute",
3062
- "CommandLineTool.list",
3063
- "CommandLineTool.stop",
3064
- "LaunchTool.startDevServer",
3065
- "LaunchTool.stopDevServer",
3066
- "FileEditor.read",
3067
- "FileEditor.find",
3068
- "FileEditor.findAndReplace",
3069
- "FileEditor.write"
3070
- ],
3071
- modelConfig,
3072
- verbose: 2,
3073
- postProcessor: parseLaunchResult
3074
- });
3075
- }
3076
-
3077
- // src/nodes/validation/launch/index.ts
3078
- var launch = async (appPath, config) => {
3079
- if (!appPath) {
3080
- throw new Error("appPath is required");
3081
- }
3082
- try {
3083
- const agentResult = await createLaunchAgent().run(
3084
- launchAgentInstruction({
3085
- appPath,
3086
- skipDependencyInstall: config?.skipDependencyInstall
3087
- })
3088
- );
3089
- if (agentResult.success === false) {
3090
- return {
3091
- success: false,
3092
- error: agentResult.error ?? "Launch agent failed without error message"
3093
- };
3094
- }
3095
- return {
3096
- success: true,
3097
- message: agentResult.message ?? "Launch and quality assurance completed",
3098
- serverKey: agentResult.serverKey,
3099
- url: agentResult.url,
3100
- port: agentResult.port
3101
- };
3102
- } catch (error) {
3103
- return {
3104
- success: false,
3105
- error: error instanceof Error ? error.message : String(error)
3106
- };
3107
- }
3108
- };
3109
-
3110
- // src/nodes/validation/utils/extraction/extract-layout-metadata.ts
3111
- var LAYOUT_SCORES = {
3112
- FRAME_WITH_LAYOUT: 4,
3113
- NODE_WITH_LAYOUT: 3,
3114
- GROUP: 2,
3115
- DEFAULT: 1
3116
- };
3117
- function hasLayoutMode(element) {
3118
- return element.layoutMode !== "NONE" && element.layoutMode !== void 0;
3119
- }
3120
- function getLayoutScore(element) {
3121
- if (element.type === "FRAME" && hasLayoutMode(element)) {
3122
- return LAYOUT_SCORES.FRAME_WITH_LAYOUT;
3123
- }
3124
- if (hasLayoutMode(element)) {
3125
- return LAYOUT_SCORES.NODE_WITH_LAYOUT;
3126
- }
3127
- if (element.type === "GROUP") {
3128
- return LAYOUT_SCORES.GROUP;
3129
- }
3130
- return LAYOUT_SCORES.DEFAULT;
3131
- }
3132
- function findBestLayoutElement(elementIds, context) {
3133
- let bestElement;
3134
- let bestScore = -1;
3135
- for (const elemId of elementIds) {
3136
- const element = context.elements.get(elemId);
3137
- if (!element) continue;
3138
- const score = getLayoutScore(element);
3139
- if (score > bestScore) {
3140
- bestScore = score;
3141
- bestElement = element;
3142
- if (score === LAYOUT_SCORES.FRAME_WITH_LAYOUT) break;
3143
- }
3144
- }
3145
- return bestElement;
3146
- }
3147
- function extractLayoutFromContext(context, elementIds) {
3148
- const element = elementIds.length > 0 ? findBestLayoutElement(elementIds, context) : void 0;
3149
- if (!element) {
3150
- return {
3151
- layoutMode: "NONE",
3152
- primaryAxisAlignItems: "N/A",
3153
- counterAxisAlignItems: "N/A",
3154
- itemSpacing: 0,
3155
- padding: { top: 0, right: 0, bottom: 0, left: 0 },
3156
- constraints: {},
3157
- absoluteBoundingBox: {}
3158
- };
3159
- }
3160
- return {
3161
- layoutMode: element.layoutMode ?? "NONE",
3162
- primaryAxisAlignItems: element.primaryAxisAlignItems ?? "N/A",
3163
- counterAxisAlignItems: element.counterAxisAlignItems ?? "N/A",
3164
- itemSpacing: element.itemSpacing ?? 0,
3165
- padding: {
3166
- top: element.paddingTop ?? 0,
3167
- right: element.paddingRight ?? 0,
3168
- bottom: element.paddingBottom ?? 0,
3169
- left: element.paddingLeft ?? 0
3170
- },
3171
- constraints: element.constraints ?? {},
3172
- absoluteBoundingBox: {
3173
- x: element.position.x,
3174
- y: element.position.y,
3175
- width: element.position.w,
3176
- height: element.position.h
3177
- }
3178
- };
3179
- }
3180
-
3181
- // src/nodes/validation/report/index.ts
3182
- init_logger();
3183
-
3184
- // src/tools/report-tool/index.ts
3185
- init_logger();
3186
- import * as fs6 from "fs";
3187
- import * as fsPromises from "fs/promises";
3188
- import * as path8 from "path";
3189
- import { fileURLToPath } from "url";
3190
- import { tools as tools6 } from "evoltagent";
3191
-
3192
- // src/tools/visualization-tool/index.ts
3193
- import { tools as tools5 } from "evoltagent";
3194
- import * as path7 from "path";
3195
-
3196
- // src/tools/visualization-tool/utils/annotation-styles.ts
3197
- var ANNOTATION_COLORS = {
3198
- RED: "#ef4444",
3199
- // Tailwind red-500 - for current/render positions
3200
- GREEN: "#22c55e"
3201
- // Tailwind green-500 - for target/expected positions
3202
- };
3203
- function createContainerStyle() {
3204
- return `
3205
- position: absolute;
3206
- top: 0;
3207
- left: 0;
3208
- width: 100%;
3209
- height: 100%;
3210
- pointer-events: none;
3211
- z-index: 999999;
3212
- `;
3213
- }
3214
- function createBoxStyle(x, y, width, height, color) {
3215
- return `
3216
- position: absolute;
3217
- left: ${x}px;
3218
- top: ${y}px;
3219
- width: ${width}px;
3220
- height: ${height}px;
3221
- border: 4px solid ${color};
3222
- box-sizing: border-box;
3223
- pointer-events: none;
3224
- `;
3225
- }
3226
- function createCircleLabelStyle(x, y, color) {
3227
- const labelX = Math.max(5, x - 22);
3228
- const labelY = Math.max(5, y - 22);
3229
- return `
3230
- position: absolute;
3231
- left: ${labelX}px;
3232
- top: ${labelY}px;
3233
- background: ${color};
3234
- color: white;
3235
- width: 32px;
3236
- height: 32px;
3237
- display: flex;
3238
- align-items: center;
3239
- justify-content: center;
3240
- font: bold 18px Arial, sans-serif;
3241
- border-radius: 50%;
3242
- pointer-events: none;
3243
- border: 2px solid white;
3244
- box-shadow: 0 2px 4px rgba(0,0,0,0.3);
3245
- `;
3246
- }
3247
- function createTextLabelStyle(x, y, height, color) {
3248
- return `
3249
- position: absolute;
3250
- left: ${x}px;
3251
- top: ${y + height + 4}px;
3252
- background: ${color};
3253
- color: white;
3254
- padding: 4px 8px;
3255
- font: 12px Arial, sans-serif;
3256
- border-radius: 4px;
3257
- pointer-events: none;
3258
- white-space: nowrap;
3259
- box-shadow: 0 2px 4px rgba(0,0,0,0.3);
3260
- `;
3261
- }
3262
-
3263
- // src/tools/visualization-tool/utils/annotate-render.ts
3264
- async function annotateRenderWithPlaywright(page, misalignedData) {
3265
- await page.evaluate(
3266
- (data) => {
3267
- const { items, styles } = data;
3268
- const container = document.createElement("div");
3269
- container.id = "validation-annotations";
3270
- container.style.cssText = styles.container;
3271
- items.forEach((item) => {
3272
- const box = document.createElement("div");
3273
- const boxStyle = styles.boxes[item.index];
3274
- if (boxStyle) {
3275
- box.style.cssText = boxStyle;
3276
- }
3277
- container.appendChild(box);
3278
- const label = document.createElement("div");
3279
- label.textContent = `${item.index}`;
3280
- const circleLabelStyle = styles.circleLabels[item.index];
3281
- if (circleLabelStyle) {
3282
- label.style.cssText = circleLabelStyle;
3283
- }
3284
- container.appendChild(label);
3285
- const textLabel = document.createElement("div");
3286
- textLabel.textContent = `${item.componentName} (${item.distance.toFixed(1)}px)`;
3287
- const textLabelStyle = styles.textLabels[item.index];
3288
- if (textLabelStyle) {
3289
- textLabel.style.cssText = textLabelStyle;
3290
- }
3291
- container.appendChild(textLabel);
3292
- });
3293
- document.body.appendChild(container);
3294
- },
3295
- {
3296
- items: misalignedData,
3297
- styles: {
3298
- container: createContainerStyle(),
3299
- boxes: Object.fromEntries(
3300
- misalignedData.map((item) => [
3301
- item.index,
3302
- createBoxStyle(item.currentX, item.currentY, item.currentWidth, item.currentHeight, ANNOTATION_COLORS.RED)
3303
- ])
3304
- ),
3305
- circleLabels: Object.fromEntries(
3306
- misalignedData.map((item) => [item.index, createCircleLabelStyle(item.currentX, item.currentY, ANNOTATION_COLORS.RED)])
3307
- ),
3308
- textLabels: Object.fromEntries(
3309
- misalignedData.map((item) => [
3310
- item.index,
3311
- createTextLabelStyle(item.currentX, item.currentY, item.currentHeight, ANNOTATION_COLORS.RED)
3312
- ])
3313
- )
3314
- }
3315
- }
3316
- );
3317
- }
3318
-
3319
- // src/tools/visualization-tool/utils/image-converter.ts
3320
- import sharp from "sharp";
3321
- var SCREENSHOT_WEBP_QUALITY = 88;
3322
- async function captureAsWebP(page, options = {}) {
3323
- const pngBuffer = await page.screenshot({
3324
- type: "png",
3325
- fullPage: options.fullPage ?? false
3326
- });
3327
- return await bufferToWebPDataUri(pngBuffer);
3328
- }
3329
- async function bufferToWebPDataUri(buffer) {
3330
- const webpBuffer = await sharp(buffer).webp({ quality: SCREENSHOT_WEBP_QUALITY }).toBuffer();
3331
- return `data:image/webp;base64,${webpBuffer.toString("base64")}`;
3332
- }
3333
-
3334
- // src/tools/visualization-tool/utils/annotate-target.ts
3335
- async function annotateTargetWithPlaywright(browser, figmaThumbnailBase64, misalignedData, viewport, designOffset = { x: 0, y: 0 }, isThumbnailCropped = true) {
3336
- const context = await browser.newContext({ viewport });
3337
- const page = await context.newPage();
3338
- try {
3339
- const dataUri = figmaThumbnailBase64.startsWith("data:") ? figmaThumbnailBase64 : `data:image/png;base64,${figmaThumbnailBase64}`;
3340
- const html = `
3341
- <!DOCTYPE html>
3342
- <html>
3343
- <head>
3344
- <style>
3345
- * { margin: 0; padding: 0; }
3346
- body {
3347
- width: ${viewport.width}px;
3348
- height: ${viewport.height}px;
3349
- background-image: url('${dataUri}');
3350
- background-size: 100% 100%;
3351
- background-repeat: no-repeat;
3352
- background-position: top left;
3353
- position: relative;
3354
- overflow: hidden;
3355
- }
3356
- </style>
3357
- </head>
3358
- <body></body>
3359
- </html>
3360
- `;
3361
- await page.setContent(html);
3362
- await page.waitForLoadState("domcontentloaded");
3363
- const offsetX = isThumbnailCropped ? 0 : designOffset.x;
3364
- const offsetY = isThumbnailCropped ? 0 : designOffset.y;
3365
- const elementsData = misalignedData.map((item, i) => ({
3366
- index: i + 1,
3367
- componentName: item.componentName,
3368
- targetX: item.targetX + offsetX,
3369
- targetY: item.targetY + offsetY,
3370
- width: item.targetWidth,
3371
- height: item.targetHeight
3372
- }));
3373
- const styles = {
3374
- container: createContainerStyle(),
3375
- boxes: Object.fromEntries(
3376
- elementsData.map((item) => [
3377
- item.index,
3378
- createBoxStyle(item.targetX, item.targetY, item.width, item.height, ANNOTATION_COLORS.GREEN)
3379
- ])
3380
- ),
3381
- circleLabels: Object.fromEntries(
3382
- elementsData.map((item) => [item.index, createCircleLabelStyle(item.targetX, item.targetY, ANNOTATION_COLORS.GREEN)])
3383
- ),
3384
- textLabels: Object.fromEntries(
3385
- elementsData.map((item) => [
3386
- item.index,
3387
- createTextLabelStyle(item.targetX, item.targetY, item.height, ANNOTATION_COLORS.GREEN)
3388
- ])
3389
- )
3390
- };
3391
- await page.evaluate(
3392
- (data) => {
3393
- const { items, styles: styles2 } = data;
3394
- const container = document.createElement("div");
3395
- container.id = "target-annotations";
3396
- container.style.cssText = styles2.container;
3397
- items.forEach((item) => {
3398
- const box = document.createElement("div");
3399
- const boxStyle = styles2.boxes[item.index];
3400
- if (boxStyle) {
3401
- box.style.cssText = boxStyle;
3402
- }
3403
- container.appendChild(box);
3404
- const label = document.createElement("div");
3405
- label.textContent = `${item.index}`;
3406
- const circleLabelStyle = styles2.circleLabels[item.index];
3407
- if (circleLabelStyle) {
3408
- label.style.cssText = circleLabelStyle;
3409
- }
3410
- container.appendChild(label);
3411
- const textLabel = document.createElement("div");
3412
- textLabel.textContent = `${item.componentName}`;
3413
- const textLabelStyle = styles2.textLabels[item.index];
3414
- if (textLabelStyle) {
3415
- textLabel.style.cssText = textLabelStyle;
3416
- }
3417
- container.appendChild(textLabel);
3418
- });
3419
- document.body.appendChild(container);
3420
- },
3421
- { items: elementsData, styles }
3422
- );
3423
- const screenshotBuffer = await page.screenshot({ type: "png", fullPage: false });
3424
- return await bufferToWebPDataUri(screenshotBuffer);
3425
- } finally {
3426
- await page.close();
3427
- await context.close();
3428
- }
3429
- }
3430
-
3431
- // src/nodes/validation/utils/playwright/launcher.ts
3432
- init_logger();
3433
- import { chromium } from "playwright";
3434
- import { spawn } from "child_process";
3435
- import { createRequire } from "module";
3436
- import { dirname, join } from "path";
3437
- var MISSING_EXECUTABLE_MESSAGE = "Executable doesn't exist";
3438
- function getLocalPlaywrightCliPath() {
3439
- const require2 = createRequire(import.meta.url);
3440
- const playwrightMain = require2.resolve("playwright");
3441
- return join(dirname(playwrightMain), "cli.js");
3442
- }
3443
- var installPromise = null;
3444
- var installCompleted = false;
3445
- async function installChromiumBrowsers() {
3446
- if (installCompleted) return;
3447
- if (!installPromise) {
3448
- logger.printInfoLog("Playwright Chromium binaries are missing. Installing them automatically...");
3449
- installPromise = new Promise((resolve2, reject) => {
3450
- const cliPath = getLocalPlaywrightCliPath();
3451
- const child = spawn(process.execPath, [cliPath, "install", "chromium"], {
3452
- stdio: "inherit",
3453
- env: process.env
3454
- });
3455
- child.on("error", (error) => {
3456
- installPromise = null;
3457
- reject(error);
3458
- });
3459
- child.on("close", (code) => {
3460
- installPromise = null;
3461
- if (code === 0) {
3462
- installCompleted = true;
3463
- logger.printSuccessLog("Playwright Chromium installation finished.");
3464
- resolve2();
3465
- } else {
3466
- reject(new Error(`Playwright install exited with code ${code}`));
3467
- }
3468
- });
3469
- });
3470
- }
3471
- return installPromise;
3472
- }
3473
- function isMissingExecutableError(error) {
3474
- return error instanceof Error && error.message.includes(MISSING_EXECUTABLE_MESSAGE);
3475
- }
3476
- async function launchChromiumWithAutoInstall(options = {}) {
3477
- const launchOptions = { headless: true, ...options };
3478
- try {
3479
- return await chromium.launch(launchOptions);
3480
- } catch (error) {
3481
- if (isMissingExecutableError(error)) {
3482
- try {
3483
- await installChromiumBrowsers();
3484
- } catch (installError) {
3485
- throw new Error(
3486
- `Failed to auto-install Playwright browsers required for validation.
3487
- Please run "npx playwright install chromium" or "pnpm exec playwright install chromium" manually and retry.
3488
- Installer error: ${installError.message}`
3489
- );
3490
- }
3491
- return chromium.launch(launchOptions);
3492
- }
3493
- throw error;
3494
- }
3495
- }
3496
-
3497
- // src/tools/visualization-tool/utils/browser-management.ts
3498
- async function browserManagement(viewport, operation) {
3499
- let browser = null;
3500
- let page = null;
3501
- try {
3502
- browser = await launchChromiumWithAutoInstall({ headless: true });
3503
- const context = await browser.newContext({ viewport });
3504
- page = await context.newPage();
3505
- return await operation(browser, page);
3506
- } finally {
3507
- if (page) await page.close().catch(() => {
3508
- });
3509
- if (browser) await browser.close().catch(() => {
3510
- });
3511
- }
3512
- }
3513
-
3514
- // src/tools/visualization-tool/utils/combine.ts
3515
- import fs5 from "fs";
3516
- import path6 from "path";
3517
- import sharp2 from "sharp";
3518
- var DEFAULT_OPTIONS = {
3519
- leftHeader: "RENDER (Current)",
3520
- rightHeader: "TARGET (Expected)",
3521
- gapWidth: 40,
3522
- headerHeight: 60
3523
- };
3524
- async function combineSideBySide(renderBase64, targetBase64, outputPath, options) {
3525
- const opts = { ...DEFAULT_OPTIONS, ...options };
3526
- try {
3527
- const leftBase64 = extractBase64Data(renderBase64);
3528
- const rightBase64 = extractBase64Data(targetBase64);
3529
- const leftBuffer = Buffer.from(leftBase64, "base64");
3530
- const rightBuffer = Buffer.from(rightBase64, "base64");
3531
- const leftMeta = await sharp2(leftBuffer).metadata();
3532
- const rightMeta = await sharp2(rightBuffer).metadata();
3533
- if (!leftMeta.width || !leftMeta.height || !rightMeta.width || !rightMeta.height) {
3534
- throw new Error("Failed to read image dimensions");
3535
- }
3536
- const scale = leftMeta.height / rightMeta.height;
3537
- const rightResizedWidth = Math.round(rightMeta.width * scale);
3538
- const rightResized = await sharp2(rightBuffer).resize({ height: leftMeta.height, width: rightResizedWidth, fit: "fill" }).toBuffer();
3539
- const combinedWidth = leftMeta.width + opts.gapWidth + rightResizedWidth;
3540
- const combinedHeight = opts.headerHeight + leftMeta.height;
3541
- const centerX = leftMeta.width + opts.gapWidth / 2;
3542
- const leftTextX = leftMeta.width / 2;
3543
- const rightTextX = leftMeta.width + opts.gapWidth + rightResizedWidth / 2;
3544
- const textY = opts.headerHeight / 2 + 8;
3545
- const headerSvg = `
3546
- <svg width="${combinedWidth}" height="${opts.headerHeight}">
3547
- <rect width="${combinedWidth}" height="${opts.headerHeight}" fill="#F0F0F0"/>
3548
- <line x1="${centerX}" y1="0" x2="${centerX}" y2="${combinedHeight}"
3549
- stroke="#C8C8C8" stroke-width="2"/>
3550
- <text x="${leftTextX}" y="${textY}"
3551
- font-family="Arial" font-size="24" font-weight="bold" fill="#C80000"
3552
- text-anchor="middle">
3553
- ${opts.leftHeader}
3554
- </text>
3555
- <text x="${rightTextX}" y="${textY}"
3556
- font-family="Arial" font-size="24" font-weight="bold" fill="#009600"
3557
- text-anchor="middle">
3558
- ${opts.rightHeader}
3559
- </text>
3560
- </svg>
3561
- `;
3562
- const headerBuffer = Buffer.from(headerSvg);
3563
- const outputDir = path6.dirname(outputPath);
3564
- if (!fs5.existsSync(outputDir)) {
3565
- fs5.mkdirSync(outputDir, { recursive: true });
3566
- }
3567
- await sharp2({
3568
- create: {
3569
- width: combinedWidth,
3570
- height: combinedHeight,
3571
- channels: 3,
3572
- background: { r: 255, g: 255, b: 255 }
3573
- }
3574
- }).composite([
3575
- { input: headerBuffer, top: 0, left: 0 },
3576
- { input: leftBuffer, top: opts.headerHeight, left: 0 },
3577
- { input: rightResized, top: opts.headerHeight, left: leftMeta.width + opts.gapWidth }
3578
- ]).webp({ quality: SCREENSHOT_WEBP_QUALITY }).toFile(outputPath);
3579
- } catch (error) {
3580
- throw new Error(`Failed to combine side-by-side screenshots: ${error instanceof Error ? error.message : String(error)}`);
3581
- }
3582
- }
3583
- function extractBase64Data(base64String) {
3584
- const dataUriMatch = base64String.match(/^data:image\/[a-z]+;base64,(.+)$/);
3585
- if (dataUriMatch && dataUriMatch[1]) {
3586
- return dataUriMatch[1];
3587
- }
3588
- return base64String;
3589
- }
3590
-
3591
- // src/tools/visualization-tool/utils/pixel-diff-heatmap.ts
3592
- init_logger();
3593
- import sharp3 from "sharp";
3594
- import pixelmatch from "pixelmatch";
3595
- async function generatePixelDiffHeatmap(renderSnap, targetSnap) {
3596
- const img1Data = await loadImageData(renderSnap);
3597
- const img2Data = await loadImageData(targetSnap);
3598
- let { width: width1, height: height1, data: data1 } = img1Data;
3599
- let { width: width2, height: height2, data: data2 } = img2Data;
3600
- if (width1 !== width2 || height1 !== height2) {
3601
- const resized = await sharp3(data2, {
3602
- raw: { width: width2, height: height2, channels: 4 }
3603
- }).resize(width1, height1, { fit: "fill" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
3604
- data2 = resized.data;
3605
- width2 = resized.info.width;
3606
- height2 = resized.info.height;
3607
- }
3608
- const MAX_DIMENSION = 2500;
3609
- if (height1 > MAX_DIMENSION || width1 > MAX_DIMENSION) {
3610
- const scale = MAX_DIMENSION / Math.max(height1, width1);
3611
- const newWidth = Math.floor(width1 * scale);
3612
- const newHeight = Math.floor(height1 * scale);
3613
- const downsampled1 = await sharp3(data1, {
3614
- raw: { width: width1, height: height1, channels: 4 }
3615
- }).resize(newWidth, newHeight, { kernel: "lanczos3" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
3616
- const downsampled2 = await sharp3(data2, {
3617
- raw: { width: width2, height: height2, channels: 4 }
3618
- }).resize(newWidth, newHeight, { kernel: "lanczos3" }).ensureAlpha().raw().toBuffer({ resolveWithObject: true });
3619
- data1 = downsampled1.data;
3620
- data2 = downsampled2.data;
3621
- width1 = downsampled1.info.width;
3622
- height1 = downsampled1.info.height;
3623
- width2 = downsampled2.info.width;
3624
- height2 = downsampled2.info.height;
3625
- }
3626
- const diffBuffer = Buffer.alloc(width1 * height1 * 4);
3627
- const numDiffPixels = pixelmatch(data1, data2, diffBuffer, width1, height1, {
3628
- threshold: 0.1,
3629
- includeAA: false,
3630
- alpha: 0.1,
3631
- diffColor: [255, 0, 0],
3632
- diffMask: false
3633
- });
3634
- const totalPixels = width1 * height1;
3635
- const diffPercentage = (numDiffPixels / totalPixels * 100).toFixed(2);
3636
- logger.printTestLog(`[Pixelmatch] Different pixels: ${numDiffPixels} / ${totalPixels} (${diffPercentage}%)`);
3637
- const heatmapBuffer = await generateRedGreenOverlay(data1, data2, diffBuffer, width1, height1);
3638
- return await bufferToWebPDataUri(heatmapBuffer);
3639
- }
3640
- async function generateRedGreenOverlay(data1, data2, diffBuffer, width, height) {
3641
- const outputData = Buffer.alloc(width * height * 3);
3642
- const differences = new Float32Array(width * height);
3643
- let maxDiff = 0;
3644
- for (let i = 0; i < width * height; i++) {
3645
- const r1 = data1[i * 4] ?? 0;
3646
- const g1 = data1[i * 4 + 1] ?? 0;
3647
- const b1 = data1[i * 4 + 2] ?? 0;
3648
- const r2 = data2[i * 4] ?? 0;
3649
- const g2 = data2[i * 4 + 1] ?? 0;
3650
- const b2 = data2[i * 4 + 2] ?? 0;
3651
- const diff = Math.sqrt(Math.pow(r1 - r2, 2) * 0.299 + Math.pow(g1 - g2, 2) * 0.587 + Math.pow(b1 - b2, 2) * 0.114);
3652
- differences[i] = diff;
3653
- maxDiff = Math.max(maxDiff, diff);
3654
- }
3655
- let pixelsWithDifferences = 0;
3656
- for (let y = 0; y < height; y++) {
3657
- for (let x = 0; x < width; x++) {
3658
- const idx = y * width + x;
3659
- const outIdx = idx * 3;
3660
- const isDifferent = (diffBuffer[idx * 4] ?? 0) > 0;
3661
- if (isDifferent) {
3662
- pixelsWithDifferences++;
3663
- const normalizedDiff = maxDiff > 0 ? (differences[idx] ?? 0) / maxDiff : 0;
3664
- const [r, g, b] = getDiffColor(normalizedDiff);
3665
- outputData[outIdx] = r;
3666
- outputData[outIdx + 1] = g;
3667
- outputData[outIdx + 2] = b;
3668
- } else {
3669
- outputData[outIdx] = 0;
3670
- outputData[outIdx + 1] = 0;
3671
- outputData[outIdx + 2] = 200;
3672
- }
3673
- }
3674
- }
3675
- logger.printTestLog(
3676
- `[Heatmap] Pixels with differences: ${pixelsWithDifferences} / ${width * height} (${(pixelsWithDifferences / (width * height) * 100).toFixed(2)}%)`
3677
- );
3678
- return sharp3(outputData, { raw: { width, height, channels: 3 } }).png().toBuffer();
3679
- }
3680
- function getDiffColor(normalizedDiff) {
3681
- normalizedDiff = Math.max(0, Math.min(1, normalizedDiff));
3682
- if (normalizedDiff < 0.33) {
3683
- const t = normalizedDiff / 0.33;
3684
- return [0, Math.round(255 * t), 255];
3685
- } else if (normalizedDiff < 0.67) {
3686
- const t = (normalizedDiff - 0.33) / 0.34;
3687
- return [Math.round(255 * t), 255, Math.round(255 * (1 - t))];
3688
- } else {
3689
- const t = (normalizedDiff - 0.67) / 0.33;
3690
- return [255, Math.round(255 * (1 - t)), 0];
3691
- }
3692
- }
3693
- async function loadImageData(dataUri) {
3694
- if (!dataUri.startsWith("data:")) {
3695
- const axios4 = (await import("axios")).default;
3696
- const response = await axios4.get(dataUri, { responseType: "arraybuffer", timeout: 3e4 });
3697
- const base64 = Buffer.from(response.data).toString("base64");
3698
- dataUri = `data:image/png;base64,${base64}`;
3699
- }
3700
- const base64Data = dataUri.split(",")[1];
3701
- if (!base64Data) {
3702
- throw new Error("Invalid data URI format: missing base64 data");
3703
- }
3704
- const buffer = Buffer.from(base64Data, "base64");
3705
- const image = sharp3(buffer);
3706
- const { data, info } = await image.ensureAlpha().raw().toBuffer({ resolveWithObject: true });
3707
- return { width: info.width, height: info.height, data };
3708
- }
3709
-
3710
- // src/tools/visualization-tool/index.ts
3711
- init_logger();
3712
- var _VisualizationTool_decorators, _init5;
3713
- _VisualizationTool_decorators = [tools5({
3714
- annotateRender: {
3715
- description: "Navigate to the dev server, capture a clean render screenshot, inject RED annotation boxes for misaligned components, and capture the annotated render screenshot.",
3716
- params: [
3717
- { name: "serverUrl", type: "string", description: "Dev server URL (e.g., http://localhost:5173)" },
3718
- {
3719
- name: "misalignedData",
3720
- type: "object",
3721
- description: "MisalignedComponentData[] used to draw RED boxes on the render."
3722
- },
3723
- { name: "viewport", type: "object", description: "Viewport {width,height} to use in Playwright." }
3724
- ],
3725
- returns: {
3726
- type: "object",
3727
- description: "AnnotateRenderResult containing renderSnap and renderMarked (WebP data URIs)."
3728
- }
3729
- },
3730
- annotateTarget: {
3731
- description: "Fetch the Figma thumbnail, inject GREEN annotation boxes for misaligned components, and return an annotated target screenshot.",
3732
- params: [
3733
- { name: "figmaThumbnailUrl", type: "string", description: "Figma thumbnail CDN URL." },
3734
- {
3735
- name: "misalignedData",
3736
- type: "object",
3737
- description: "MisalignedComponentData[] used to draw GREEN boxes on the target."
3738
- },
3739
- { name: "viewport", type: "object", description: "Viewport {width,height} to match the Figma thumbnail." },
3740
- {
3741
- name: "designOffset",
3742
- type: "object",
3743
- description: "Design offset {x,y} used when the thumbnail is not cropped (default behavior is cropped).",
3744
- optional: true
3745
- }
3746
- ],
3747
- returns: { type: "string", description: "Annotated target screenshot as WebP data URI." }
3748
- },
3749
- annotateTargetFromBase64: {
3750
- description: "Annotate target using pre-cached base64 thumbnail data. Use this to avoid redundant Figma thumbnail downloads.",
3751
- params: [
3752
- { name: "figmaThumbnailBase64", type: "string", description: "Base64-encoded Figma thumbnail data." },
3753
- {
3754
- name: "misalignedData",
3755
- type: "object",
3756
- description: "MisalignedComponentData[] used to draw GREEN boxes on the target."
3757
- },
3758
- { name: "viewport", type: "object", description: "Viewport {width,height} to match the Figma thumbnail." },
3759
- {
3760
- name: "designOffset",
3761
- type: "object",
3762
- description: "Design offset {x,y} used when the thumbnail is not cropped (default behavior is cropped).",
3763
- optional: true
3764
- }
3765
- ],
3766
- returns: { type: "string", description: "Annotated target screenshot as WebP data URI." }
3767
- },
3768
- combine: {
3769
- description: "Combine two base64 screenshots side-by-side with headers and write to outputPath (WebP).",
3770
- params: [
3771
- { name: "renderMarked", type: "string", description: "Left image (render, typically annotated) as base64 data URI." },
3772
- { name: "targetMarked", type: "string", description: "Right image (target, typically annotated) as base64 data URI." },
3773
- { name: "outputPath", type: "string", description: "Filesystem output path for the combined WebP." },
3774
- { name: "options", type: "object", description: "Optional CombineOptions", optional: true }
3775
- ],
3776
- returns: { type: "string", description: "The outputPath written." }
3777
- },
3778
- diffHeatmap: {
3779
- description: "Generate pixel-diff heatmap between a render screenshot and the target thumbnail.",
3780
- params: [
3781
- { name: "renderSnap", type: "string", description: "Render screenshot (WebP data URI)." },
3782
- { name: "targetSnap", type: "string", description: "Target screenshot (WebP data URI) OR URL." }
3783
- ],
3784
- returns: { type: "string", description: "Heatmap image as WebP data URI." }
3785
- },
3786
- generateIterationScreenshot: {
3787
- description: "Generate annotated comparison screenshot for validation iteration. Orchestrates annotate + combine for convenience.",
3788
- params: [
3789
- { name: "misalignedComponents", type: "object", description: "MisalignedComponent[] from validation result" },
3790
- { name: "serverUrl", type: "string", description: "Dev server URL" },
3791
- { name: "figmaThumbnailUrl", type: "string", description: "Figma thumbnail URL" },
3792
- { name: "viewport", type: "object", description: "Viewport {width, height}" },
3793
- { name: "designOffset", type: "object", description: "Design offset {x, y}" },
3794
- { name: "outputPath", type: "string", description: "Output file path for combined screenshot" },
3795
- {
3796
- name: "cachedFigmaThumbnailBase64",
3797
- type: "string",
3798
- description: "Optional cached thumbnail base64",
3799
- optional: true
3800
- }
3801
- ],
3802
- returns: { type: "object", description: "IterationScreenshotResult with renderMarked, targetMarked, and combinedPath" }
3803
- }
3804
- })];
3805
- var VisualizationTool = class {
3806
- async annotateRender(serverUrl, misalignedData, viewport) {
3807
- return await browserManagement(viewport, async (_browser, page) => {
3808
- await page.goto(serverUrl, { waitUntil: "domcontentloaded", timeout: 3e4 });
3809
- await new Promise((r) => setTimeout(r, 1e3));
3810
- const renderSnap = await captureAsWebP(page);
3811
- await annotateRenderWithPlaywright(page, misalignedData);
3812
- const renderMarked = await captureAsWebP(page);
3813
- return { renderSnap, renderMarked };
3814
- });
3815
- }
3816
- async annotateTarget(figmaThumbnailUrl, misalignedData, viewport, designOffset) {
3817
- const axios4 = (await import("axios")).default;
3818
- const response = await axios4.get(figmaThumbnailUrl, { responseType: "arraybuffer", timeout: 3e4 });
3819
- const figmaThumbnailBase64 = Buffer.from(response.data).toString("base64");
3820
- return await browserManagement(viewport, async (browser) => {
3821
- return await annotateTargetWithPlaywright(browser, figmaThumbnailBase64, misalignedData, viewport, designOffset);
3822
- });
3823
- }
3824
- /**
3825
- * Annotate target using pre-cached base64 thumbnail data.
3826
- * Use this method when you have already downloaded the Figma thumbnail to avoid redundant downloads.
3827
- */
3828
- async annotateTargetFromBase64(figmaThumbnailBase64, misalignedData, viewport, designOffset) {
3829
- return await browserManagement(viewport, async (browser) => {
3830
- return await annotateTargetWithPlaywright(browser, figmaThumbnailBase64, misalignedData, viewport, designOffset);
3831
- });
3832
- }
3833
- async combine(renderMarked, targetMarked, outputPath, options) {
3834
- await combineSideBySide(renderMarked, targetMarked, outputPath, options);
3835
- return outputPath;
3836
- }
3837
- async diffHeatmap(renderSnap, targetSnap) {
3838
- return await generatePixelDiffHeatmap(renderSnap, targetSnap);
3839
- }
3840
- /**
3841
- * Generate annotated comparison screenshot for a single iteration.
3842
- * This is a convenience method that orchestrates the full screenshot workflow.
3843
- *
3844
- * @returns IterationScreenshotResult with individual annotated screenshots and combined path
3845
- */
3846
- async generateIterationScreenshot(misalignedComponents, serverUrl, figmaThumbnailUrl, viewport, designOffset, outputPath, cachedFigmaThumbnailBase64) {
3847
- if (misalignedComponents.length === 0) {
3848
- return { renderMarked: "", targetMarked: "", combinedPath: "" };
3849
- }
3850
- try {
3851
- const misalignedData = this.formatForVisualization(misalignedComponents);
3852
- const render = await this.annotateRender(serverUrl, misalignedData, viewport);
3853
- const targetMarked = cachedFigmaThumbnailBase64 ? await this.annotateTargetFromBase64(cachedFigmaThumbnailBase64, misalignedData, viewport, designOffset) : await this.annotateTarget(figmaThumbnailUrl, misalignedData, viewport, designOffset);
3854
- await this.combine(render.renderMarked, targetMarked, outputPath);
3855
- logger.printInfoLog(`Saved comparison screenshot: ${path7.basename(outputPath)}`);
3856
- return {
3857
- renderMarked: render.renderMarked,
3858
- targetMarked,
3859
- combinedPath: outputPath
3860
- };
3861
- } catch (error) {
3862
- const errorMsg = error instanceof Error ? error.message : String(error);
3863
- logger.printWarnLog(`Failed to generate iteration screenshot: ${errorMsg}`);
3864
- return { renderMarked: "", targetMarked: "", combinedPath: "" };
3865
- }
3866
- }
3867
- /**
3868
- * Transform MisalignedComponent[] to MisalignedComponentData[] format.
3869
- * This is an internal helper for converting validation results to visualization format.
3870
- */
3871
- formatForVisualization(components) {
3872
- return components.map((comp, idx) => {
3873
- if (comp.elementIds.length === 0) {
3874
- logger.printWarnLog(`Component ${comp.name} has no elementIds. Using componentId as fallback.`);
3875
- }
3876
- return {
3877
- index: idx + 1,
3878
- elementId: comp.elementIds[0] || comp.componentId,
3879
- elementName: comp.name,
3880
- componentId: comp.componentId,
3881
- componentName: comp.name,
3882
- componentPath: comp.path,
3883
- currentX: comp.currentX,
3884
- currentY: comp.currentY,
3885
- currentWidth: comp.currentWidth,
3886
- currentHeight: comp.currentHeight,
3887
- targetX: comp.targetX,
3888
- targetY: comp.targetY,
3889
- targetWidth: comp.targetWidth,
3890
- targetHeight: comp.targetHeight,
3891
- distance: comp.distance,
3892
- xDelta: comp.validationReport.absoluteError[0],
3893
- yDelta: comp.validationReport.absoluteError[1]
3894
- };
3895
- });
3896
- }
3897
- };
3898
- _init5 = __decoratorStart(null);
3899
- VisualizationTool = __decorateElement(_init5, 0, "VisualizationTool", _VisualizationTool_decorators, VisualizationTool);
3900
- __runInitializers(_init5, 1, VisualizationTool);
3901
-
3902
- // src/tools/report-tool/index.ts
3903
- var _ReportTool_decorators, _init6;
3904
- _ReportTool_decorators = [tools6({
3905
- generateReport: {
3906
- description: "Generate visual validation report structure from pre-computed validation results. Creates annotated screenshots, heatmap, and userReport structure.",
3907
- params: [
3908
- {
3909
- name: "request",
3910
- type: "object",
3911
- description: "ReportGenerationRequest with validationResult, figmaThumbnailUrl, serverUrl, outputDir, etc."
3912
- }
3913
- ],
3914
- returns: { type: "object", description: "ReportGenerationResult with userReport and misalignedCount" }
3915
- },
3916
- createMinimalReport: {
3917
- description: "Create a minimal user report for error scenarios without screenshots",
3918
- params: [{ name: "options", type: "object", description: "ErrorReportOptions with serverUrl, figmaThumbnailUrl, mae, sae" }],
3919
- returns: { type: "object", description: "Minimal UserReport structure" }
3920
- },
3921
- generateHtml: {
3922
- description: "Generate standalone HTML report file from userReport with embedded data and inlined assets",
3923
- params: [
3924
- { name: "userReport", type: "object", description: "UserReport data from validation with screenshots and metrics" },
3925
- { name: "outputDir", type: "string", description: "Output directory path for HTML file" }
3926
- ],
3927
- returns: { type: "object", description: "GenerateHtmlResult with success/htmlPath/error" }
3928
- }
3929
- })];
3930
- var ReportTool = class {
3931
- /**
3932
- * Generate visual validation report from validation results.
3933
- *
3934
- * This method ONLY does visualization and formatting:
3935
- * - Loads saved annotated screenshots from last validation iteration (avoids port mismatch)
3936
- * - Generates pixel difference heatmap
3937
- * - Builds userReport structure
3938
- *
3939
- */
3940
- async generateReport(request) {
3941
- logger.printInfoLog("\nGenerating validation report...");
3942
- const visualizationTool = new VisualizationTool();
3943
- const { validationResult } = request;
3944
- const comparisonDir = path8.join(request.outputDir, "comparison_screenshots");
3945
- if (!fs6.existsSync(comparisonDir)) {
3946
- fs6.mkdirSync(comparisonDir, { recursive: true });
3947
- }
3948
- const misalignedData = visualizationTool["formatForVisualization"](validationResult.misalignedComponents);
3949
- logger.printInfoLog(`Final misaligned components: ${misalignedData.length}`);
3950
- const renderMarkedBuffer = await fsPromises.readFile(request.savedRenderMarkedPath);
3951
- const targetMarkedBuffer = await fsPromises.readFile(request.savedTargetMarkedPath);
3952
- const renderMarked = `data:image/webp;base64,${renderMarkedBuffer.toString("base64")}`;
3953
- const targetMarked = `data:image/webp;base64,${targetMarkedBuffer.toString("base64")}`;
3954
- const screenshots = {
3955
- renderSnap: validationResult.screenshots?.renderSnap || renderMarked,
3956
- renderMarked,
3957
- targetMarked
3958
- };
3959
- const finalScreenshotPath = path8.join(comparisonDir, "final.webp");
3960
- await visualizationTool.combine(screenshots.renderMarked, screenshots.targetMarked, finalScreenshotPath);
3961
- logger.printSuccessLog(`Saved final comparison screenshot: ${path8.basename(finalScreenshotPath)}`);
3962
- let heatmap = "";
3963
- try {
3964
- heatmap = await visualizationTool.diffHeatmap(screenshots.renderSnap, request.figmaThumbnailUrl);
3965
- if (heatmap) {
3966
- const heatmapPath = path8.join(comparisonDir, "heatmap.webp");
3967
- const base64Data = heatmap.split(",")[1];
3968
- if (!base64Data) {
3969
- throw new Error("Invalid heatmap data URI format");
3970
- }
3971
- const buffer = Buffer.from(base64Data, "base64");
3972
- await fs6.promises.writeFile(heatmapPath, buffer);
3973
- logger.printSuccessLog(`Saved pixel difference heatmap: ${path8.basename(heatmapPath)}`);
3974
- }
3975
- } catch (heatmapError) {
3976
- const errorMsg = heatmapError instanceof Error ? heatmapError.message : "Unknown error";
3977
- logger.printWarnLog(`Failed to generate pixel difference heatmap: ${errorMsg}. Continuing without heatmap.`);
3978
- }
3979
- const userReport = this.buildUserReportStructure(
3980
- validationResult.mae,
3981
- validationResult.sae,
3982
- misalignedData.length,
3983
- screenshots,
3984
- heatmap,
3985
- request.serverUrl,
3986
- request.figmaThumbnailUrl,
3987
- misalignedData
3988
- );
3989
- logger.printSuccessLog("Validation report generated successfully");
3990
- return {
3991
- userReport,
3992
- misalignedCount: misalignedData.length
3993
- };
3994
- }
3995
- /**
3996
- * Creates a minimal user report for error scenarios.
3997
- */
3998
- createMinimalReport(options) {
3999
- return {
4000
- design: {
4001
- snap: options.figmaThumbnailUrl || "",
4002
- markedSnap: ""
4003
- },
4004
- page: {
4005
- url: options.serverUrl,
4006
- snap: "",
4007
- markedSnap: ""
4008
- },
4009
- report: {
4010
- heatmap: "",
4011
- detail: {
4012
- metrics: {
4013
- mae: options.mae ?? -1,
4014
- sae: options.sae ?? -1,
4015
- misalignedCount: 0
4016
- },
4017
- components: []
4018
- }
4019
- }
4020
- };
4021
- }
4022
- /**
4023
- * Generate standalone HTML report file from userReport.
4024
- * Inlines all assets (JS/CSS) for single-file distribution.
4025
- */
4026
- async generateHtml(userReport, outputDir) {
4027
- const reportDistDir = this.getReportDistDir();
4028
- const reportIndexHtml = path8.join(reportDistDir, "index.html");
4029
- logger.printInfoLog(`[ReportTool] Using template: ${reportIndexHtml}`);
4030
- logger.printInfoLog(`[ReportTool] Output directory: ${outputDir}`);
4031
- try {
4032
- try {
4033
- await fsPromises.access(reportIndexHtml);
4034
- } catch {
4035
- const errorMsg = `Template not found at ${reportIndexHtml}`;
4036
- logger.printErrorLog(`[ReportTool] ${errorMsg}. Skipping report generation.`);
4037
- return { success: false, error: errorMsg };
4038
- }
4039
- await fsPromises.mkdir(outputDir, { recursive: true });
4040
- let reportData = userReport;
4041
- if (reportData) {
4042
- reportData = this.transformReportData(reportData);
4043
- }
4044
- logger.printTestLog(`Report data: ${JSON.stringify(reportData)}`);
4045
- let htmlContent = await fsPromises.readFile(reportIndexHtml, "utf-8");
4046
- const jsonString = JSON.stringify(reportData).replace(/<\/script>/gi, "<\\/script>").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
4047
- const scriptTag = `<script>window.__REPORT_DATA__ = ${jsonString};</script>`;
4048
- htmlContent = htmlContent.replace(/<script>\s*window\.__REPORT_DATA__\s*=\s*null;?\s*<\/script>/, scriptTag);
4049
- const scriptRegexGlobal = /<script\s+type="module"\s+crossorigin\s+src="\/assets\/([^"]+)"><\/script>/g;
4050
- const jsMatches = [...htmlContent.matchAll(scriptRegexGlobal)];
4051
- const jsContentsMap = /* @__PURE__ */ new Map();
4052
- for (const match of jsMatches) {
4053
- const fileName = match[1];
4054
- if (fileName) {
4055
- const jsFilePath = path8.join(reportDistDir, "assets", fileName);
4056
- try {
4057
- let jsContent = await fsPromises.readFile(jsFilePath, "utf-8");
4058
- jsContent = jsContent.replace(/<\/script>/g, "\\u003c/script>");
4059
- jsContentsMap.set(fileName, jsContent);
4060
- } catch {
4061
- logger.printWarnLog(`[ReportTool] JS asset not found: ${jsFilePath}`);
4062
- }
4063
- }
4064
- }
4065
- htmlContent = htmlContent.replace(scriptRegexGlobal, (match, fileName) => {
4066
- const jsContent = jsContentsMap.get(fileName);
4067
- if (jsContent) {
4068
- return `<script type="module">${jsContent}</script>`;
4069
- }
4070
- return match;
4071
- });
4072
- const cssRegex = /<link\s+rel="stylesheet"\s+crossorigin\s+href="\/assets\/(.*?)">/;
4073
- const cssMatch = htmlContent.match(cssRegex);
4074
- if (cssMatch) {
4075
- const cssFileName = cssMatch[1];
4076
- if (!cssFileName) {
4077
- logger.printWarnLog("[ReportTool] CSS filename not found in match");
4078
- } else {
4079
- const cssFilePath = path8.join(reportDistDir, "assets", cssFileName);
4080
- try {
4081
- const cssContent = await fsPromises.readFile(cssFilePath, "utf-8");
4082
- htmlContent = htmlContent.replace(cssMatch[0], `<style>${cssContent}</style>`);
4083
- } catch {
4084
- }
4085
- }
4086
- }
4087
- htmlContent = htmlContent.replace('<link rel="stylesheet" href="/index.css">', "");
4088
- const absoluteOutputPath = path8.join(outputDir, "index.html");
4089
- await fsPromises.writeFile(absoluteOutputPath, htmlContent);
4090
- return { success: true, htmlPath: absoluteOutputPath };
4091
- } catch (error) {
4092
- const errorMsg = error instanceof Error ? error.message : String(error);
4093
- logger.printErrorLog(`[ReportTool] Failed to generate report: ${errorMsg}`);
4094
- return { success: false, error: errorMsg };
4095
- }
4096
- }
4097
- // ==================== PRIVATE HELPER METHODS ====================
4098
- /**
4099
- * Build userReport structure from validation results
4100
- */
4101
- buildUserReportStructure(mae, sae, misalignedCount, screenshots, heatmap, serverUrl, figmaThumbnailUrl, misalignedData) {
4102
- return {
4103
- design: {
4104
- snap: figmaThumbnailUrl,
4105
- markedSnap: screenshots.targetMarked
4106
- },
4107
- page: {
4108
- url: serverUrl,
4109
- snap: screenshots.renderSnap,
4110
- markedSnap: screenshots.renderMarked
4111
- },
4112
- report: {
4113
- heatmap,
4114
- detail: {
4115
- metrics: {
4116
- mae,
4117
- sae,
4118
- misalignedCount
4119
- },
4120
- components: misalignedData.map((c) => ({
4121
- componentId: c.componentId,
4122
- componentPath: c.componentPath,
4123
- elementId: c.elementId,
4124
- validationInfo: {
4125
- x: c.xDelta,
4126
- y: c.yDelta
4127
- }
4128
- }))
4129
- }
4130
- }
4131
- };
4132
- }
4133
- /**
4134
- * Transform report data by grouping components by componentId
4135
- */
4136
- transformReportData(reportData) {
4137
- const components = reportData?.report?.detail?.components || [];
4138
- if (!components?.length) return reportData;
4139
- const groupedComponentsMap = /* @__PURE__ */ new Map();
4140
- components.forEach((component, index) => {
4141
- const { componentId, componentPath, elementId, validationInfo } = component;
4142
- const elementIndex = index + 1;
4143
- if (!groupedComponentsMap.has(componentId)) {
4144
- groupedComponentsMap.set(componentId, {
4145
- componentId,
4146
- componentPath,
4147
- elements: []
4148
- });
4149
- }
4150
- groupedComponentsMap.get(componentId)?.elements?.push({
4151
- elementId: elementId || "",
4152
- elementIndex,
4153
- validationInfo: validationInfo || { x: 0, y: 0 }
4154
- });
4155
- });
4156
- const groupedComponents = Array.from(groupedComponentsMap.values());
4157
- return {
4158
- ...reportData,
4159
- report: {
4160
- ...reportData.report,
4161
- detail: {
4162
- ...reportData.report.detail,
4163
- components: groupedComponents
4164
- }
4165
- }
4166
- };
4167
- }
4168
- /**
4169
- * Determine the root directory of the package by walking up to find package.json
4170
- */
4171
- getPackageRoot() {
4172
- const __filename = fileURLToPath(import.meta.url);
4173
- const __dirname = path8.dirname(__filename);
4174
- let currentDir = __dirname;
4175
- while (currentDir !== path8.parse(currentDir).root) {
4176
- if (fs6.existsSync(path8.join(currentDir, "package.json"))) {
4177
- return currentDir;
4178
- }
4179
- currentDir = path8.dirname(currentDir);
4180
- }
4181
- return process.cwd();
4182
- }
4183
- /**
4184
- * Get the report dist directory containing built HTML/assets
4185
- */
4186
- getReportDistDir() {
4187
- const root = this.getPackageRoot();
4188
- const paths = [
4189
- path8.join(root, "dist/tools/report-tool/template"),
4190
- // Production layout (if copied)
4191
- path8.join(root, "src/tools/report-tool/template/dist")
4192
- // Development layout
4193
- ];
4194
- for (const p of paths) {
4195
- if (fs6.existsSync(path8.join(p, "index.html"))) {
4196
- return p;
4197
- }
4198
- }
4199
- return path8.join(root, "src/tools/report-tool/template/dist");
4200
- }
4201
- };
4202
- _init6 = __decoratorStart(null);
4203
- ReportTool = __decorateElement(_init6, 0, "ReportTool", _ReportTool_decorators, ReportTool);
4204
- __runInitializers(_init6, 1, ReportTool);
4205
-
4206
- // src/nodes/validation/report/index.ts
4207
- async function report(options) {
4208
- const tool = new ReportTool();
4209
- try {
4210
- logger.printInfoLog("Generating validation report structure...");
4211
- const reportResult = await tool.generateReport({
4212
- validationResult: options.validationResult,
4213
- figmaThumbnailUrl: options.figmaThumbnailUrl,
4214
- cachedFigmaThumbnailBase64: options.cachedFigmaThumbnailBase64,
4215
- designOffset: options.designOffset,
4216
- outputDir: options.outputDir,
4217
- serverUrl: options.serverUrl,
4218
- savedRenderMarkedPath: options.savedRenderMarkedPath,
4219
- savedTargetMarkedPath: options.savedTargetMarkedPath
4220
- });
4221
- logger.printInfoLog("Generating HTML report file...");
4222
- const htmlResult = await tool.generateHtml(reportResult.userReport, options.outputDir);
4223
- if (!htmlResult.success) {
4224
- logger.printWarnLog(`Failed to generate HTML: ${htmlResult.error}`);
4225
- return {
4226
- success: false,
4227
- error: htmlResult.error,
4228
- userReport: reportResult.userReport
4229
- };
4230
- }
4231
- logger.printSuccessLog(`Report generated successfully: ${htmlResult.htmlPath}`);
4232
- return {
4233
- success: true,
4234
- htmlPath: htmlResult.htmlPath,
4235
- userReport: reportResult.userReport
4236
- };
4237
- } catch (error) {
4238
- const errorMessage = error instanceof Error ? error.message : String(error);
4239
- logger.printErrorLog(`Error generating report: ${errorMessage}`);
4240
- const minimalReport = tool.createMinimalReport({
4241
- serverUrl: options.serverUrl,
4242
- figmaThumbnailUrl: options.figmaThumbnailUrl,
4243
- mae: options.validationResult?.mae,
4244
- sae: options.validationResult?.sae
4245
- });
4246
- return {
4247
- success: false,
4248
- error: errorMessage,
4249
- userReport: minimalReport
4250
- };
4251
- }
4252
- }
4253
-
4254
- // src/tools/launch-tool/index.ts
4255
- import * as path9 from "path";
4256
- import { tools as tools7 } from "evoltagent";
4257
-
4258
- // src/tools/launch-tool/utils/dev-server-manager.ts
4259
- import * as http from "http";
4260
- import * as net from "net";
4261
- import { spawn as spawn2 } from "child_process";
4262
- init_logger();
4263
- function normalizeExecutable(executable) {
4264
- if (process.platform !== "win32") {
4265
- return executable;
4266
- }
4267
- const lower = executable.toLowerCase();
4268
- if (lower === "npm" || lower === "pnpm" || lower === "yarn" || lower === "npx") {
4269
- return `${lower}.cmd`;
4270
- }
4271
- return executable;
4272
- }
4273
- function parseCommandWithPortSupport(commandStr) {
4274
- const parts = commandStr.trim().split(/\s+/);
4275
- const executable = parts[0];
4276
- if (!executable) {
4277
- throw new Error("Failed to parse command: empty command string");
4278
- }
4279
- const args = parts.slice(1);
4280
- const needsDoubleDash = ["npm", "pnpm", "yarn"].includes(executable);
4281
- return { executable, args, needsDoubleDash };
4282
- }
4283
- async function isPortAvailable(port) {
4284
- return await new Promise((resolve2) => {
4285
- const server = net.createServer();
4286
- server.unref();
4287
- server.on("error", () => resolve2(false));
4288
- server.listen(port, () => {
4289
- server.close(() => resolve2(true));
4290
- });
4291
- });
4292
- }
4293
- function calculateHashPort(workspacePath, basePort = 5200) {
4294
- let hash = 0;
4295
- for (let i = 0; i < workspacePath.length; i++) {
4296
- hash = (hash << 5) - hash + workspacePath.charCodeAt(i);
4297
- hash = hash & hash;
4298
- }
4299
- const offset = Math.abs(hash) % 1e3;
4300
- return basePort + offset;
4301
- }
4302
- async function getFreeHashPort(workspacePath, _startPort = DEFAULT_PORT) {
4303
- const preferredPort = calculateHashPort(workspacePath);
4304
- if (await isPortAvailable(preferredPort)) {
4305
- logger.printInfoLog(`Using workspace-preferred port: ${preferredPort}`);
4306
- return preferredPort;
4307
- }
4308
- logger.printInfoLog(`Preferred port ${preferredPort} occupied, trying nearby ports...`);
4309
- for (let offset = 1; offset <= 10; offset++) {
4310
- const portUp = preferredPort + offset;
4311
- const portDown = preferredPort - offset;
4312
- if (await isPortAvailable(portUp)) {
4313
- logger.printInfoLog(`Using nearby port: ${portUp} (preferred was ${preferredPort})`);
4314
- return portUp;
4315
- }
4316
- if (portDown > 1024 && await isPortAvailable(portDown)) {
4317
- logger.printInfoLog(`Using nearby port: ${portDown} (preferred was ${preferredPort})`);
4318
- return portDown;
4319
- }
4320
- }
4321
- throw new Error(`No available ports found in workspace-preferred range (${preferredPort - 10} to ${preferredPort + 10})`);
4322
- }
4323
- async function waitForServerReady(url, timeoutMs) {
4324
- const start = Date.now();
4325
- const intervalMs = 400;
4326
- while (Date.now() - start < timeoutMs) {
4327
- const ok = await new Promise((resolve2) => {
4328
- const req = http.get(url, (res) => {
4329
- res.resume();
4330
- resolve2(res.statusCode !== void 0 && res.statusCode >= 200 && res.statusCode < 500);
4331
- });
4332
- req.on("error", () => resolve2(false));
4333
- req.setTimeout(500, () => req.destroy());
4334
- });
4335
- if (ok) return true;
4336
- await new Promise((r) => setTimeout(r, intervalMs));
4337
- }
4338
- return false;
4339
- }
4340
- var DevServerManager = class {
4341
- constructor(params) {
4342
- this.params = params;
4343
- }
4344
- handle = null;
4345
- get current() {
4346
- return this.handle;
4347
- }
4348
- async start(options = { timeoutMs: 6e4 }) {
4349
- if (this.handle) {
4350
- return this.handle;
4351
- }
4352
- const port = await getFreeHashPort(this.params.appPath);
4353
- const url = buildDevServerUrl(port);
4354
- const { executable, args, needsDoubleDash } = parseCommandWithPortSupport(this.params.runCommand);
4355
- const normalizedExecutable = normalizeExecutable(executable);
4356
- const commandArgs = needsDoubleDash ? [...args, "--", "--port", String(port)] : [...args, "--port", String(port)];
4357
- logger.printInfoLog(`Starting dev server: ${normalizedExecutable} ${commandArgs.join(" ")}`);
4358
- const child = spawn2(normalizedExecutable, commandArgs, {
4359
- cwd: this.params.appPath,
4360
- stdio: ["ignore", "pipe", "pipe"],
4361
- env: {
4362
- ...process.env,
4363
- PORT: String(port),
4364
- FORCE_COLOR: "1"
4365
- }
4366
- });
4367
- const maxBytes = 1e6;
4368
- let out = "";
4369
- const push = (chunk) => {
4370
- out += chunk.toString("utf-8");
4371
- if (out.length > maxBytes) {
4372
- out = out.slice(out.length - maxBytes);
4373
- }
4374
- };
4375
- child.stdout?.on("data", push);
4376
- child.stderr?.on("data", push);
4377
- process.once("exit", () => {
4378
- try {
4379
- child.kill("SIGTERM");
4380
- } catch {
4381
- }
4382
- });
4383
- const ready = await waitForServerReady(url, options.timeoutMs);
4384
- if (!ready) {
4385
- const tail = out.trim();
4386
- try {
4387
- child.kill("SIGTERM");
4388
- } catch {
4389
- }
4390
- throw new Error(`Dev server did not become ready at ${url} within ${options.timeoutMs}ms.
4391
- ${tail}`);
4392
- }
4393
- this.handle = {
4394
- child,
4395
- port,
4396
- url,
4397
- outputTail: () => out.trim()
4398
- };
4399
- logger.printSuccessLog(`Dev server ready at ${url}`);
4400
- return this.handle;
4401
- }
4402
- async stop() {
4403
- if (!this.handle) return;
4404
- const child = this.handle.child;
4405
- this.handle = null;
4406
- await new Promise((resolve2) => {
4407
- try {
4408
- child.once("close", () => resolve2());
4409
- child.kill("SIGTERM");
4410
- } catch {
4411
- resolve2();
4412
- }
4413
- setTimeout(() => resolve2(), 1500);
4414
- });
4415
- }
4416
- async restart(options = { timeoutMs: 6e4 }) {
4417
- await this.stop();
4418
- return await this.start(options);
4419
- }
4420
- };
4421
-
4422
- // src/tools/launch-tool/index.ts
4423
- function makeServerKey(appPath) {
4424
- const safeApp = appPath.split(path9.sep).join("_");
4425
- return `launch:${safeApp}`;
4426
- }
4427
- var _LaunchTool_decorators, _init7;
4428
- _LaunchTool_decorators = [tools7({
4429
- startDevServer: {
4430
- description: "Start a dev server in-process and wait for it to become reachable.",
4431
- params: [
4432
- { name: "appPath", type: "string", description: "Absolute path to the app directory" },
4433
- { name: "runCommand", type: "string", description: 'Run command (e.g. "pnpm dev")' },
4434
- { name: "timeoutMs", type: "number", description: "Timeout in milliseconds", optional: true }
4435
- ],
4436
- returns: { type: "string", description: "JSON string containing StartDevServerResult with url/port/pid/serverKey." }
4437
- },
4438
- stopDevServer: {
4439
- description: "Stop a previously started dev server by serverKey.",
4440
- params: [{ name: "serverKey", type: "string", description: "Server key returned by startDevServer()" }],
4441
- returns: { type: "string", description: "JSON string containing StopDevServerResult" }
4442
- }
4443
- })];
4444
- var _LaunchTool = class _LaunchTool {
4445
- static serverManagers = /* @__PURE__ */ new Map();
4446
- /** Mutex per appPath so only one startDevServer runs at a time for a given app (avoids duplicate port logs). */
4447
- static inFlightStarts = /* @__PURE__ */ new Map();
4448
- async startDevServer(appPath, runCommand, timeoutMs = 6e4) {
4449
- const serverKey = makeServerKey(appPath);
4450
- const prev = _LaunchTool.inFlightStarts.get(appPath);
4451
- let resolveThis;
4452
- const thisRun = new Promise((r) => {
4453
- resolveThis = r;
4454
- });
4455
- _LaunchTool.inFlightStarts.set(appPath, thisRun);
4456
- await prev;
4457
- try {
4458
- const entry = _LaunchTool.serverManagers.get(serverKey);
4459
- if (entry) {
4460
- const handle2 = await entry.manager.restart({ timeoutMs });
4461
- const result2 = {
4462
- success: true,
4463
- serverKey,
4464
- url: handle2.url,
4465
- port: handle2.port,
4466
- pid: handle2.child.pid ?? void 0,
4467
- outputTail: handle2.outputTail()
4468
- };
4469
- return JSON.stringify(result2, null, 2);
4470
- }
4471
- const manager = new DevServerManager({ appPath, runCommand: runCommand.trim() });
4472
- const handle = await manager.start({ timeoutMs });
4473
- _LaunchTool.serverManagers.set(serverKey, { manager, appPath });
4474
- const result = {
4475
- success: true,
4476
- serverKey,
4477
- url: handle.url,
4478
- port: handle.port,
4479
- pid: handle.child.pid ?? void 0,
4480
- outputTail: handle.outputTail()
4481
- };
4482
- return JSON.stringify(result, null, 2);
4483
- } catch (error) {
4484
- const result = {
4485
- success: false,
4486
- error: error instanceof Error ? error.message : String(error)
4487
- };
4488
- return JSON.stringify(result, null, 2);
4489
- } finally {
4490
- resolveThis();
4491
- _LaunchTool.inFlightStarts.delete(appPath);
4492
- }
4493
- }
4494
- async stopDevServer(serverKey) {
4495
- const entry = _LaunchTool.serverManagers.get(serverKey);
4496
- if (!entry) {
4497
- const result = { success: false, serverKey, error: `Unknown serverKey: ${serverKey}` };
4498
- return JSON.stringify(result, null, 2);
4499
- }
4500
- try {
4501
- await entry.manager.stop();
4502
- _LaunchTool.serverManagers.delete(serverKey);
4503
- const result = { success: true, serverKey };
4504
- return JSON.stringify(result, null, 2);
4505
- } catch (error) {
4506
- const result = {
4507
- success: false,
4508
- serverKey,
4509
- error: error instanceof Error ? error.message : String(error)
4510
- };
4511
- return JSON.stringify(result, null, 2);
4512
- }
4513
- }
4514
- };
4515
- _init7 = __decoratorStart(null);
4516
- _LaunchTool = __decorateElement(_init7, 0, "LaunchTool", _LaunchTool_decorators, _LaunchTool);
4517
- __runInitializers(_init7, 1, _LaunchTool);
4518
- var LaunchTool = _LaunchTool;
4519
-
4520
- // src/nodes/validation/utils/extraction/extract-protocol-context.ts
4521
- function extractValidationContext(protocol) {
4522
- const offset = extractOffset(protocol);
4523
- const elements = /* @__PURE__ */ new Map();
4524
- const components = /* @__PURE__ */ new Map();
4525
- const elementToComponent = /* @__PURE__ */ new Map();
4526
- const figmaPositions = {};
4527
- traverseProtocol(protocol, (node) => {
4528
- const componentInfo = {
4529
- id: node.id,
4530
- name: node.data.name,
4531
- path: node.data.componentPath ?? node.data.path ?? node.data.kebabName ?? node.id
4532
- };
4533
- components.set(node.id, componentInfo);
4534
- const hasChildren = Array.isArray(node.children) && node.children.length > 0;
4535
- const itemType = hasChildren ? "frame" : "component";
4536
- if (node.data.elements && Array.isArray(node.data.elements)) {
4537
- extractElementsRecursive(
4538
- node.data.elements,
4539
- componentInfo,
4540
- itemType,
4541
- elements,
4542
- elementToComponent,
4543
- figmaPositions,
4544
- offset
4545
- );
4546
- }
4547
- });
4548
- return { offset, elements, components, elementToComponent, figmaPositions };
4549
- }
4550
- function extractOffset(protocol) {
4551
- if (protocol.data.layout?.boundingBox) {
4552
- return {
4553
- x: protocol.data.layout.boundingBox.left,
4554
- y: protocol.data.layout.boundingBox.top
4555
- };
4556
- }
4557
- const firstElement = protocol.data.elements?.[0];
4558
- if (firstElement?.absoluteBoundingBox) {
4559
- return {
4560
- x: firstElement.absoluteBoundingBox.x,
4561
- y: firstElement.absoluteBoundingBox.y
4562
- };
4563
- }
4564
- return { x: 0, y: 0 };
4565
- }
4566
- function traverseProtocol(node, callback) {
4567
- callback(node);
4568
- if (node.children && Array.isArray(node.children)) {
4569
- for (const child of node.children) {
4570
- traverseProtocol(child, callback);
4571
- }
4572
- }
4573
- }
4574
- function extractElementsRecursive(elements, componentInfo, itemType, elementsMap, elementToComponent, figmaPositions, offset) {
4575
- for (const element of elements) {
4576
- if (!element || !element.id) continue;
4577
- const bbox = element.absoluteBoundingBox;
4578
- const position = bbox ? {
4579
- x: bbox.x - offset.x,
4580
- y: bbox.y - offset.y,
4581
- w: bbox.width,
4582
- h: bbox.height
4583
- } : { x: 0, y: 0, w: 0, h: 0 };
4584
- figmaPositions[element.id] = position;
4585
- elementsMap.set(element.id, {
4586
- id: element.id,
4587
- name: element.name,
4588
- type: element.type,
4589
- position,
4590
- layoutMode: element.layoutMode,
4591
- primaryAxisAlignItems: element.primaryAxisAlignItems,
4592
- counterAxisAlignItems: element.counterAxisAlignItems,
4593
- itemSpacing: element.itemSpacing,
4594
- paddingTop: element.paddingTop,
4595
- paddingRight: element.paddingRight,
4596
- paddingBottom: element.paddingBottom,
4597
- paddingLeft: element.paddingLeft,
4598
- constraints: element.constraints,
4599
- parentComponentId: componentInfo.id,
4600
- parentComponentName: componentInfo.name,
4601
- parentComponentPath: componentInfo.path,
4602
- parentItemType: itemType
4603
- });
4604
- elementToComponent.set(element.id, componentInfo);
4605
- if (element.children && Array.isArray(element.children)) {
4606
- extractElementsRecursive(element.children, componentInfo, itemType, elementsMap, elementToComponent, figmaPositions, offset);
4607
- }
4608
- if (element.frames && Array.isArray(element.frames)) {
4609
- extractElementsRecursive(element.frames, componentInfo, itemType, elementsMap, elementToComponent, figmaPositions, offset);
4610
- }
4611
- }
4612
- }
4613
- function extractComponentPaths(context, workspace) {
4614
- const paths = {};
4615
- for (const [id, info] of context.components) {
4616
- if (info.path) {
4617
- paths[id] = workspaceManager.resolveAppSrc(workspace, workspaceManager.resolveComponentPath(info.path));
4618
- }
4619
- }
4620
- return paths;
4621
- }
4622
- function toElementMetadataRegistry(context) {
4623
- const elements = /* @__PURE__ */ new Map();
4624
- for (const [id, element] of context.elements) {
4625
- elements.set(id, {
4626
- id: element.id,
4627
- name: element.name,
4628
- parentComponentId: element.parentComponentId,
4629
- parentComponentName: element.parentComponentName,
4630
- parentComponentPath: element.parentComponentPath,
4631
- parentItemType: element.parentItemType
4632
- });
4633
- }
4634
- return { elements, components: context.components };
4635
- }
4636
-
4637
- // src/nodes/validation/core/validate-position.ts
4638
- init_logger();
4639
-
4640
- // src/tools/position-tool/index.ts
4641
- init_logger();
4642
- import { tools as tools8 } from "evoltagent";
4643
-
4644
- // src/tools/position-tool/utils/capture-position.ts
4645
- init_logger();
4646
-
4647
- // src/tools/position-tool/utils/position-metrics.ts
4648
- function calculatePositionMetrics(positions) {
4649
- const positionValues = Object.values(positions);
4650
- const comparableItems = positionValues.filter((p) => p.metrics);
4651
- const comparableCount = comparableItems.length;
4652
- if (comparableCount === 0) {
4653
- return {
4654
- mae: 0,
4655
- mse: 0,
4656
- rmse: 0,
4657
- accurateItems: 0,
4658
- misalignedItems: 0,
4659
- comparableItems: 0,
4660
- accuracyRate: 0,
4661
- averageDistance: 0,
4662
- maxDistance: 0
4663
- };
4664
- }
4665
- const distances = comparableItems.map((p) => p.metrics.absoluteDistance);
4666
- const mae = distances.reduce((sum, d) => sum + d, 0) / comparableCount;
4667
- const mse = distances.reduce((sum, d) => sum + d * d, 0) / comparableCount;
4668
- const rmse = Math.sqrt(mse);
4669
- const maxDistance = Math.max(...distances);
4670
- const accurateItems = comparableItems.filter((p) => p.metrics.status === "accurate").length;
4671
- const misalignedItems = comparableItems.filter((p) => p.metrics.status === "misaligned").length;
4672
- const accuracyRate = comparableCount > 0 ? accurateItems / comparableCount * 100 : 0;
4673
- return {
4674
- mae: Math.round(mae * 100) / 100,
4675
- mse: Math.round(mse * 100) / 100,
4676
- rmse: Math.round(rmse * 100) / 100,
4677
- accurateItems,
4678
- misalignedItems,
4679
- comparableItems: comparableCount,
4680
- accuracyRate: Math.round(accuracyRate * 100) / 100,
4681
- averageDistance: Math.round(mae * 100) / 100,
4682
- maxDistance: Math.round(maxDistance * 100) / 100
4683
- };
4684
- }
4685
-
4686
- // src/tools/position-tool/utils/capture-position.ts
4687
- async function captureBrowserPositions(input) {
4688
- const url = input.url;
4689
- const timeout = input.timeout ?? DEFAULT_TIMEOUT;
4690
- const positionThreshold = input.positionThreshold ?? POSITION_THRESHOLD;
4691
- const errors = [];
4692
- let browser = null;
4693
- let page = null;
4694
- try {
4695
- const figmaPositions = input.validationContext.figmaPositions;
4696
- const designOffset = input.validationContext.offset;
4697
- const allElements = Array.from(input.elementRegistry.elements.values()).map((e) => ({
4698
- id: e.id,
4699
- name: e.name,
4700
- parentItemId: e.parentComponentId,
4701
- parentItemName: e.parentComponentName,
4702
- parentItemType: e.parentItemType
4703
- }));
4704
- let viewport = input.viewport ?? DEFAULT_VIEWPORT;
4705
- if (input.figmaThumbnailUrl && !input.viewport) {
4706
- const { fetchThumbnailDimensions: fetchThumbnailDimensions2 } = await Promise.resolve().then(() => (init_fetch_thumbnail_dimensions(), fetch_thumbnail_dimensions_exports));
4707
- const thumbnailDimensions = await fetchThumbnailDimensions2(input.figmaThumbnailUrl);
4708
- if (thumbnailDimensions) {
4709
- viewport = { width: thumbnailDimensions.width, height: thumbnailDimensions.height };
4710
- logger.printInfoLog(`Using viewport dimensions from Figma thumbnail: ${viewport.width}\xD7${viewport.height}px`);
4711
- } else {
4712
- logger.printWarnLog(`Using default viewport dimensions: ${viewport.width}\xD7${viewport.height}px`);
4713
- }
4714
- }
4715
- try {
4716
- browser = await launchChromiumWithAutoInstall({ headless: true });
4717
- } catch (launchError) {
4718
- throw new Error(`Failed to launch Playwright Chromium browser for position validation: ${launchError.message}`);
4719
- }
4720
- const context = await browser.newContext({ viewport });
4721
- page = await context.newPage();
4722
- try {
4723
- await page.goto(url, { waitUntil: "domcontentloaded", timeout });
4724
- } catch (error) {
4725
- throw new Error(`Failed to load ${url}. Make sure the development server is running. Error: ${error.message}`);
4726
- }
4727
- if (input.waitForSelector) {
4728
- try {
4729
- await page.waitForSelector(input.waitForSelector, { timeout: SELECTOR_WAIT_TIMEOUT });
4730
- } catch {
4731
- errors.push(`Warning: waitForSelector "${input.waitForSelector}" timed out`);
4732
- }
4733
- }
4734
- let screenshot;
4735
- if (input.returnScreenshot) {
4736
- try {
4737
- const buffer = await page.screenshot({ fullPage: true, type: "png" });
4738
- screenshot = `data:image/png;base64,${buffer.toString("base64")}`;
4739
- } catch (error) {
4740
- errors.push(`Warning: Failed to capture screenshot: ${error.message}`);
4741
- }
4742
- }
4743
- const positions = {};
4744
- let capturedCount = 0;
4745
- for (const element of allElements) {
4746
- try {
4747
- const escapedId = element.id.replace(/:/g, "\\:");
4748
- const selector = `[id="${escapedId}"]`;
4749
- const result = await page.evaluate((sel) => {
4750
- const el = document.querySelector(sel);
4751
- if (!el) return null;
4752
- const rect = el.getBoundingClientRect();
4753
- const style = window.getComputedStyle(el);
4754
- if (rect.width === 0 && rect.height === 0) {
4755
- return null;
4756
- }
4757
- return {
4758
- boundingBox: {
4759
- x: rect.x,
4760
- y: rect.y,
4761
- width: rect.width,
4762
- height: rect.height,
4763
- top: rect.top,
4764
- left: rect.left,
4765
- right: rect.right,
4766
- bottom: rect.bottom
4767
- },
4768
- computedStyle: {
4769
- position: style.position,
4770
- display: style.display,
4771
- top: style.top,
4772
- left: style.left,
4773
- right: style.right,
4774
- bottom: style.bottom,
4775
- transform: style.transform,
4776
- tagName: el.tagName.toLowerCase()
4777
- }
4778
- };
4779
- }, selector);
4780
- if (!result) {
4781
- errors.push(`Element ${element.id} not found in DOM or has no dimensions`);
4782
- continue;
4783
- }
4784
- const figmaPos = figmaPositions[element.id];
4785
- let metrics;
4786
- if (figmaPos) {
4787
- const xDelta = result.boundingBox.x - figmaPos.x;
4788
- const yDelta = result.boundingBox.y - figmaPos.y;
4789
- const xOffset = Math.abs(xDelta);
4790
- const yOffset = Math.abs(yDelta);
4791
- const absoluteDistance = Math.sqrt(xOffset ** 2 + yOffset ** 2);
4792
- metrics = {
4793
- xOffset: Math.round(xOffset * 100) / 100,
4794
- yOffset: Math.round(yOffset * 100) / 100,
4795
- xDelta: Math.round(xDelta * 100) / 100,
4796
- yDelta: Math.round(yDelta * 100) / 100,
4797
- absoluteDistance: Math.round(absoluteDistance * 100) / 100,
4798
- status: absoluteDistance <= positionThreshold ? "accurate" : "misaligned"
4799
- };
4800
- }
4801
- positions[element.id] = {
4802
- elementId: element.id,
4803
- elementName: element.name,
4804
- parentItemId: element.parentItemId,
4805
- parentItemName: element.parentItemName,
4806
- parentItemType: element.parentItemType,
4807
- boundingBox: {
4808
- x: Math.round(result.boundingBox.x * 100) / 100,
4809
- y: Math.round(result.boundingBox.y * 100) / 100,
4810
- width: Math.round(result.boundingBox.width * 100) / 100,
4811
- height: Math.round(result.boundingBox.height * 100) / 100,
4812
- top: Math.round(result.boundingBox.top * 100) / 100,
4813
- left: Math.round(result.boundingBox.left * 100) / 100,
4814
- right: Math.round(result.boundingBox.right * 100) / 100,
4815
- bottom: Math.round(result.boundingBox.bottom * 100) / 100
4816
- },
4817
- computedStyle: result.computedStyle,
4818
- figmaPosition: figmaPos ? { x: figmaPos.x, y: figmaPos.y, width: figmaPos.w, height: figmaPos.h } : void 0,
4819
- metrics
4820
- };
4821
- capturedCount++;
4822
- } catch (error) {
4823
- const errorMsg = `Failed to capture position for ${element.id}` + (element.name ? ` (${element.name})` : "") + ` in ${element.parentItemName || "unknown component"}: ${error.message}`;
4824
- errors.push(errorMsg);
4825
- }
4826
- }
4827
- const maeMetrics = calculatePositionMetrics(positions);
4828
- return {
4829
- metadata: {
4830
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
4831
- totalItems: allElements.length,
4832
- capturedItems: capturedCount,
4833
- ...maeMetrics,
4834
- url,
4835
- viewport,
4836
- designOffset
4837
- },
4838
- positions,
4839
- errors,
4840
- screenshot
4841
- };
4842
- } finally {
4843
- if (page) {
4844
- await page.close().catch(() => {
4845
- });
4846
- }
4847
- if (browser) {
4848
- await browser.close().catch(() => {
4849
- });
4850
- }
4851
- }
4852
- }
4853
-
4854
- // src/tools/position-tool/utils/aggregate-elements.ts
4855
- function toRect(rectLike) {
4856
- return { x: rectLike.x, y: rectLike.y, width: rectLike.width, height: rectLike.height };
4857
- }
4858
- function calculateAggregateBoundingBox(rectangles) {
4859
- if (rectangles.length === 0) {
4860
- throw new Error("Cannot calculate bounding box from empty array");
4861
- }
4862
- const minX = Math.min(...rectangles.map((r) => r.x));
4863
- const minY = Math.min(...rectangles.map((r) => r.y));
4864
- const maxRight = Math.max(...rectangles.map((r) => r.x + r.width));
4865
- const maxBottom = Math.max(...rectangles.map((r) => r.y + r.height));
4866
- return {
4867
- minX,
4868
- minY,
4869
- maxRight,
4870
- maxBottom,
4871
- width: maxRight - minX,
4872
- height: maxBottom - minY
4873
- };
4874
- }
4875
- function calculatePositionError(current, target) {
4876
- const errorX = Math.abs(target.minX - current.minX);
4877
- const errorY = Math.abs(target.minY - current.minY);
4878
- const distance = Math.sqrt(errorX ** 2 + errorY ** 2);
4879
- return { errorX, errorY, distance };
4880
- }
4881
- function calculateComponentMetrics(compData, positionThreshold) {
4882
- if (compData.positions.length === 0 || compData.targets.length === 0) {
4883
- return null;
4884
- }
4885
- const currentBounds = calculateAggregateBoundingBox(compData.positions);
4886
- const targetBounds = calculateAggregateBoundingBox(compData.targets);
4887
- const error = calculatePositionError(currentBounds, targetBounds);
4888
- if (error.distance <= positionThreshold) {
4889
- return null;
4890
- }
4891
- return { currentBounds, targetBounds, error };
4892
- }
4893
-
4894
- // src/tools/position-tool/index.ts
4895
- var _PositionTool_decorators, _init8;
4896
- _PositionTool_decorators = [tools8({
4897
- capturePosition: {
4898
- description: "Capture viewport-relative positions in the browser and compare to Figma absoluteBoundingBox, returning per-element deltas and metrics.",
4899
- params: [{ name: "input", type: "object", description: "BrowserPositionInput (figmaJSON, structure, url, etc.)" }],
4900
- returns: { type: "object", description: "PositionValidationOutput containing metadata, positions, and errors." }
4901
- },
4902
- aggregateElements: {
4903
- description: "Aggregate element-level rectangles into component-level bounds and return misaligned components based on aggregate distance threshold.",
4904
- params: [
4905
- { name: "positions", type: "object", description: "Positions keyed by elementId (from capturePosition)." },
4906
- {
4907
- name: "elementToComponentMap",
4908
- type: "object",
4909
- description: "Mapping from elementId -> {id,name,path} for parent component (built from structure tree)."
4910
- },
4911
- { name: "positionThreshold", type: "number", description: "Pixel threshold for aggregate distance." }
4912
- ],
4913
- returns: { type: "object", description: "AggregateElementsResult: misalignedComponents + skippedElements." }
4914
- },
4915
- computeMetrics: {
4916
- description: "Compute MAE/MSE/RMSE/accuracy metrics from element positions, and SAE from component misalignments.",
4917
- params: [
4918
- { name: "positions", type: "object", description: "Positions keyed by elementId (from capturePosition)." },
4919
- {
4920
- name: "misalignedComponents",
4921
- type: "object",
4922
- description: "Optional component misalignments (from aggregateElements) used to compute SAE.",
4923
- optional: true
4924
- }
4925
- ],
4926
- returns: { type: "object", description: "PositionToolMetrics (ValidationMetrics + sae)." }
4927
- }
4928
- })];
4929
- var PositionTool = class {
4930
- async capturePosition(input) {
4931
- return await captureBrowserPositions(input);
4932
- }
4933
- /**
4934
- * Deterministic in-process aggregation.
4935
- * NOTE: Map-based inputs are not JSON-friendly for future agent calls; we can add a JSON form later.
4936
- */
4937
- aggregateElements(positions, elementToComponentMap, positionThreshold) {
4938
- const componentMap = /* @__PURE__ */ new Map();
4939
- const skippedElements = [];
4940
- for (const [elementId, position] of Object.entries(positions)) {
4941
- if (!position.figmaPosition) {
4942
- skippedElements.push({
4943
- elementId,
4944
- reason: "no_figma_position",
4945
- details: "Element exists in render but has no corresponding Figma position data"
4946
- });
4947
- continue;
4948
- }
4949
- const component = elementToComponentMap.get(elementId);
4950
- if (!component) {
4951
- skippedElements.push({
4952
- elementId,
4953
- reason: "missing_component_mapping",
4954
- details: "Element has no component mapping in structure tree"
4955
- });
4956
- continue;
4957
- }
4958
- if (!componentMap.has(component.id)) {
4959
- componentMap.set(component.id, {
4960
- componentId: component.id,
4961
- componentName: component.name,
4962
- componentPath: component.path,
4963
- elementIds: [],
4964
- positions: [],
4965
- targets: [],
4966
- errors: []
4967
- });
4968
- }
4969
- const compData = componentMap.get(component.id);
4970
- compData.elementIds.push(elementId);
4971
- compData.positions.push(toRect(position.boundingBox));
4972
- compData.targets.push(toRect(position.figmaPosition));
4973
- compData.errors.push({
4974
- x: Math.abs(position.metrics?.xDelta ?? 0),
4975
- y: Math.abs(position.metrics?.yDelta ?? 0)
4976
- });
4977
- }
4978
- this.logSkippedElementsSummary(skippedElements);
4979
- const misalignedComponents = [];
4980
- for (const compData of componentMap.values()) {
4981
- if (compData.positions.length === 0 || compData.targets.length === 0) {
4982
- for (const elemId of compData.elementIds) {
4983
- skippedElements.push({
4984
- elementId: elemId,
4985
- reason: "incomplete_data",
4986
- details: `Component ${compData.componentName} has ${compData.positions.length} positions, ${compData.targets.length} targets`
4987
- });
4988
- }
4989
- continue;
4990
- }
4991
- const metrics = calculateComponentMetrics(compData, positionThreshold);
4992
- if (!metrics) {
4993
- continue;
4994
- }
4995
- const { currentBounds, targetBounds, error } = metrics;
4996
- misalignedComponents.push({
4997
- name: compData.componentName,
4998
- componentId: compData.componentId,
4999
- elementIds: compData.elementIds,
5000
- path: compData.componentPath,
5001
- validationReport: {
5002
- currentPosition: [currentBounds.minX, currentBounds.minY],
5003
- targetPosition: [targetBounds.minX, targetBounds.minY],
5004
- absoluteError: [error.errorX, error.errorY]
5005
- },
5006
- currentX: currentBounds.minX,
5007
- currentY: currentBounds.minY,
5008
- targetX: targetBounds.minX,
5009
- targetY: targetBounds.minY,
5010
- currentWidth: currentBounds.width,
5011
- currentHeight: currentBounds.height,
5012
- targetWidth: targetBounds.width,
5013
- targetHeight: targetBounds.height,
5014
- distance: error.distance
5015
- });
5016
- }
5017
- return { misalignedComponents, skippedElements };
5018
- }
5019
- computeMetrics(positions, misalignedComponents) {
5020
- const metrics = calculatePositionMetrics(positions);
5021
- const sae = misalignedComponents?.reduce((sum, comp) => {
5022
- const [errorX, errorY] = comp.validationReport.absoluteError;
5023
- return sum + errorX + errorY;
5024
- }, 0) ?? 0;
5025
- return { ...metrics, sae: Math.round(sae * 100) / 100 };
5026
- }
5027
- logSkippedElementsSummary(skippedElements) {
5028
- if (skippedElements.length === 0) {
5029
- return;
5030
- }
5031
- const byReason = {
5032
- missing_component_mapping: 0,
5033
- incomplete_data: 0,
5034
- no_figma_position: 0
5035
- };
5036
- for (const elem of skippedElements) {
5037
- byReason[elem.reason] += 1;
5038
- }
5039
- const summary = Object.entries(byReason).filter(([, count]) => count > 0).map(([reason, count]) => `${count} ${reason.replace(/_/g, " ")}`).join(", ");
5040
- const firstFew = skippedElements.slice(0, 3).map((e) => e.elementId).join(", ");
5041
- logger.printWarnLog(`Skipped ${skippedElements.length} element(s): ${summary}. First few: ${firstFew}`);
5042
- }
5043
- };
5044
- _init8 = __decoratorStart(null);
5045
- PositionTool = __decorateElement(_init8, 0, "PositionTool", _PositionTool_decorators, PositionTool);
5046
- __runInitializers(_init8, 1, PositionTool);
5047
-
5048
- // src/nodes/validation/core/validate-position.ts
5049
- async function validatePositions(config) {
5050
- const { serverUrl, figmaThumbnailUrl, protocol, positionThreshold, validationContext, elementRegistry, resolvedComponentPaths } = config;
5051
- const positionTool = new PositionTool();
5052
- const captureResult = await positionTool.capturePosition({
5053
- protocol,
5054
- validationContext,
5055
- url: serverUrl,
5056
- figmaThumbnailUrl,
5057
- positionThreshold,
5058
- returnScreenshot: true,
5059
- elementRegistry
5060
- });
5061
- const resolvedElementToComponent = /* @__PURE__ */ new Map();
5062
- for (const [elementId, componentInfo] of validationContext.elementToComponent) {
5063
- const resolvedPath = resolvedComponentPaths[componentInfo.id] || componentInfo.path;
5064
- resolvedElementToComponent.set(elementId, {
5065
- id: componentInfo.id,
5066
- name: componentInfo.name,
5067
- path: resolvedPath
5068
- });
5069
- }
5070
- const aggregated = positionTool.aggregateElements(captureResult.positions, resolvedElementToComponent, positionThreshold);
5071
- const misalignedComponents = aggregated.misalignedComponents;
5072
- const skippedElements = aggregated.skippedElements;
5073
- const sae = misalignedComponents.reduce((sum, comp) => {
5074
- const [errorX, errorY] = comp.validationReport.absoluteError;
5075
- return sum + errorX + errorY;
5076
- }, 0);
5077
- logger.printInfoLog(`Found ${misalignedComponents.length} misaligned components`);
5078
- return {
5079
- mae: captureResult.metadata.mae,
5080
- sae,
5081
- misalignedComponents,
5082
- skippedElements,
5083
- viewport: captureResult.metadata.viewport,
5084
- screenshots: captureResult.screenshot ? {
5085
- renderSnap: captureResult.screenshot
5086
- } : void 0
5087
- };
5088
- }
5089
-
5090
- // src/nodes/validation/core/validation-loop.ts
5091
- function filterComponentsToFix(misaligned, positionThreshold) {
5092
- return misaligned.filter((comp) => {
5093
- const [errorX, errorY] = comp.validationReport.absoluteError;
5094
- return errorX > positionThreshold || errorY > positionThreshold;
5095
- });
5096
- }
5097
- function recordComponentPosition(comp, iteration, componentHistory) {
5098
- const history = componentHistory[comp.componentId] ?? [];
5099
- history.push({
5100
- iteration,
5101
- position: comp.validationReport.currentPosition,
5102
- error: comp.validationReport.absoluteError,
5103
- fixApplied: null
5104
- });
5105
- componentHistory[comp.componentId] = history;
5106
- }
5107
- async function refineComponent(comp, context) {
5108
- const { workspace, structureTree, componentPaths, componentHistory, validationContext, previousScreenshotPath } = context;
5109
- try {
5110
- const elementIds = Array.from(validationContext.elements.values()).filter((e) => e.parentComponentId === comp.componentId).map((e) => e.id);
5111
- const figmaMetadata = extractLayoutFromContext(validationContext, elementIds);
5112
- logger.printLog(` Analyzing ${comp.name}...`);
5113
- const judger = createJudgerAgent({
5114
- workspaceDir: workspace.app,
5115
- structureTree,
5116
- componentPaths,
5117
- history: componentHistory
5118
- });
5119
- const judgerInstruction = formatJudgerInstruction(comp, figmaMetadata, componentPaths);
5120
- const judgerImages = previousScreenshotPath ? [previousScreenshotPath] : void 0;
5121
- const diagnosis = await judger.run(judgerInstruction, judgerImages);
5122
- logger.printLog(` Error type: ${diagnosis.errorType}`);
5123
- logger.printLog(` Fix instructions: ${diagnosis.refineInstructions?.length || 0}`);
5124
- if (!diagnosis.refineInstructions || diagnosis.refineInstructions.length === 0) {
5125
- const history2 = componentHistory[comp.componentId];
5126
- return {
5127
- componentId: comp.componentId,
5128
- componentPath: comp.path,
5129
- elementIds: comp.elementIds,
5130
- validationReport: comp.validationReport,
5131
- diagnosis,
5132
- refinerResult: {
5133
- success: false,
5134
- summary: ["Skipped - no instructions from judger"],
5135
- editsApplied: 0
5136
- },
5137
- positionHistory: history2 ? [...history2] : void 0
5138
- };
5139
- }
5140
- logger.printLog(` Applying fixes to ${comp.name}...`);
5141
- const refiner = createRefinerAgent(workspace.app);
5142
- const refinerInstruction = formatRefinerInstruction(comp, diagnosis, componentPaths);
5143
- const refinerResult = await refiner.run(refinerInstruction);
5144
- if (refinerResult.success) {
5145
- logger.printSuccessLog(`${comp.name}: ${refinerResult.editsApplied} edits applied`);
5146
- } else {
5147
- logger.printWarnLog(`${comp.name}: ${refinerResult.editsApplied} edits applied`);
5148
- }
5149
- const history = componentHistory[comp.componentId];
5150
- const last = history?.at(-1);
5151
- if (last) {
5152
- last.fixApplied = refinerResult.summary;
5153
- }
5154
- return {
5155
- componentId: comp.componentId,
5156
- componentPath: comp.path,
5157
- elementIds: comp.elementIds,
5158
- validationReport: comp.validationReport,
5159
- diagnosis,
5160
- refinerResult,
5161
- positionHistory: history ? [...history] : void 0
5162
- };
5163
- } catch (error) {
5164
- const errorMsg = error instanceof Error ? error.message : String(error);
5165
- logger.printInfoLog(`${comp.name}: ${errorMsg}`);
5166
- const history = componentHistory[comp.componentId];
5167
- return {
5168
- componentId: comp.componentId,
5169
- componentPath: comp.path,
5170
- elementIds: comp.elementIds,
5171
- validationReport: comp.validationReport,
5172
- diagnosis: void 0,
5173
- refinerResult: void 0,
5174
- positionHistory: history ? [...history] : void 0
5175
- };
5176
- }
5177
- }
5178
- function saveProcessedJson(outputDir, processedOutput) {
5179
- const processedJsonPath = path10.join(outputDir, "processed.json");
5180
- fs7.writeFileSync(processedJsonPath, JSON.stringify(processedOutput, null, 2));
5181
- logger.printInfoLog("Saved processed.json");
5182
- }
5183
- async function performCommit(appPath, iteration, stage) {
5184
- const commitResult = await commit({ appPath, iteration });
5185
- if (!commitResult.success) {
5186
- logger.printWarnLog(`Git commit (${stage}) failed: ${commitResult.message}`);
5187
- } else if (iteration === void 0) {
5188
- logger.printSuccessLog(`Initial project committed successfully, starting validation loop...`);
5189
- }
5190
- }
5191
- function saveIterationAndProcessedJson(iterations, iteration, currentMae, currentSae, misalignedCount, components, screenshotPath, skippedElements, outputDir, targetMae) {
5192
- iterations.push({
5193
- iteration,
5194
- metrics: { mae: currentMae, sae: currentSae, misalignedCount },
5195
- components,
5196
- screenshotPath,
5197
- skippedElements: skippedElements && skippedElements.length > 0 ? skippedElements : void 0
5198
- });
5199
- const processedOutput = {
5200
- iterations,
5201
- finalResult: {
5202
- success: currentMae <= targetMae,
5203
- finalMae: currentMae,
5204
- finalSae: currentSae,
5205
- totalIterations: iterations.length,
5206
- misalignedCount
5207
- }
5208
- };
5209
- saveProcessedJson(outputDir, processedOutput);
5210
- }
5211
- async function validationLoop(params) {
5212
- const { protocol, figmaThumbnailUrl, outputDir, workspace } = params;
5213
- if (!protocol || !figmaThumbnailUrl || !outputDir || !workspace) {
5214
- throw new Error("Something wrong in validation loop, missing required parameters...");
5215
- }
5216
- const config = {
5217
- ...DEFAULT_VALIDATION_LOOP_CONFIG,
5218
- ...params.config
5219
- };
5220
- const mode = config.mode ?? "full";
5221
- const maxIterations = mode === "reportOnly" ? 1 : config.maxIterations;
5222
- const visualizationTool = new VisualizationTool();
5223
- let currentServerUrl;
5224
- let serverKey;
5225
- try {
5226
- const validationContext = extractValidationContext(protocol);
5227
- const designOffset = [validationContext.offset.x, validationContext.offset.y];
5228
- if (Math.abs(designOffset[0]) >= 1 || Math.abs(designOffset[1]) >= 1) {
5229
- logger.printInfoLog(`Design offset: (${designOffset[0].toFixed(0)}, ${designOffset[1].toFixed(0)} px)`);
5230
- }
5231
- const resolvedComponentPaths = extractComponentPaths(validationContext, workspace);
5232
- const elementRegistry = toElementMetadataRegistry(validationContext);
5233
- logger.printInfoLog("Downloading Figma thumbnail (will be cached for all iterations)...");
5234
- const cachedFigmaThumbnailBase64 = await downloadImage(figmaThumbnailUrl, void 0, void 0, true);
5235
- logger.printSuccessLog("Figma thumbnail cached successfully");
5236
- const iterations = [];
5237
- const componentHistory = {};
5238
- let previousScreenshotPath;
5239
- let currentMae = -1;
5240
- let currentSae = 0;
5241
- let lastMisalignedCount = 0;
5242
- let lastValidationResult;
5243
- let lastRenderMarkedPath;
5244
- let lastTargetMarkedPath;
5245
- for (let iteration = 1; iteration <= maxIterations; iteration++) {
5246
- if (mode === "reportOnly") {
5247
- logger.printLog(`
5248
- ${"=".repeat(60)}`);
5249
- logger.printLog(`Running validation (report-only mode)`);
5250
- logger.printLog(`${"=".repeat(60)}`);
5251
- } else {
5252
- logger.printLog(`
5253
- ${"=".repeat(60)}`);
5254
- logger.printLog(`Iteration ${iteration}/${maxIterations}`);
5255
- logger.printLog(`${"=".repeat(60)}`);
5256
- }
5257
- if (iteration === 1) {
5258
- logger.printInfoLog("Initial launch: installing dependencies, building, fixing errors...");
5259
- const launchResult2 = await launch(workspace.app, {
5260
- skipDependencyInstall: false
5261
- });
5262
- if (!launchResult2.success) {
5263
- throw new Error(`Initial launch failed: ${launchResult2.error}`);
5264
- }
5265
- currentServerUrl = launchResult2.url;
5266
- serverKey = launchResult2.serverKey;
5267
- logger.printSuccessLog(`Dev server ready at ${currentServerUrl}`);
5268
- if (mode === "reportOnly") {
5269
- await performCommit(workspace.app, void 0, "initial state");
5270
- }
5271
- }
5272
- const validationResult = await validatePositions({
5273
- serverUrl: currentServerUrl,
5274
- figmaThumbnailUrl,
5275
- protocol,
5276
- iteration,
5277
- positionThreshold: config.positionThreshold,
5278
- designOffset,
5279
- outputDir,
5280
- validationContext,
5281
- elementRegistry,
5282
- cachedFigmaThumbnailBase64,
5283
- resolvedComponentPaths
5284
- });
5285
- lastValidationResult = validationResult;
5286
- currentMae = validationResult.mae;
5287
- currentSae = validationResult.sae;
5288
- const misaligned = validationResult.misalignedComponents;
5289
- lastMisalignedCount = misaligned.length;
5290
- logger.printLog(`MAE: ${currentMae.toFixed(2)}px (target: <=${config.targetMae}px)`);
5291
- logger.printLog(`SAE: ${currentSae.toFixed(2)}px`);
5292
- logger.printLog(`Misaligned: ${misaligned.length}`);
5293
- const screenshotResult = await visualizationTool.generateIterationScreenshot(
5294
- misaligned,
5295
- currentServerUrl,
5296
- figmaThumbnailUrl,
5297
- validationResult.viewport,
5298
- { x: designOffset[0], y: designOffset[1] },
5299
- path10.join(outputDir, "comparison_screenshots", `iteration_${iteration}.webp`),
5300
- cachedFigmaThumbnailBase64
5301
- );
5302
- if (screenshotResult.renderMarked && screenshotResult.targetMarked) {
5303
- const comparisonDir = path10.join(outputDir, "comparison_screenshots");
5304
- lastRenderMarkedPath = path10.join(comparisonDir, `iteration_${iteration}_render_marked.webp`);
5305
- lastTargetMarkedPath = path10.join(comparisonDir, `iteration_${iteration}_target_marked.webp`);
5306
- const sharp5 = (await import("sharp")).default;
5307
- await sharp5(Buffer.from(screenshotResult.renderMarked.split(",")[1], "base64")).toFile(lastRenderMarkedPath);
5308
- await sharp5(Buffer.from(screenshotResult.targetMarked.split(",")[1], "base64")).toFile(lastTargetMarkedPath);
5309
- }
5310
- const comparisonScreenshotPath = screenshotResult.combinedPath;
5311
- previousScreenshotPath = comparisonScreenshotPath;
5312
- const misalignedToFix = filterComponentsToFix(misaligned, config.positionThreshold);
5313
- if (mode === "reportOnly") {
5314
- } else {
5315
- logger.printInfoLog(
5316
- `Skipping ${misaligned.length - misalignedToFix.length} components with error <= ${config.positionThreshold}px`
5317
- );
5318
- }
5319
- for (const comp of misalignedToFix) {
5320
- recordComponentPosition(comp, iteration, componentHistory);
5321
- }
5322
- const componentLogs = [];
5323
- if (mode === "reportOnly") {
5324
- logger.printInfoLog("Report-only mode: skipping component refinement, saving validation report...");
5325
- saveIterationAndProcessedJson(
5326
- iterations,
5327
- iteration,
5328
- currentMae,
5329
- currentSae,
5330
- misaligned.length,
5331
- componentLogs,
5332
- comparisonScreenshotPath,
5333
- validationResult.skippedElements,
5334
- outputDir,
5335
- config.targetMae
5336
- );
5337
- break;
5338
- }
5339
- if (currentMae <= config.targetMae) {
5340
- logger.printSuccessLog("Validation passed!");
5341
- saveIterationAndProcessedJson(
5342
- iterations,
5343
- iteration,
5344
- currentMae,
5345
- currentSae,
5346
- misaligned.length,
5347
- componentLogs,
5348
- comparisonScreenshotPath,
5349
- validationResult.skippedElements,
5350
- outputDir,
5351
- config.targetMae
5352
- );
5353
- if (serverKey) {
5354
- await performCommit(workspace.app, iteration, `iteration ${iteration}`);
5355
- }
5356
- break;
5357
- }
5358
- logger.printInfoLog(`Refining ${misalignedToFix.length} components...`);
5359
- const refinementContext = {
5360
- workspace,
5361
- structureTree: protocol,
5362
- componentPaths: resolvedComponentPaths,
5363
- componentHistory,
5364
- validationContext,
5365
- previousScreenshotPath
5366
- };
5367
- for (const comp of misalignedToFix) {
5368
- const log = await refineComponent(comp, refinementContext);
5369
- componentLogs.push(log);
5370
- }
5371
- logger.printInfoLog("Re-launching dev server after refiner changes...");
5372
- const launchTool = new LaunchTool();
5373
- await launchTool.stopDevServer(serverKey).catch(() => {
5374
- logger.printWarnLog("Failed to stop previous server");
5375
- });
5376
- const launchResult = await launch(workspace.app, {
5377
- skipDependencyInstall: true
5378
- });
5379
- if (!launchResult.success) {
5380
- throw new Error(`Launch failed after refiner at iteration ${iteration}: ${launchResult.error}`);
5381
- }
5382
- currentServerUrl = launchResult.url;
5383
- serverKey = launchResult.serverKey;
5384
- logger.printSuccessLog(`Dev server restarted at ${currentServerUrl}`);
5385
- if (mode === "full") {
5386
- await performCommit(workspace.app, iteration, `iteration ${iteration}`);
5387
- }
5388
- saveIterationAndProcessedJson(
5389
- iterations,
5390
- iteration,
5391
- currentMae,
5392
- currentSae,
5393
- misaligned.length,
5394
- componentLogs,
5395
- comparisonScreenshotPath,
5396
- validationResult.skippedElements,
5397
- outputDir,
5398
- config.targetMae
5399
- );
5400
- logger.printInfoLog(`Iteration ${iteration} complete
5401
- `);
5402
- }
5403
- const validationPassed = currentMae <= config.targetMae;
5404
- if (!validationPassed) {
5405
- if (mode === "reportOnly") {
5406
- logger.printWarnLog(
5407
- `Validation did not satisfy MAE threshold (${config.targetMae}px) in report-only mode. Final MAE: ${currentMae.toFixed(2)}px`
5408
- );
5409
- } else {
5410
- logger.printWarnLog(
5411
- `Max iterations (${maxIterations}) reached without satisfying MAE threshold (${config.targetMae}px). Final MAE: ${currentMae.toFixed(2)}px`
5412
- );
5413
- }
5414
- }
5415
- const finalOutput = {
5416
- iterations,
5417
- finalResult: {
5418
- success: validationPassed,
5419
- finalMae: currentMae,
5420
- finalSae: currentSae,
5421
- totalIterations: iterations.length,
5422
- misalignedCount: lastMisalignedCount
5423
- }
5424
- };
5425
- if (!currentServerUrl || !serverKey) {
5426
- throw new Error("Server was not launched properly during validation");
5427
- }
5428
- try {
5429
- if (!lastValidationResult) {
5430
- throw new Error("No validation results available for report generation");
5431
- }
5432
- if (!lastRenderMarkedPath || !lastTargetMarkedPath) {
5433
- throw new Error("No saved screenshots available for report generation");
5434
- }
5435
- const reportResult = await report({
5436
- validationResult: lastValidationResult,
5437
- figmaThumbnailUrl,
5438
- cachedFigmaThumbnailBase64,
5439
- designOffset: { x: designOffset[0], y: designOffset[1] },
5440
- outputDir,
5441
- serverUrl: currentServerUrl,
5442
- savedRenderMarkedPath: lastRenderMarkedPath,
5443
- savedTargetMarkedPath: lastTargetMarkedPath
5444
- });
5445
- finalOutput.finalResult.misalignedCount = lastValidationResult.misalignedComponents.length;
5446
- saveProcessedJson(outputDir, finalOutput);
5447
- return {
5448
- reportGenerated: reportResult.success,
5449
- validationPassed,
5450
- finalMae: currentMae,
5451
- finalSae: currentSae,
5452
- totalIterations: iterations.length,
5453
- processedOutput: finalOutput,
5454
- userReport: reportResult.userReport
5455
- };
5456
- } catch (screenshotError) {
5457
- const errorMsg = screenshotError instanceof Error ? screenshotError.message : String(screenshotError);
5458
- logger.printWarnLog(`Failed to generate final report: ${errorMsg}. Returning minimal report.`);
5459
- saveProcessedJson(outputDir, finalOutput);
5460
- return {
5461
- reportGenerated: false,
5462
- validationPassed,
5463
- finalMae: currentMae,
5464
- finalSae: currentSae,
5465
- totalIterations: iterations.length,
5466
- processedOutput: finalOutput,
5467
- userReport: {
5468
- design: { snap: figmaThumbnailUrl, markedSnap: "" },
5469
- page: { url: currentServerUrl, snap: "", markedSnap: "" },
5470
- report: {
5471
- heatmap: "",
5472
- detail: {
5473
- metrics: { mae: currentMae, sae: currentSae, misalignedCount: lastMisalignedCount },
5474
- components: []
5475
- }
5476
- }
5477
- }
5478
- };
5479
- }
5480
- } finally {
5481
- if (serverKey) {
5482
- logger.printInfoLog("Cleaning up dev server...");
5483
- const launchTool = new LaunchTool();
5484
- await launchTool.stopDevServer(serverKey).catch((err) => {
5485
- logger.printWarnLog(`Failed to stop dev server: ${err instanceof Error ? err.message : String(err)}`);
5486
- });
5487
- }
5488
- }
5489
- }
5490
-
5491
- // src/nodes/validation/index.ts
5492
- var runValidation = async (state) => {
5493
- if (!state.protocol) {
5494
- throw new Error("No protocol found for validation (state.protocol is missing).");
5495
- }
5496
- if (!state.figmaInfo?.thumbnail) {
5497
- throw new Error("Missing Figma thumbnail URL (state.figmaInfo.thumbnail is missing).");
5498
- }
5499
- const mode = state.config?.validationMode ?? "full";
5500
- if (mode === "codeOnly") {
5501
- logger.printInfoLog("Code-only mode: skipping validation, committing generated code...");
5502
- const commitResult = await commit({ appPath: state.workspace.app });
5503
- if (!commitResult.success) {
5504
- logger.printWarnLog(`Git commit failed: ${commitResult.message}`);
5505
- } else {
5506
- logger.printSuccessLog("Git commit completed successfully!");
5507
- }
5508
- return;
5509
- }
5510
- const outputDir = path11.join(state.workspace.process, "validation");
5511
- if (!fs8.existsSync(outputDir)) {
5512
- fs8.mkdirSync(outputDir, { recursive: true });
5513
- }
5514
- logger.printInfoLog(`Starting validation loop (mode: ${mode})...`);
5515
- const result = await validationLoop({
5516
- protocol: state.protocol,
5517
- figmaThumbnailUrl: state.figmaInfo.thumbnail,
5518
- outputDir,
5519
- workspace: state.workspace,
5520
- config: { mode }
5521
- });
5522
- if (result.error) {
5523
- throw new Error(result.error);
5524
- }
5525
- const reportHtmlPath = path11.join(outputDir, "index.html");
5526
- if (result.validationPassed) {
5527
- logger.printSuccessLog(`Validation PASSED (MAE: ${result.finalMae.toFixed(METRIC_DECIMAL_PLACES)}px)`);
5528
- } else {
5529
- logger.printWarnLog(`Validation FAILED (MAE: ${result.finalMae.toFixed(METRIC_DECIMAL_PLACES)}px)`);
5530
- }
5531
- logger.printInfoLog(`Validation report: ${reportHtmlPath}`);
5532
- };
5533
-
5534
- // src/utils/url-parser.ts
5535
- var parseFigmaUrl = (url) => {
5536
- let fileId = null;
5537
- let name = "untitled";
5538
- let nodeId = null;
5539
- try {
5540
- const urlObj = new URL(decodeURIComponent(url));
5541
- const pathParts = urlObj.pathname.split("/").filter(Boolean);
5542
- if (pathParts.length >= 3) {
5543
- fileId = pathParts[pathParts.length - 2] || null;
5544
- const fileName = pathParts[pathParts.length - 1];
5545
- name = fileName ? encodeURI(fileName).toLowerCase() : "untitled";
5546
- name = name.length > 20 ? name.substring(0, 20) : name;
5547
- }
5548
- nodeId = urlObj.searchParams.get("node-id") || null;
5549
- nodeId = nodeId ? nodeId.replace(/-/g, ":") : null;
5550
- } catch {
5551
- }
5552
- if (!fileId || !nodeId) {
5553
- throw new Error("Invalid Figma URL");
5554
- }
5555
- return { fileId, name, nodeId, projectName: `${name}_${nodeId}` };
5556
- };
5557
-
5558
- // src/nodes/code/index.ts
5559
- init_logger();
5560
-
5561
- // src/nodes/code/utils.ts
5562
- init_logger();
5563
- import fs10 from "fs";
5564
-
5565
- // src/nodes/code/prompt.ts
5566
- var STYLING_GUIDELINES = `
5567
- - **Style Consistency**: Implement styles using the technical stack and libraries listed in <styling>.
5568
- - **Strict Restriction**: Absolutely ONLY use the technical stack and libraries listed in <styling>. Do NOT use any other styling methods, libraries, or frameworks (e.g., if clsx is not listed, do not use clsx).
5569
- - **Default Styling**: If <styling> is empty or does not contain specific libraries, DEFAULT to standard vanilla CSS.
5570
-
5571
- - **Tailwind CSS + CSS Modules (CRITICAL)**:
5572
- - If the stack includes BOTH Tailwind and CSS Modules (Less/SCSS), use them correctly:
5573
- 1. **Tailwind utilities**: Use DIRECTLY in JSX className (e.g., \`className="flex items-center gap-4"\`)
5574
- 2. **CSS Modules**: Use ONLY for complex styles that can't be expressed with Tailwind utilities (e.g., gradients, animations, pseudo-elements)
5575
- 3. **NEVER use \`@apply\` in CSS Module files** - it's a Tailwind-specific directive that doesn't work in Less/SCSS
5576
- 4. Example correct usage:
5577
- TSX: \`<div className={\`flex \${styles.customGradient}\`}>\`
5578
- Less: \`.customGradient { background: linear-gradient(...); }\`
5579
-
5580
- - **CSS Modules Only**: If the tech stack specifies CSS Modules without Tailwind:
5581
- 1. Create a corresponding style file (e.g., \`index.module.less\`, \`index.module.scss\`, or \`index.module.css\`)
5582
- 2. Import it as \`import styles from './index.module.[ext]';\` in the TSX
5583
- 3. Define all styles in the style file using standard CSS/Less/SCSS syntax
5584
- 4. Use \`styles.className\` in JSX`;
5585
- var ASSETS_HANDLING = `
5586
- - **CRITICAL**: For any image URL starting with \`@/assets\`, you MUST import it at the top of the file.
5587
- - **Asset Name Matching**:
5588
- - Check the \`<available_assets>\` list for actual filenames in the project.
5589
- - Asset filenames follow the pattern: \`kebab-case-name-id1-id2.ext\` (e.g., "Star 2.svg" \u2192 "star-2-1-2861.svg")
5590
- - Match the base name (ignoring spaces, case, and ID suffix): "@/assets/arXiv.svg" \u2192 look for "arxiv-*.svg" in the list
5591
- - Use the EXACT filename from the available assets list in your import.
5592
- - Example: If available_assets contains "arxiv-1-2956.svg", use:
5593
- \`import ArXivIcon from '@/assets/arxiv-1-2956.svg';\`
5594
- - **Usage**: \`<img src={ArXivIcon} />\`, do not use backgroundImage property.
5595
- - **NEVER** use the string path directly in JSX or styles.`;
5596
- var DOM_IDS_REQUIREMENT = `
5597
- - Assign \`id\` attributes to the main container and any internal elements, matching \`frame_details\`.`;
5598
- var REACT_IMPORT_RULE = `
5599
- - Do **NOT** include \`import React from 'react';\` at the top of the file.`;
5600
- var FILE_NAMING_CONVENTION = `
5601
- - ALWAYS name the main component file \`index.tsx\`.
5602
- - ALWAYS name the style file (if applicable) \`index.module.[css|less|scss]\`.
5603
- - NEVER use PascalCase or other names for filenames (e.g., DO NOT use \`MainFrame.tsx\` or \`Button.tsx\`).`;
5604
- var OUTPUT_FORMAT = `
5605
- <output_format>
5606
- If only one file (TSX) is needed:
5607
- \`\`\`tsx
5608
- // code...
5609
- \`\`\`
5610
-
5611
- If multiple files are needed (e.g., TSX + Styles):
5612
- ## index.tsx
5613
- \`\`\`tsx
5614
- // code...
5615
- \`\`\`
1895
+ If multiple files are needed (e.g., TSX + Styles):
1896
+ ## index.tsx
1897
+ \`\`\`tsx
1898
+ // code...
1899
+ \`\`\`
5616
1900
 
5617
1901
  ## index.module.[css|less|scss]
5618
1902
  \`\`\`[css|less|scss]
@@ -5851,72 +2135,6 @@ ${OUTPUT_FORMAT}
5851
2135
  </system_instructions>
5852
2136
  `.trim();
5853
2137
  };
5854
- var injectRootComponentPrompt = ({
5855
- appContent,
5856
- componentName,
5857
- componentPath
5858
- }) => {
5859
- return `
5860
- <system_instructions>
5861
- <role>
5862
- You are a React code refactoring expert.
5863
- Your task is to inject a root component into an existing App.tsx file with minimal changes.
5864
- </role>
5865
-
5866
- <input_context>
5867
- <current_app_content>
5868
- ${appContent}
5869
- </current_app_content>
5870
- <component_to_inject>
5871
- - Component Name: ${componentName}
5872
- - Import Path: ${componentPath}
5873
- </component_to_inject>
5874
- </input_context>
5875
-
5876
- <requirements>
5877
- <req_1>
5878
- **Import Statement**:
5879
- - Add the import statement at the top of the file: \`import ${componentName} from '${componentPath}';\`
5880
- - Place it after existing imports, before the component definition.
5881
- - Do NOT remove or modify existing imports.
5882
- </req_1>
5883
-
5884
- <req_2>
5885
- **Component Rendering**:
5886
- - Inside the App component's return statement, replace the existing content with \`<${componentName} />\`.
5887
- - If the return contains a wrapper div or other container, keep it and place the component inside.
5888
- - Preserve any existing className, styles, or other attributes on wrapper elements.
5889
- </req_2>
5890
-
5891
- <req_3>
5892
- **Preserve Existing Code**:
5893
- - Keep all existing imports (CSS, Less, etc.)
5894
- - Preserve any hooks, state, or logic inside the App component
5895
- - Maintain the component structure and export statement
5896
- - Do NOT add comments or explanatory text
5897
- </req_3>
5898
-
5899
- <req_4>
5900
- **Minimal Changes**:
5901
- - Only add the necessary import and render the component
5902
- - Do NOT refactor or optimize existing code
5903
- - Do NOT change formatting or styling unless necessary
5904
- - Do NOT add TypeScript types unless they already exist
5905
- </req_4>
5906
-
5907
- <req_5>
5908
- **React Import**:
5909
- ${REACT_IMPORT_RULE}
5910
- </req_5>
5911
- </requirements>
5912
-
5913
- <output_format>
5914
- Return ONLY the complete updated App.tsx code without markdown code blocks or explanation.
5915
- The output should be valid TypeScript/React code that can be directly written to the file.
5916
- </output_format>
5917
- </system_instructions>
5918
- `.trim();
5919
- };
5920
2138
 
5921
2139
  // src/nodes/code/constants.ts
5922
2140
  var DEFAULT_STYLING = {
@@ -5932,124 +2150,14 @@ var DEFAULT_STYLING = {
5932
2150
  }
5933
2151
  ]
5934
2152
  };
5935
- var DEFAULT_APP_CONTENT = `function App() {
5936
- return (
5937
- <div>
5938
- {/* Component will be injected here */}
5939
- </div>
5940
- );
5941
- }
5942
-
5943
- export default App;`;
5944
2153
 
5945
2154
  // src/nodes/code/utils.ts
5946
- import path12 from "path";
2155
+ import path4 from "path";
5947
2156
 
5948
2157
  // src/utils/code-cache.ts
5949
- init_logger();
5950
- import fs9 from "fs";
5951
- function getCachePath(workspace) {
5952
- return workspace.checkpoint;
5953
- }
5954
- function loadCodeCache(workspace) {
5955
- const cachePath = getCachePath(workspace);
5956
- try {
5957
- if (!fs9.existsSync(cachePath)) {
5958
- logger.printInfoLog("No code cache found, starting fresh");
5959
- return createEmptyCache();
5960
- }
5961
- const content = fs9.readFileSync(cachePath, "utf-8");
5962
- const cache = JSON.parse(content);
5963
- if (!Array.isArray(cache.generatedComponents) || typeof cache.appInjected !== "boolean") {
5964
- logger.printWarnLog("Invalid cache format, starting fresh");
5965
- return createEmptyCache();
5966
- }
5967
- logger.printInfoLog(`Loaded code cache: ${cache.generatedComponents.length} components cached`);
5968
- return cache;
5969
- } catch (error) {
5970
- const errorMessage = error instanceof Error ? error.message : String(error);
5971
- logger.printWarnLog(`Failed to load code cache: ${errorMessage}. Starting fresh.`);
5972
- return createEmptyCache();
5973
- }
5974
- }
5975
- function saveCodeCache(workspace, cache) {
5976
- const cachePath = getCachePath(workspace);
5977
- try {
5978
- if (!fs9.existsSync(workspace.process)) {
5979
- fs9.mkdirSync(workspace.process, { recursive: true });
5980
- }
5981
- const content = JSON.stringify(cache, null, 2);
5982
- fs9.writeFileSync(cachePath, content, "utf-8");
5983
- } catch (error) {
5984
- const errorMessage = error instanceof Error ? error.message : String(error);
5985
- logger.printWarnLog(`Failed to save code cache: ${errorMessage}`);
5986
- }
5987
- }
5988
- function isComponentGenerated(cache, nodeId) {
5989
- return cache.generatedComponents.includes(nodeId);
5990
- }
5991
- function markComponentGenerated(cache, nodeId) {
5992
- if (!isComponentGenerated(cache, nodeId)) {
5993
- cache.generatedComponents.push(nodeId);
5994
- }
5995
- }
5996
- function saveComponentGenerated(cache, nodeId, workspace) {
5997
- markComponentGenerated(cache, nodeId);
5998
- saveCodeCache(workspace, cache);
5999
- }
6000
- function isAppInjected(cache) {
6001
- return cache.appInjected;
6002
- }
6003
- function markAppInjected(cache) {
6004
- cache.appInjected = true;
6005
- }
6006
- function saveAppInjected(cache, workspace) {
6007
- markAppInjected(cache);
6008
- saveCodeCache(workspace, cache);
6009
- }
6010
- function createEmptyCache() {
6011
- return {
6012
- generatedComponents: [],
6013
- appInjected: false
6014
- };
6015
- }
2158
+ import fs4 from "fs";
6016
2159
 
6017
2160
  // src/nodes/code/utils.ts
6018
- async function processNode(state, cache) {
6019
- const assetFilesList = getAssetFilesList(state);
6020
- const flatNodes = flattenPostOrder(state.protocol);
6021
- const total = flatNodes.length;
6022
- if (total === 0) {
6023
- logger.printWarnLog("No components found in structure to generate.");
6024
- return 0;
6025
- }
6026
- logger.printInfoLog(`Processing ${total} nodes...`);
6027
- let processedCount = 0;
6028
- let skippedCount = 0;
6029
- const processSingleNode = async (currentNode) => {
6030
- const componentName = currentNode.data.name || currentNode.data.componentName || "UnknownComponent";
6031
- const nodeId = currentNode.id;
6032
- if (isComponentGenerated(cache, nodeId)) {
6033
- skippedCount++;
6034
- logger.printInfoLog(`[${processedCount + skippedCount}/${total}] \u23ED\uFE0F Skipping (cached): ${componentName}`);
6035
- return;
6036
- }
6037
- const progressInfo = `[${++processedCount + skippedCount}/${total}]`;
6038
- const isLeaf = !currentNode.children?.length;
6039
- if (isLeaf) {
6040
- await generateComponent(currentNode, state, assetFilesList, progressInfo);
6041
- } else {
6042
- await generateFrame(currentNode, state, assetFilesList, progressInfo);
6043
- }
6044
- saveComponentGenerated(cache, nodeId, state.workspace);
6045
- };
6046
- await promisePool(flatNodes, processSingleNode);
6047
- if (skippedCount > 0) {
6048
- logger.printInfoLog(`\u23ED\uFE0F Skipped ${skippedCount} cached components`);
6049
- }
6050
- logger.printSuccessLog(`Generated ${processedCount} components`);
6051
- return processedCount;
6052
- }
6053
2161
  function flattenPostOrder(node) {
6054
2162
  const result = [];
6055
2163
  function traverse(n) {
@@ -6069,221 +2177,44 @@ function detectRenderingModes(node) {
6069
2177
  });
6070
2178
  return { hasStates, hasIndependentChildren };
6071
2179
  }
6072
- async function generateFrame(node, state, assetFilesList, progressInfo) {
6073
- const frameName = node.data.name;
6074
- logger.printInfoLog(`${progressInfo} \u{1F5BC}\uFE0F Generating Frame: ${frameName}`);
6075
- const childrenImports = (node.children || []).map((child) => ({
6076
- name: child.data.name || "",
6077
- path: child.data.path
6078
- }));
6079
- const renderingModes = detectRenderingModes(node);
6080
- const prompt = generateFramePrompt({
6081
- frameDetails: JSON.stringify(node.data),
6082
- childrenImports: JSON.stringify(childrenImports),
6083
- styling: JSON.stringify(DEFAULT_STYLING),
6084
- assetFiles: assetFilesList,
6085
- renderingModes
6086
- });
6087
- const code = await callModel({
6088
- question: prompt,
6089
- imageUrls: state.figmaInfo.thumbnail
6090
- });
6091
- const componentPath = node.data.path || "";
6092
- const filePath = workspaceManager.resolveAppSrc(state.workspace, workspaceManager.resolveComponentPath(componentPath));
6093
- saveGeneratedCode(code, filePath);
6094
- logger.printSuccessLog(`Successfully generated frame: ${frameName}`);
6095
- }
6096
- async function generateComponent(node, state, assetFilesList, progressInfo) {
6097
- const componentName = node.data.componentName || node.data.name || "UnknownComponent";
6098
- const componentPath = node.data.componentPath || node.data.path || "";
6099
- logger.printInfoLog(`${progressInfo} \u{1F4E6} Generating Component: ${componentName}`);
6100
- const prompt = generateComponentPrompt({
6101
- componentName,
6102
- componentDetails: JSON.stringify(node.data),
6103
- styling: JSON.stringify(DEFAULT_STYLING),
6104
- assetFiles: assetFilesList
6105
- });
6106
- const code = await callModel({
6107
- question: prompt,
6108
- imageUrls: state.figmaInfo.thumbnail
6109
- });
6110
- const filePath = workspaceManager.resolveAppSrc(state.workspace, workspaceManager.resolveComponentPath(componentPath));
6111
- saveGeneratedCode(code, filePath);
6112
- logger.printSuccessLog(`Successfully generated component: ${componentName}`);
6113
- }
6114
2180
  function saveGeneratedCode(code, filePath) {
6115
2181
  const files = extractFiles(code);
6116
2182
  if (files.length > 0) {
6117
2183
  createFiles({ files, filePath });
6118
2184
  } else {
6119
2185
  const extractedCode = extractCode(code);
6120
- const folderPath = path12.dirname(filePath);
6121
- const fileName = path12.basename(filePath);
2186
+ const folderPath = path4.dirname(filePath);
2187
+ const fileName = path4.basename(filePath);
6122
2188
  writeFile(folderPath, fileName, extractedCode);
6123
2189
  }
6124
2190
  }
6125
- function getAssetFilesList(state) {
6126
- try {
6127
- const assetsDir = workspaceManager.resolveAppSrc(state.workspace, "assets");
6128
- if (!fs10.existsSync(assetsDir)) {
6129
- return "";
6130
- }
6131
- const files = fs10.readdirSync(assetsDir);
6132
- return files.join(", ");
6133
- } catch {
6134
- return "";
6135
- }
6136
- }
6137
- async function injectRootComponentToApp(state, cache) {
6138
- try {
6139
- if (isAppInjected(cache)) {
6140
- logger.printInfoLog("\u23ED\uFE0F Skipping App.tsx injection (already injected)");
6141
- return;
6142
- }
6143
- logger.printInfoLog("\u{1F489} Injecting root component into App.tsx...");
6144
- const appTsxPath = workspaceManager.resolveAppSrc(state.workspace, "App.tsx");
6145
- let appContent;
6146
- try {
6147
- appContent = fs10.readFileSync(appTsxPath, "utf8");
6148
- } catch {
6149
- logger.printWarnLog("App.tsx not found, using default template");
6150
- appContent = DEFAULT_APP_CONTENT;
6151
- }
6152
- const rootNode = state.protocol;
6153
- const componentName = rootNode.data.name || "RootComponent";
6154
- const componentPath = rootNode.data.path || "";
6155
- const prompt = injectRootComponentPrompt({
6156
- appContent,
6157
- componentName,
6158
- componentPath
6159
- });
6160
- const updatedCode = await callModel({
6161
- question: prompt
6162
- });
6163
- const finalCode = updatedCode.includes("```") ? extractCode(updatedCode) : updatedCode.trim();
6164
- const appFolderPath = path12.dirname(appTsxPath);
6165
- writeFile(appFolderPath, "App.tsx", finalCode);
6166
- saveAppInjected(cache, state.workspace);
6167
- logger.printSuccessLog(`Successfully injected ${componentName} into App.tsx`);
6168
- } catch (error) {
6169
- logger.printErrorLog(`Failed to inject root component: ${error.message}`);
6170
- }
6171
- }
6172
-
6173
- // src/nodes/code/index.ts
6174
- async function generateCode(state) {
6175
- try {
6176
- logger.printInfoLog("\u{1F680} Starting Code Generation...");
6177
- if (!state.protocol) {
6178
- throw new Error("No protocol data found");
6179
- }
6180
- const cache = loadCodeCache(state.workspace);
6181
- const totalComponents = await processNode(state, cache);
6182
- await injectRootComponentToApp(state, cache);
6183
- logger.printSuccessLog(`\u2728 Code generation completed! Generated ${totalComponents} components`);
6184
- } catch (error) {
6185
- const errorMessage = error instanceof Error ? error.message : String(error);
6186
- throw new Error(`Code generation failed: ${errorMessage}`);
6187
- }
6188
- }
6189
-
6190
- // src/utils/checkpoint.ts
6191
- init_logger();
6192
- import fs11 from "fs";
6193
- import { SqliteSaver } from "@langchain/langgraph-checkpoint-sqlite";
6194
-
6195
- // src/cli/prompts.ts
6196
- init_logger();
6197
- import prompts from "prompts";
6198
- async function typedPrompt(config) {
6199
- return prompts(config, {
6200
- onCancel: () => {
6201
- logger.printWarnLog("Operation cancelled by user.");
6202
- process.exit(0);
6203
- }
6204
- });
6205
- }
6206
- async function promptUserChoice() {
6207
- const response = await typedPrompt({
6208
- type: "select",
6209
- name: "choice",
6210
- message: "What would you like to do?",
6211
- choices: [
6212
- { title: "Resume from cache", value: "resume" },
6213
- { title: "Start fresh (will clear existing cache)", value: "fresh" }
6214
- ],
6215
- initial: 0
6216
- });
6217
- return response.choice || "resume";
6218
- }
6219
-
6220
- // src/utils/checkpoint.ts
6221
- async function checkpointExists(checkpointer, threadId) {
6222
- try {
6223
- const checkpoints = checkpointer.list({ configurable: { thread_id: threadId } }, { limit: 1 });
6224
- const firstCheckpoint = await checkpoints.next();
6225
- return !firstCheckpoint.done && firstCheckpoint.value !== void 0;
6226
- } catch (error) {
6227
- const errorMessage = error instanceof Error ? error.message : String(error);
6228
- logger.printTestLog(`Error checking checkpoint: ${errorMessage}`);
6229
- return false;
6230
- }
6231
- }
6232
- async function promptCheckpointChoice(checkpointer, threadId) {
6233
- const hasCheckpoint = await checkpointExists(checkpointer, threadId);
6234
- if (!hasCheckpoint) {
6235
- return void 0;
6236
- }
6237
- const choice = await promptUserChoice();
6238
- return choice === "resume";
6239
- }
6240
- function initializeSqliteSaver(dbPath) {
6241
- const dbDir = dbPath.substring(0, dbPath.lastIndexOf("/"));
6242
- if (!fs11.existsSync(dbDir)) {
6243
- fs11.mkdirSync(dbDir, { recursive: true });
6244
- }
6245
- const checkpointer = SqliteSaver.fromConnString(dbPath);
6246
- return checkpointer;
6247
- }
6248
-
6249
- // src/graph.ts
6250
- init_logger();
6251
- async function design2code(url, mode) {
6252
- const urlInfo = parseFigmaUrl(url);
6253
- const threadId = urlInfo.projectName;
6254
- const workspace = workspaceManager.initWorkspace(threadId);
6255
- let checkpointer = initializeSqliteSaver(workspace.db);
6256
- const resume = await promptCheckpointChoice(checkpointer, threadId);
6257
- logger.printInfoLog(`Starting design-to-code process for: ${urlInfo.projectName}`);
6258
- if (resume !== true) {
6259
- workspaceManager.deleteWorkspace(workspace);
6260
- logger.printInfoLog("Starting fresh...");
6261
- checkpointer = initializeSqliteSaver(workspace.db);
6262
- } else {
6263
- logger.printInfoLog("Resuming from cache...");
6264
- }
6265
- await callModel({
6266
- question: "\u8BF7\u4ECB\u7ECD\u4F60\u81EA\u5DF1\uFF0C\u4F60\u662F\u4EC0\u4E48\u6A21\u578B",
6267
- streaming: false
6268
- });
6269
- const graph = new StateGraph(GraphStateAnnotation).addNode("initial" /* INITIAL */, initialProject).addNode("process" /* PROCESS */, generateProtocol).addNode("code" /* CODE */, generateCode).addNode("validation" /* VALIDATION */, runValidation).addEdge(START, "initial" /* INITIAL */).addEdge("initial" /* INITIAL */, "process" /* PROCESS */).addEdge("process" /* PROCESS */, "code" /* CODE */).addEdge("code" /* CODE */, "validation" /* VALIDATION */).addEdge("validation" /* VALIDATION */, END).compile({ checkpointer });
6270
- const config = { configurable: { thread_id: threadId } };
6271
- const validationMode = mode ?? "full";
6272
- const state = resume === true ? null : {
6273
- messages: [],
6274
- urlInfo,
6275
- workspace,
6276
- config: {
6277
- validationMode
6278
- }
6279
- };
6280
- await graph.invoke(state, config);
6281
- logger.printSuccessLog("Design-to-code process completed!");
6282
- }
6283
2191
  export {
2192
+ DEFAULT_STYLING,
2193
+ FigmaImageFormat,
6284
2194
  GraphNode,
6285
- GraphStateAnnotation,
6286
- design2code,
2195
+ INITIAL_AGENT_SYSTEM_PROMPT,
2196
+ ValidationMode,
2197
+ applyPropsAndStateToProtocol,
2198
+ detectRenderingModes,
2199
+ downloadImage,
2200
+ executeFigmaAndImagesActions,
2201
+ extractComponentGroups,
2202
+ extractDataListPrompt,
2203
+ extractHierarchicalNodesByIds,
2204
+ extractJSON,
2205
+ extractNodePositionsHierarchical,
2206
+ figmaTool,
2207
+ flattenPostOrder,
2208
+ generateComponentPrompt,
2209
+ generateFramePrompt,
2210
+ generateStructurePrompt,
2211
+ initialAgentInstruction,
2212
+ parseFigmaUrl,
2213
+ postProcessStructure,
2214
+ saveGeneratedCode,
2215
+ simplifyFigmaNodeForContent,
2216
+ toKebabCase,
2217
+ toPascalCase,
6287
2218
  workspaceManager
6288
2219
  };
6289
2220
  //# sourceMappingURL=index.js.map