@sun-asterisk/sungen 2.5.0 → 2.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/README.md +88 -7
  2. package/dist/cli/commands/add.d.ts.map +1 -1
  3. package/dist/cli/commands/add.js +109 -9
  4. package/dist/cli/commands/add.js.map +1 -1
  5. package/dist/cli/commands/figma.d.ts +11 -0
  6. package/dist/cli/commands/figma.d.ts.map +1 -0
  7. package/dist/cli/commands/figma.js +178 -0
  8. package/dist/cli/commands/figma.js.map +1 -0
  9. package/dist/cli/index.js +4 -2
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  12. package/dist/orchestrator/ai-rules-updater.js +2 -0
  13. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  14. package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts +33 -0
  15. package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts.map +1 -0
  16. package/dist/orchestrator/figma/figma-scaffolder-helpers.js +135 -0
  17. package/dist/orchestrator/figma/figma-scaffolder-helpers.js.map +1 -0
  18. package/dist/orchestrator/figma/figma-scaffolder-types.d.ts +25 -0
  19. package/dist/orchestrator/figma/figma-scaffolder-types.d.ts.map +1 -0
  20. package/dist/orchestrator/figma/figma-scaffolder-types.js +7 -0
  21. package/dist/orchestrator/figma/figma-scaffolder-types.js.map +1 -0
  22. package/dist/orchestrator/figma/figma-scaffolder.d.ts +23 -0
  23. package/dist/orchestrator/figma/figma-scaffolder.d.ts.map +1 -0
  24. package/dist/orchestrator/figma/figma-scaffolder.js +212 -0
  25. package/dist/orchestrator/figma/figma-scaffolder.js.map +1 -0
  26. package/dist/orchestrator/figma/node-path-collapser.d.ts +16 -0
  27. package/dist/orchestrator/figma/node-path-collapser.d.ts.map +1 -0
  28. package/dist/orchestrator/figma/node-path-collapser.js +37 -0
  29. package/dist/orchestrator/figma/node-path-collapser.js.map +1 -0
  30. package/dist/orchestrator/figma/spec-figma-renderer.d.ts +44 -0
  31. package/dist/orchestrator/figma/spec-figma-renderer.d.ts.map +1 -0
  32. package/dist/orchestrator/figma/spec-figma-renderer.js +45 -0
  33. package/dist/orchestrator/figma/spec-figma-renderer.js.map +1 -0
  34. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +23 -0
  35. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts.map +1 -0
  36. package/dist/orchestrator/figma/spec-figma-section-renderers.js +47 -0
  37. package/dist/orchestrator/figma/spec-figma-section-renderers.js.map +1 -0
  38. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
  39. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
  40. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
  41. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +33 -1
  42. package/dist/orchestrator/templates/ai-instructions/claude-config.md +1 -0
  43. package/dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
  44. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +39 -20
  45. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
  46. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
  47. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
  48. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
  49. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +33 -1
  50. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +1 -0
  51. package/dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
  52. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
  53. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
  54. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +51 -25
  55. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
  56. package/dist/tools/figma/figma-auth.d.ts +36 -0
  57. package/dist/tools/figma/figma-auth.d.ts.map +1 -0
  58. package/dist/tools/figma/figma-auth.js +182 -0
  59. package/dist/tools/figma/figma-auth.js.map +1 -0
  60. package/dist/tools/figma/figma-cache.d.ts +45 -0
  61. package/dist/tools/figma/figma-cache.d.ts.map +1 -0
  62. package/dist/tools/figma/figma-cache.js +191 -0
  63. package/dist/tools/figma/figma-cache.js.map +1 -0
  64. package/dist/tools/figma/figma-client-types.d.ts +112 -0
  65. package/dist/tools/figma/figma-client-types.d.ts.map +1 -0
  66. package/dist/tools/figma/figma-client-types.js +7 -0
  67. package/dist/tools/figma/figma-client-types.js.map +1 -0
  68. package/dist/tools/figma/figma-errors.d.ts +49 -0
  69. package/dist/tools/figma/figma-errors.d.ts.map +1 -0
  70. package/dist/tools/figma/figma-errors.js +105 -0
  71. package/dist/tools/figma/figma-errors.js.map +1 -0
  72. package/dist/tools/figma/figma-image-downloader.d.ts +25 -0
  73. package/dist/tools/figma/figma-image-downloader.d.ts.map +1 -0
  74. package/dist/tools/figma/figma-image-downloader.js +128 -0
  75. package/dist/tools/figma/figma-image-downloader.js.map +1 -0
  76. package/dist/tools/figma/figma-node-filter.d.ts +26 -0
  77. package/dist/tools/figma/figma-node-filter.d.ts.map +1 -0
  78. package/dist/tools/figma/figma-node-filter.js +164 -0
  79. package/dist/tools/figma/figma-node-filter.js.map +1 -0
  80. package/dist/tools/figma/figma-rest-client.d.ts +24 -0
  81. package/dist/tools/figma/figma-rest-client.d.ts.map +1 -0
  82. package/dist/tools/figma/figma-rest-client.js +154 -0
  83. package/dist/tools/figma/figma-rest-client.js.map +1 -0
  84. package/dist/tools/figma/figma-url-parser.d.ts +18 -0
  85. package/dist/tools/figma/figma-url-parser.d.ts.map +1 -0
  86. package/dist/tools/figma/figma-url-parser.js +51 -0
  87. package/dist/tools/figma/figma-url-parser.js.map +1 -0
  88. package/dist/utils/exec-file-no-throw.d.ts +20 -0
  89. package/dist/utils/exec-file-no-throw.d.ts.map +1 -0
  90. package/dist/utils/exec-file-no-throw.js +36 -0
  91. package/dist/utils/exec-file-no-throw.js.map +1 -0
  92. package/package.json +1 -1
  93. package/src/cli/commands/add.ts +80 -9
  94. package/src/cli/commands/figma.ts +162 -0
  95. package/src/cli/index.ts +4 -2
  96. package/src/orchestrator/ai-rules-updater.ts +2 -0
  97. package/src/orchestrator/figma/figma-scaffolder-helpers.ts +126 -0
  98. package/src/orchestrator/figma/figma-scaffolder-types.ts +26 -0
  99. package/src/orchestrator/figma/figma-scaffolder.ts +209 -0
  100. package/src/orchestrator/figma/node-path-collapser.ts +38 -0
  101. package/src/orchestrator/figma/spec-figma-renderer.ts +80 -0
  102. package/src/orchestrator/figma/spec-figma-section-renderers.ts +46 -0
  103. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
  104. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
  105. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
  106. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +33 -1
  107. package/src/orchestrator/templates/ai-instructions/claude-config.md +1 -0
  108. package/src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
  109. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +39 -20
  110. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
  111. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
  112. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
  113. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
  114. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +33 -1
  115. package/src/orchestrator/templates/ai-instructions/copilot-config.md +1 -0
  116. package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
  117. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
  118. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
  119. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +51 -25
  120. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
  121. package/src/tools/figma/figma-auth.ts +161 -0
  122. package/src/tools/figma/figma-cache.ts +184 -0
  123. package/src/tools/figma/figma-client-types.ts +125 -0
  124. package/src/tools/figma/figma-errors.ts +127 -0
  125. package/src/tools/figma/figma-image-downloader.ts +112 -0
  126. package/src/tools/figma/figma-node-filter.ts +198 -0
  127. package/src/tools/figma/figma-rest-client.ts +183 -0
  128. package/src/tools/figma/figma-url-parser.ts +55 -0
  129. package/src/utils/exec-file-no-throw.ts +45 -0
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ /**
3
+ * Internal helpers for figma-scaffolder.ts.
4
+ * Extracted to keep the main orchestration file under 200 LOC.
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.sanitizeName = sanitizeName;
41
+ exports.readGeneratorVersion = readGeneratorVersion;
42
+ exports.minimalSpecMd = minimalSpecMd;
43
+ exports.downloadImages = downloadImages;
44
+ const fs = __importStar(require("node:fs"));
45
+ const path = __importStar(require("node:path"));
46
+ const figma_image_downloader_1 = require("../../tools/figma/figma-image-downloader");
47
+ const FigmaCache = __importStar(require("../../tools/figma/figma-cache"));
48
+ // ---------------------------------------------------------------------------
49
+ // Name utilities
50
+ // ---------------------------------------------------------------------------
51
+ /** Sanitize a Figma frame/variant name to a safe filename (lowercase, dashes). */
52
+ function sanitizeName(name) {
53
+ return name
54
+ .toLowerCase()
55
+ .replace(/[^a-z0-9]+/g, '-')
56
+ .replace(/^-+|-+$/g, '');
57
+ }
58
+ // ---------------------------------------------------------------------------
59
+ // Version / stub helpers
60
+ // ---------------------------------------------------------------------------
61
+ /** Read generator version from root package.json. Falls back to "sungen". */
62
+ function readGeneratorVersion(cwd) {
63
+ try {
64
+ const pkgPath = path.join(cwd, 'package.json');
65
+ if (fs.existsSync(pkgPath)) {
66
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
67
+ if (pkg.version)
68
+ return `sungen v${pkg.version}`;
69
+ }
70
+ }
71
+ catch {
72
+ // fall through
73
+ }
74
+ return 'sungen';
75
+ }
76
+ /** Minimal spec.md stub pointing users to spec_figma.md. */
77
+ function minimalSpecMd(screenName) {
78
+ return `# ${screenName} Screen Specification
79
+
80
+ <!-- This file is the human-authored source of truth. -->
81
+ <!-- spec_figma.md contains auto-generated Figma data — copy useful sections here. -->
82
+
83
+ ## Overview
84
+ - **URL Path:** /${screenName}
85
+ - **Auth Required:** no
86
+ - **Platform:** web
87
+
88
+ ## Sections
89
+
90
+ <!-- Add sections, fields, validation rules, and business rules here. -->
91
+ `;
92
+ }
93
+ /**
94
+ * Download (or copy from cache) images for each node ID.
95
+ * Returns relative paths (e.g. "ui/login-frame.png") for each successfully written file.
96
+ */
97
+ async function downloadImages(opts) {
98
+ const imagePaths = [];
99
+ for (let i = 0; i < opts.imageNodeIds.length; i++) {
100
+ const nodeId = opts.imageNodeIds[i];
101
+ const isMain = nodeId === opts.mainNodeId;
102
+ const nameSuffix = isMain
103
+ ? opts.frameBaseName
104
+ : sanitizeName(opts.variantNames[i - 1] ?? `variant-${i}`);
105
+ const destPath = path.join(opts.uiDir, `${nameSuffix}.png`);
106
+ const relPath = `ui/${nameSuffix}.png`;
107
+ // Cache hit path
108
+ if (opts.cachedImages.has(nodeId)) {
109
+ opts.spinner.start(`Writing cached image: ${nameSuffix}.png`);
110
+ fs.writeFileSync(destPath, opts.cachedImages.get(nodeId));
111
+ imagePaths.push(relPath);
112
+ opts.spinner.succeed(` ui/${nameSuffix}.png (cached)`);
113
+ continue;
114
+ }
115
+ const url = opts.imageUrls[nodeId];
116
+ if (!url) {
117
+ opts.spinner.warn(` No image URL for node ${nodeId} — skipping`);
118
+ continue;
119
+ }
120
+ opts.spinner.start(`Downloading ui/${nameSuffix}.png…`);
121
+ try {
122
+ await (0, figma_image_downloader_1.downloadToPath)(url, destPath);
123
+ // Cache downloaded bytes for future runs
124
+ const bytes = fs.readFileSync(destPath);
125
+ FigmaCache.put(opts.cwd, opts.fileKey, opts.versionId, nodeId, opts.scaleKind, bytes);
126
+ imagePaths.push(relPath);
127
+ opts.spinner.succeed(` ui/${nameSuffix}.png`);
128
+ }
129
+ catch {
130
+ opts.spinner.warn(` Failed to download ui/${nameSuffix}.png — skipping`);
131
+ }
132
+ }
133
+ return imagePaths;
134
+ }
135
+ //# sourceMappingURL=figma-scaffolder-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-scaffolder-helpers.js","sourceRoot":"","sources":["../../../src/orchestrator/figma/figma-scaffolder-helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared input/output types for FigmaScaffolder.
3
+ * Kept separate to avoid growing figma-client-types.ts beyond its scope.
4
+ */
5
+ export interface FigmaScaffolderOptions {
6
+ /** Screen name (e.g. "login", "dashboard"). */
7
+ screenName: string;
8
+ /** Full Figma share URL. */
9
+ figmaUrl: string;
10
+ /** Absolute or cwd-relative working directory. Default: process.cwd(). */
11
+ cwd?: string;
12
+ /** When true, bypass cache and re-fetch from Figma API. Default: false. */
13
+ refresh?: boolean;
14
+ /** Render scale for PNG export. Default: 2. */
15
+ scale?: number;
16
+ }
17
+ export interface FigmaScaffolderResult {
18
+ /** Absolute path to the written spec_figma.md. */
19
+ specFigmaPath: string;
20
+ /** Absolute paths to downloaded PNG files. */
21
+ imagePaths: string[];
22
+ /** True when spec.md was newly created (was not already present). */
23
+ specMdCreated: boolean;
24
+ }
25
+ //# sourceMappingURL=figma-scaffolder-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-scaffolder-types.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/figma/figma-scaffolder-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,sBAAsB;IACrC,+CAA+C;IAC/C,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,2EAA2E;IAC3E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,kDAAkD;IAClD,aAAa,EAAE,MAAM,CAAC;IACtB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,qEAAqE;IACrE,aAAa,EAAE,OAAO,CAAC;CACxB"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Shared input/output types for FigmaScaffolder.
4
+ * Kept separate to avoid growing figma-client-types.ts beyond its scope.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=figma-scaffolder-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-scaffolder-types.js","sourceRoot":"","sources":["../../../src/orchestrator/figma/figma-scaffolder-types.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * FigmaScaffolder: orchestrates Figma URL → spec_figma.md + ui/*.png.
3
+ *
4
+ * Flow: parseFigmaUrl → assertSafeToUse → loadPat → cache check
5
+ * → getFileNodes → filterFigmaNode → getImageUrls → downloadImages
6
+ * → renderSpecFigma → write files
7
+ *
8
+ * Idempotent: re-running overwrites spec_figma.md and PNGs.
9
+ * spec.md: written only if absent (never overwritten).
10
+ * Cache: keyed by file+version+node; bypassed when refresh=true.
11
+ *
12
+ * Helpers (image download, name utils, stubs) live in figma-scaffolder-helpers.ts.
13
+ */
14
+ import type { FigmaScaffolderOptions, FigmaScaffolderResult } from './figma-scaffolder-types';
15
+ export declare class FigmaScaffolder {
16
+ /**
17
+ * Orchestrate the full Figma → screen files flow.
18
+ *
19
+ * @throws Error with remediation instructions on auth / parse / network failure.
20
+ */
21
+ static run(options: FigmaScaffolderOptions): Promise<FigmaScaffolderResult>;
22
+ }
23
+ //# sourceMappingURL=figma-scaffolder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-scaffolder.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/figma/figma-scaffolder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAkBH,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAK9F,qBAAa,eAAe;IAC1B;;;;OAIG;WACU,GAAG,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC;CAuKlF"}
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ /**
3
+ * FigmaScaffolder: orchestrates Figma URL → spec_figma.md + ui/*.png.
4
+ *
5
+ * Flow: parseFigmaUrl → assertSafeToUse → loadPat → cache check
6
+ * → getFileNodes → filterFigmaNode → getImageUrls → downloadImages
7
+ * → renderSpecFigma → write files
8
+ *
9
+ * Idempotent: re-running overwrites spec_figma.md and PNGs.
10
+ * spec.md: written only if absent (never overwritten).
11
+ * Cache: keyed by file+version+node; bypassed when refresh=true.
12
+ *
13
+ * Helpers (image download, name utils, stubs) live in figma-scaffolder-helpers.ts.
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __importDefault = (this && this.__importDefault) || function (mod) {
49
+ return (mod && mod.__esModule) ? mod : { "default": mod };
50
+ };
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.FigmaScaffolder = void 0;
53
+ const fs = __importStar(require("node:fs"));
54
+ const path = __importStar(require("node:path"));
55
+ const ora_1 = __importDefault(require("ora"));
56
+ const figma_url_parser_1 = require("../../tools/figma/figma-url-parser");
57
+ const figma_auth_1 = require("../../tools/figma/figma-auth");
58
+ const figma_rest_client_1 = require("../../tools/figma/figma-rest-client");
59
+ const figma_node_filter_1 = require("../../tools/figma/figma-node-filter");
60
+ const FigmaCache = __importStar(require("../../tools/figma/figma-cache"));
61
+ const spec_figma_renderer_1 = require("./spec-figma-renderer");
62
+ const figma_scaffolder_helpers_1 = require("./figma-scaffolder-helpers");
63
+ // Max variant images to download in addition to the main frame.
64
+ const MAX_VARIANT_IMAGES = 5;
65
+ class FigmaScaffolder {
66
+ /**
67
+ * Orchestrate the full Figma → screen files flow.
68
+ *
69
+ * @throws Error with remediation instructions on auth / parse / network failure.
70
+ */
71
+ static async run(options) {
72
+ const cwd = options.cwd ?? process.cwd();
73
+ const refresh = options.refresh ?? false;
74
+ const scale = options.scale ?? 2;
75
+ const spinner = (0, ora_1.default)({ stream: process.stderr });
76
+ // 1. Parse and validate Figma URL
77
+ const ref = (0, figma_url_parser_1.parseFigmaUrl)(options.figmaUrl);
78
+ if (!ref) {
79
+ throw new Error(`Invalid Figma URL: "${options.figmaUrl}"\n` +
80
+ 'Expected format: https://www.figma.com/design/<KEY>/<name>?node-id=1-23');
81
+ }
82
+ if (!ref.nodeId) {
83
+ throw new Error('Figma URL must include a node-id query parameter.\n' +
84
+ 'Select a specific frame in Figma, right-click → "Copy link to selection".');
85
+ }
86
+ // 2. Security scan + PAT load
87
+ spinner.start('Checking Figma auth…');
88
+ await (0, figma_auth_1.assertSafeToUse)(cwd);
89
+ const pat = (0, figma_auth_1.loadPat)(cwd);
90
+ if (!pat) {
91
+ spinner.fail('Figma PAT not found.');
92
+ throw new Error('Figma PAT is not configured.\nRun: sungen figma auth set');
93
+ }
94
+ spinner.succeed('Figma auth OK');
95
+ // 3. Ensure screen directories exist
96
+ const screenDir = path.join(cwd, 'qa', 'screens', options.screenName);
97
+ const requirementsDir = path.join(screenDir, 'requirements');
98
+ const uiDir = path.join(requirementsDir, 'ui');
99
+ fs.mkdirSync(uiDir, { recursive: true });
100
+ // 4. Resolve node: prefer raw-JSON cache to spare Starter-tier REST quota.
101
+ // On Figma Starter, `/v1/files/:key/nodes` counts against a tiny daily
102
+ // bucket (observed Retry-After up to 4+ days). If we already have a
103
+ // cached raw response, reuse it and skip the call entirely.
104
+ let versionId;
105
+ let nodeDocument;
106
+ let fromCache = false;
107
+ if (!refresh) {
108
+ const cachedVersion = FigmaCache.findLatestCachedVersion(cwd, ref.fileKey, ref.nodeId);
109
+ if (cachedVersion) {
110
+ const cachedRaw = FigmaCache.get(cwd, ref.fileKey, cachedVersion, ref.nodeId, 'raw');
111
+ if (cachedRaw) {
112
+ try {
113
+ nodeDocument = JSON.parse(cachedRaw.toString('utf8'));
114
+ versionId = cachedVersion;
115
+ fromCache = true;
116
+ spinner.info(`Reusing cached Figma node (version ${versionId}) — pass --refresh to re-fetch`);
117
+ }
118
+ catch {
119
+ // Corrupt cache → fall through to API fetch
120
+ }
121
+ }
122
+ }
123
+ }
124
+ if (!fromCache) {
125
+ spinner.start(`Fetching Figma node ${ref.nodeId}…`);
126
+ let nodesResponse;
127
+ try {
128
+ nodesResponse = await (0, figma_rest_client_1.getFileNodes)(pat, ref.fileKey, [ref.nodeId]);
129
+ }
130
+ catch (err) {
131
+ spinner.fail('Failed to fetch Figma node.');
132
+ throw err;
133
+ }
134
+ versionId = nodesResponse.version;
135
+ const nodeEntry = nodesResponse.nodes[ref.nodeId];
136
+ if (!nodeEntry) {
137
+ spinner.fail(`Node ${ref.nodeId} not found in Figma file.`);
138
+ throw new Error(`Node "${ref.nodeId}" was not found in file "${ref.fileKey}".\n` +
139
+ 'Check the Figma URL — the node-id may be incorrect.');
140
+ }
141
+ spinner.succeed(`Fetched node: ${nodeEntry.document.name}`);
142
+ nodeDocument = nodeEntry.document;
143
+ // Persist raw (unfiltered) node JSON for downstream LLM synthesis + future
144
+ // cache-first runs. Re-runs overwrite atomically.
145
+ FigmaCache.put(cwd, ref.fileKey, versionId, ref.nodeId, 'raw', JSON.stringify(nodeDocument));
146
+ }
147
+ // nodeEntry.document shape — trust the cached bytes we wrote earlier.
148
+ const nodeEntry = { document: nodeDocument };
149
+ // 5. Filter node tree + extract metadata
150
+ const filteredNode = (0, figma_node_filter_1.filterFigmaNode)(nodeEntry.document);
151
+ const textLabels = (0, figma_node_filter_1.extractTextLabels)(filteredNode);
152
+ const variants = (0, figma_node_filter_1.extractVariants)(filteredNode);
153
+ // 6. Resolve image node IDs (main + up to MAX_VARIANT_IMAGES variants)
154
+ const imageNodeIds = [ref.nodeId, ...variants.slice(0, MAX_VARIANT_IMAGES).map((v) => v.nodeId)];
155
+ const scaleKind = `${scale}x`;
156
+ // 7. Warm image cache (skipped when refresh=true)
157
+ const cachedImages = new Map();
158
+ if (!refresh) {
159
+ for (const nodeId of imageNodeIds) {
160
+ const cached = FigmaCache.get(cwd, ref.fileKey, versionId, nodeId, scaleKind);
161
+ if (cached)
162
+ cachedImages.set(nodeId, cached);
163
+ }
164
+ }
165
+ // 8. Fetch image URLs for uncached nodes
166
+ const uncachedIds = imageNodeIds.filter((id) => !cachedImages.has(id));
167
+ let imageUrls = {};
168
+ if (uncachedIds.length > 0) {
169
+ spinner.start(`Fetching image URLs for ${uncachedIds.length} node(s)…`);
170
+ try {
171
+ const urlsResponse = await (0, figma_rest_client_1.getImageUrls)(pat, ref.fileKey, uncachedIds, { scale, format: 'png' });
172
+ imageUrls = urlsResponse.images;
173
+ }
174
+ catch (err) {
175
+ spinner.fail('Failed to fetch image URLs.');
176
+ throw err;
177
+ }
178
+ spinner.succeed('Image URLs fetched');
179
+ }
180
+ // 9. Download (or copy from cache) images
181
+ const frameBaseName = (0, figma_scaffolder_helpers_1.sanitizeName)(filteredNode.name);
182
+ const variantNames = variants.slice(0, MAX_VARIANT_IMAGES).map((v) => v.name);
183
+ const imagePaths = await (0, figma_scaffolder_helpers_1.downloadImages)({
184
+ cwd, fileKey: ref.fileKey, versionId, uiDir,
185
+ imageNodeIds, mainNodeId: ref.nodeId, variantNames,
186
+ cachedImages, imageUrls, frameBaseName, scaleKind, spinner,
187
+ });
188
+ // 10. Render and write spec_figma.md (always overwritten)
189
+ spinner.start('Rendering spec_figma.md…');
190
+ const markdown = (0, spec_figma_renderer_1.renderSpecFigma)({
191
+ filteredNode, variants, textLabels, imagePaths,
192
+ fileKey: ref.fileKey, nodeId: ref.nodeId, versionId,
193
+ fetchedAt: new Date().toISOString(),
194
+ generatorVersion: (0, figma_scaffolder_helpers_1.readGeneratorVersion)(cwd),
195
+ });
196
+ const specFigmaPath = path.join(requirementsDir, 'spec_figma.md');
197
+ fs.writeFileSync(specFigmaPath, markdown, 'utf8');
198
+ spinner.succeed('spec_figma.md written');
199
+ // 11. Write spec.md stub only if absent
200
+ const specMdPath = path.join(requirementsDir, 'spec.md');
201
+ const specMdCreated = !fs.existsSync(specMdPath);
202
+ if (specMdCreated) {
203
+ fs.writeFileSync(specMdPath, (0, figma_scaffolder_helpers_1.minimalSpecMd)(options.screenName), 'utf8');
204
+ spinner.info('spec.md created (stub — fill in your requirements)');
205
+ }
206
+ // 12. Evict old cache versions (keep last 3)
207
+ FigmaCache.bustOldVersions(cwd, ref.fileKey, versionId);
208
+ return { specFigmaPath, imagePaths, specMdCreated };
209
+ }
210
+ }
211
+ exports.FigmaScaffolder = FigmaScaffolder;
212
+ //# sourceMappingURL=figma-scaffolder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-scaffolder.js","sourceRoot":"","sources":["../../../src/orchestrator/figma/figma-scaffolder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Pure helper: collapse consecutive repeated segments in a Figma node path.
3
+ *
4
+ * Delimiter: " > " (space-angle-space).
5
+ * Collapsed runs use the multiplication sign × (U+00D7).
6
+ *
7
+ * Examples:
8
+ * "A > B > C" → "A > B > C" (unchanged)
9
+ * "A > B > B > C" → "A > B×2 > C"
10
+ * "Container > Container > Container > X" → "Container×3 > X"
11
+ * "A > A > A > A > A > B > C" → "A×5 > B > C"
12
+ * "A" → "A" (single)
13
+ * "" → "" (empty)
14
+ */
15
+ export declare function collapseNodePath(path: string): string;
16
+ //# sourceMappingURL=node-path-collapser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-path-collapser.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/figma/node-path-collapser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAuBrD"}
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collapseNodePath = collapseNodePath;
4
+ /**
5
+ * Pure helper: collapse consecutive repeated segments in a Figma node path.
6
+ *
7
+ * Delimiter: " > " (space-angle-space).
8
+ * Collapsed runs use the multiplication sign × (U+00D7).
9
+ *
10
+ * Examples:
11
+ * "A > B > C" → "A > B > C" (unchanged)
12
+ * "A > B > B > C" → "A > B×2 > C"
13
+ * "Container > Container > Container > X" → "Container×3 > X"
14
+ * "A > A > A > A > A > B > C" → "A×5 > B > C"
15
+ * "A" → "A" (single)
16
+ * "" → "" (empty)
17
+ */
18
+ function collapseNodePath(path) {
19
+ if (!path)
20
+ return path;
21
+ const DELIMITER = ' > ';
22
+ const segments = path.split(DELIMITER);
23
+ const collapsed = [];
24
+ let i = 0;
25
+ while (i < segments.length) {
26
+ const current = segments[i];
27
+ let count = 1;
28
+ // Count consecutive identical segments
29
+ while (i + count < segments.length && segments[i + count] === current) {
30
+ count++;
31
+ }
32
+ collapsed.push(count > 1 ? `${current}×${count}` : current);
33
+ i += count;
34
+ }
35
+ return collapsed.join(DELIMITER);
36
+ }
37
+ //# sourceMappingURL=node-path-collapser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-path-collapser.js","sourceRoot":"","sources":["../../../src/orchestrator/figma/node-path-collapser.ts"],"names":[],"mappings":";;AAcA,4CAuBC;AArCD;;;;;;;;;;;;;GAaG;AACH,SAAgB,gBAAgB,CAAC,IAAY;IAC3C,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IAEvB,MAAM,SAAS,GAAG,KAAK,CAAC;IACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAEvC,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,IAAI,CAAC,GAAG,CAAC,CAAC;IAEV,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC3B,MAAM,OAAO,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,uCAAuC;QACvC,OAAO,CAAC,GAAG,KAAK,GAAG,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,OAAO,EAAE,CAAC;YACtE,KAAK,EAAE,CAAC;QACV,CAAC;QAED,SAAS,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC5D,CAAC,IAAI,KAAK,CAAC;IACb,CAAC;IAED,OAAO,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Pure function: filtered Figma node → spec_figma.md envelope string.
3
+ *
4
+ * The envelope is deterministic and stable across runs: frontmatter,
5
+ * auto-gen banner, Frame metadata, Screenshots, and a SYNTHESIS marker.
6
+ * Narrative sections (Purpose, ASCII Layout, Regions, Actions, Form Fields,
7
+ * Data Columns, Navigation) are appended BELOW the marker by the
8
+ * sungen-figma-source skill, which reads the raw cached node JSON.
9
+ *
10
+ * Section renderers live in spec-figma-section-renderers.ts.
11
+ *
12
+ * This module never reads from disk or makes network calls.
13
+ */
14
+ import type { FilteredFigmaNode, FigmaTextLabel, FigmaVariantDefinition } from '../../tools/figma/figma-client-types';
15
+ export interface RenderSpecFigmaInput {
16
+ /** Root filtered node (the requested frame / component). */
17
+ filteredNode: FilteredFigmaNode;
18
+ /** Variant definitions from a COMPONENT_SET parent, or [] if none. Kept for compatibility. */
19
+ variants: FigmaVariantDefinition[];
20
+ /** Flat text labels extracted from the node tree. Kept for compatibility. */
21
+ textLabels: FigmaTextLabel[];
22
+ /** Relative paths of downloaded image files (e.g., ["ui/home.png", "ui/home-dark.png"]). */
23
+ imagePaths: string[];
24
+ /** Figma file key. */
25
+ fileKey: string;
26
+ /** Node ID in API form (e.g. "1:23"). */
27
+ nodeId: string;
28
+ /** Figma file version ID at fetch time. */
29
+ versionId: string;
30
+ /** ISO timestamp of when data was fetched. */
31
+ fetchedAt: string;
32
+ /** Generator version string (e.g., "sungen v2.4.5"). */
33
+ generatorVersion: string;
34
+ }
35
+ /**
36
+ * Render the deterministic envelope for spec_figma.md.
37
+ * The LLM synthesis step (sungen-figma-source skill) appends the narrative
38
+ * sections AFTER the SYNTHESIS_MARKER comment. Re-synthesis replaces
39
+ * everything from the marker to EOF.
40
+ *
41
+ * Pure function — no I/O side effects.
42
+ */
43
+ export declare function renderSpecFigma(input: RenderSpecFigmaInput): string;
44
+ //# sourceMappingURL=spec-figma-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-figma-renderer.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/figma/spec-figma-renderer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,iBAAiB,EACjB,cAAc,EACd,sBAAsB,EACvB,MAAM,sCAAsC,CAAC;AAY9C,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,YAAY,EAAE,iBAAiB,CAAC;IAChC,8FAA8F;IAC9F,QAAQ,EAAE,sBAAsB,EAAE,CAAC;IACnC,6EAA6E;IAC7E,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,4FAA4F;IAC5F,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,yCAAyC;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAMD;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAgBnE"}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * Pure function: filtered Figma node → spec_figma.md envelope string.
4
+ *
5
+ * The envelope is deterministic and stable across runs: frontmatter,
6
+ * auto-gen banner, Frame metadata, Screenshots, and a SYNTHESIS marker.
7
+ * Narrative sections (Purpose, ASCII Layout, Regions, Actions, Form Fields,
8
+ * Data Columns, Navigation) are appended BELOW the marker by the
9
+ * sungen-figma-source skill, which reads the raw cached node JSON.
10
+ *
11
+ * Section renderers live in spec-figma-section-renderers.ts.
12
+ *
13
+ * This module never reads from disk or makes network calls.
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.renderSpecFigma = renderSpecFigma;
17
+ const spec_figma_section_renderers_1 = require("./spec-figma-section-renderers");
18
+ // ---------------------------------------------------------------------------
19
+ // Public API
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * Render the deterministic envelope for spec_figma.md.
23
+ * The LLM synthesis step (sungen-figma-source skill) appends the narrative
24
+ * sections AFTER the SYNTHESIS_MARKER comment. Re-synthesis replaces
25
+ * everything from the marker to EOF.
26
+ *
27
+ * Pure function — no I/O side effects.
28
+ */
29
+ function renderSpecFigma(input) {
30
+ const sections = [
31
+ (0, spec_figma_section_renderers_1.renderFrontmatter)(input),
32
+ '',
33
+ '> Envelope is AUTO-GENERATED. Narrative sections below the marker are LLM-synthesized.',
34
+ '> To refine, copy useful parts into ../spec.md (authoritative).',
35
+ '',
36
+ (0, spec_figma_section_renderers_1.renderFrame)(input.filteredNode),
37
+ '',
38
+ (0, spec_figma_section_renderers_1.renderScreenshots)(input.imagePaths),
39
+ '',
40
+ spec_figma_section_renderers_1.SYNTHESIS_MARKER,
41
+ '',
42
+ ];
43
+ return sections.join('\n') + '\n';
44
+ }
45
+ //# sourceMappingURL=spec-figma-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-figma-renderer.js","sourceRoot":"","sources":["../../../src/orchestrator/figma/spec-figma-renderer.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;AAmDH,0CAgBC;AA5DD,iFAKwC;AA2BxC,8EAA8E;AAC9E,aAAa;AACb,8EAA8E;AAE9E;;;;;;;GAOG;AACH,SAAgB,eAAe,CAAC,KAA2B;IACzD,MAAM,QAAQ,GAAG;QACf,IAAA,gDAAiB,EAAC,KAAK,CAAC;QACxB,EAAE;QACF,wFAAwF;QACxF,iEAAiE;QACjE,EAAE;QACF,IAAA,0CAAW,EAAC,KAAK,CAAC,YAAY,CAAC;QAC/B,EAAE;QACF,IAAA,gDAAiB,EAAC,KAAK,CAAC,UAAU,CAAC;QACnC,EAAE;QACF,+CAAgB;QAChB,EAAE;KACH,CAAC;IAEF,OAAO,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACpC,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Section-render helpers for spec-figma-renderer.ts.
3
+ *
4
+ * After the shift to LLM-synthesized narrative sections, the envelope only
5
+ * needs: frontmatter, Frame metadata, Screenshots, and the SYNTHESIS marker.
6
+ * All prose sections (Purpose, ASCII Layout, Regions, Actions, Form Fields,
7
+ * Data Columns, Navigation) are appended below the marker by the
8
+ * sungen-figma-source skill.
9
+ *
10
+ * Pure — no I/O.
11
+ */
12
+ import type { FilteredFigmaNode } from '../../tools/figma/figma-client-types';
13
+ import type { RenderSpecFigmaInput } from './spec-figma-renderer';
14
+ /**
15
+ * Sentinel comment that separates the deterministic envelope (above) from
16
+ * the LLM-synthesized narrative (below). Re-synthesis replaces everything
17
+ * from this marker to EOF.
18
+ */
19
+ export declare const SYNTHESIS_MARKER = "<!-- SYNTHESIS-BELOW -->";
20
+ export declare function renderFrontmatter(input: RenderSpecFigmaInput): string;
21
+ export declare function renderFrame(node: FilteredFigmaNode): string;
22
+ export declare function renderScreenshots(imagePaths: string[]): string;
23
+ //# sourceMappingURL=spec-figma-section-renderers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spec-figma-section-renderers.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/figma/spec-figma-section-renderers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAElE;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,6BAA6B,CAAC;AAE3D,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAWrE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAI3D;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAI9D"}