figma-cache-toolchain 2.0.2 → 2.0.4

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 (40) hide show
  1. package/README.md +201 -170
  2. package/cursor-bootstrap/AGENT-SETUP-PROMPT.md +34 -29
  3. package/cursor-bootstrap/examples/README.md +26 -11
  4. package/cursor-bootstrap/examples/generated-ui-reset.css.template +32 -0
  5. package/cursor-bootstrap/examples/ui-adapter.contract.template.json +90 -0
  6. package/cursor-bootstrap/examples/ui-execution-template.fast.md +11 -0
  7. package/cursor-bootstrap/examples/ui-execution-template.strict.md +13 -0
  8. package/cursor-bootstrap/examples/ui-override.template.json +26 -0
  9. package/cursor-bootstrap/figma-cache.config.example.js +51 -9
  10. package/cursor-bootstrap/managed-files.json +41 -0
  11. package/cursor-bootstrap/rules/02-figma-stack-adapter.mdc +15 -25
  12. package/cursor-bootstrap/rules/03-figma-ui-implementation-hard-constraints.mdc +58 -91
  13. package/cursor-bootstrap/rules/04-ui-baseline-governance.mdc +35 -86
  14. package/cursor-bootstrap/skills/figma-ui-dual-mode-execution/SKILL.md +13 -8
  15. package/figma-cache/adapters/recipes/button.recipe.json +24 -0
  16. package/figma-cache/adapters/recipes/card.recipe.json +24 -0
  17. package/figma-cache/adapters/recipes/checkbox.recipe.json +24 -0
  18. package/figma-cache/adapters/recipes/input.recipe.json +24 -0
  19. package/figma-cache/adapters/recipes/modal.recipe.json +25 -0
  20. package/figma-cache/adapters/recipes/radio.recipe.json +23 -0
  21. package/figma-cache/adapters/recipes/select.recipe.json +24 -0
  22. package/figma-cache/adapters/recipes/table.recipe.json +25 -0
  23. package/figma-cache/adapters/recipes/tabs.recipe.json +24 -0
  24. package/figma-cache/adapters/recipes/tooltip.recipe.json +24 -0
  25. package/figma-cache/docs/README.md +322 -237
  26. package/figma-cache/docs/p0-ui-preflight-handoff.md +207 -0
  27. package/figma-cache/docs/ui-1to1-optimization-roadmap.md +182 -0
  28. package/figma-cache/docs/ui-1to1-report.schema.json +104 -0
  29. package/figma-cache/figma-cache.js +639 -556
  30. package/figma-cache/js/contract-check-cli.js +466 -0
  31. package/figma-cache/js/cursor-bootstrap-cli.js +240 -202
  32. package/figma-cache/js/ui-facts-normalizer.js +233 -0
  33. package/package.json +95 -74
  34. package/scripts/cross-project-e2e.js +453 -0
  35. package/scripts/ui-1to1-audit.js +431 -0
  36. package/scripts/ui-auto-acceptance.js +248 -0
  37. package/scripts/ui-preflight.js +289 -0
  38. package/scripts/ui-profile.js +46 -0
  39. package/scripts/ui-report-aggregate.js +124 -0
  40. package/cursor-bootstrap/skills/ui-baseline-governance/SKILL.md +0 -51
@@ -1,202 +1,240 @@
1
- /* eslint-disable no-console */
2
-
3
- function readUtf8IfExists(fs, absPath) {
4
- if (!fs.existsSync(absPath)) {
5
- return "";
6
- }
7
- return fs.readFileSync(absPath, "utf8");
8
- }
9
-
10
- function copyCursorBootstrap(force, deps) {
11
- const {
12
- fs,
13
- path,
14
- ROOT,
15
- CACHE_DIR,
16
- CURSOR_BOOTSTRAP_DIR,
17
- normalizeSlash,
18
- readSelfNpmPackageName,
19
- packageDir,
20
- } = deps;
21
-
22
- const pairs = [
23
- {
24
- from: path.join("rules", "00-output-token-budget.mdc"),
25
- to: path.join(".cursor", "rules", "00-output-token-budget.mdc"),
26
- },
27
- {
28
- from: path.join("rules", "01-figma-cache-core.mdc"),
29
- to: path.join(".cursor", "rules", "01-figma-cache-core.mdc"),
30
- },
31
- {
32
- from: path.join("rules", "02-figma-stack-adapter.mdc"),
33
- to: path.join(".cursor", "rules", "02-figma-stack-adapter.mdc"),
34
- },
35
- {
36
- from: path.join("rules", "03-figma-ui-implementation-hard-constraints.mdc"),
37
- to: path.join(".cursor", "rules", "03-figma-ui-implementation-hard-constraints.mdc"),
38
- },
39
- {
40
- from: path.join("rules", "04-ui-baseline-governance.mdc"),
41
- to: path.join(".cursor", "rules", "04-ui-baseline-governance.mdc"),
42
- },
43
- {
44
- from: path.join("rules", "figma-local-cache-first.mdc"),
45
- to: path.join(".cursor", "rules", "figma-local-cache-first.mdc"),
46
- },
47
- {
48
- from: path.join("skills", "figma-mcp-local-cache", "SKILL.md"),
49
- to: path.join(".cursor", "skills", "figma-mcp-local-cache", "SKILL.md"),
50
- },
51
- {
52
- from: path.join("skills", "ui-baseline-governance", "SKILL.md"),
53
- to: path.join(".cursor", "skills", "ui-baseline-governance", "SKILL.md"),
54
- },
55
- {
56
- from: path.join("skills", "figma-ui-dual-mode-execution", "SKILL.md"),
57
- to: path.join(".cursor", "skills", "figma-ui-dual-mode-execution", "SKILL.md"),
58
- },
59
- ];
60
-
61
- if (!fs.existsSync(CURSOR_BOOTSTRAP_DIR)) {
62
- console.error(
63
- `[figma-cache] cursor-bootstrap not found at ${normalizeSlash(CURSOR_BOOTSTRAP_DIR)} (broken package install?)`
64
- );
65
- process.exit(1);
66
- }
67
-
68
- const overwrite = !force;
69
- let copied = 0;
70
- let skipped = 0;
71
- pairs.forEach(({ from: relFrom, to: relTo }) => {
72
- const absFrom = path.join(CURSOR_BOOTSTRAP_DIR, relFrom);
73
- const absTo = path.join(ROOT, relTo);
74
- if (!fs.existsSync(absFrom)) {
75
- console.error(`[figma-cache] missing template file: ${normalizeSlash(absFrom)}`);
76
- process.exit(1);
77
- }
78
- fs.mkdirSync(path.dirname(absTo), { recursive: true });
79
- if (fs.existsSync(absTo) && !overwrite) {
80
- skipped += 1;
81
- return;
82
- }
83
- fs.copyFileSync(absFrom, absTo);
84
- copied += 1;
85
- });
86
-
87
- const configTemplatePath = path.join(CURSOR_BOOTSTRAP_DIR, "figma-cache.config.example.js");
88
- const projectConfigPath = path.join(ROOT, "figma-cache.config.js");
89
- const legacyExamplePath = path.join(ROOT, "figma-cache.config.example.js");
90
-
91
- if (!fs.existsSync(configTemplatePath)) {
92
- console.error(`[figma-cache] missing template file: ${normalizeSlash(configTemplatePath)}`);
93
- process.exit(1);
94
- }
95
-
96
- const hadProjectConfig = fs.existsSync(projectConfigPath);
97
- const hadLegacyExample = fs.existsSync(legacyExamplePath);
98
- const configTemplateBody = fs.readFileSync(configTemplatePath, "utf8");
99
-
100
- let configAction = "skipped";
101
- let configSource = "existing";
102
- if (hadProjectConfig && !force) {
103
- configAction = "skipped";
104
- configSource = "existing";
105
- } else if (!hadProjectConfig && hadLegacyExample && !force) {
106
- fs.copyFileSync(legacyExamplePath, projectConfigPath);
107
- configAction = "created";
108
- configSource = "legacy-example";
109
- } else {
110
- fs.writeFileSync(projectConfigPath, configTemplateBody, "utf8");
111
- configAction = hadProjectConfig ? "overwritten" : "created";
112
- configSource = "template";
113
- }
114
-
115
- let legacyExampleStatus = "not-found";
116
- if (fs.existsSync(legacyExamplePath)) {
117
- const legacyBody = readUtf8IfExists(fs, legacyExamplePath);
118
- const projectBody = readUtf8IfExists(fs, projectConfigPath);
119
- const sameAsTemplate = legacyBody === configTemplateBody;
120
- const sameAsProject = projectBody && legacyBody === projectBody;
121
- if (sameAsTemplate || sameAsProject) {
122
- fs.unlinkSync(legacyExamplePath);
123
- legacyExampleStatus = "deleted";
124
- } else {
125
- legacyExampleStatus = "kept-customized";
126
- }
127
- }
128
-
129
- const agentSrc = path.join(CURSOR_BOOTSTRAP_DIR, "AGENT-SETUP-PROMPT.md");
130
- const agentDest = path.join(ROOT, "AGENT-SETUP-PROMPT.md");
131
- if (!fs.existsSync(agentSrc)) {
132
- console.error(`[figma-cache] missing ${normalizeSlash(agentSrc)}`);
133
- process.exit(1);
134
- }
135
-
136
- let agentBody = fs.readFileSync(agentSrc, "utf8");
137
- const npmPkg = readSelfNpmPackageName();
138
- agentBody = agentBody.replace(/\{\{NPM_PACKAGE_NAME\}\}/g, npmPkg);
139
- fs.writeFileSync(agentDest, agentBody, "utf8");
140
-
141
- const colleagueSrc = path.join(packageDir, "docs", "colleague-guide-zh.md");
142
- const colleagueDest = path.join(CACHE_DIR, "docs", "colleague-guide-zh.md");
143
- if (!fs.existsSync(colleagueSrc)) {
144
- console.error(`[figma-cache] missing ${normalizeSlash(colleagueSrc)} (broken package install?)`);
145
- process.exit(1);
146
- }
147
- const colleagueSameFile = path.resolve(colleagueSrc) === path.resolve(colleagueDest);
148
- if (!colleagueSameFile) {
149
- fs.mkdirSync(path.dirname(colleagueDest), { recursive: true });
150
- fs.copyFileSync(colleagueSrc, colleagueDest);
151
- }
152
-
153
- console.log(
154
- JSON.stringify(
155
- {
156
- ok: true,
157
- root: normalizeSlash(ROOT),
158
- copied,
159
- skipped,
160
- force: !!force,
161
- overwriteByDefault: overwrite,
162
- hint: skipped
163
- ? "Some template files were skipped (--force means keep existing files)."
164
- : overwrite
165
- ? "Done. Existing .cursor templates were overwritten by latest bootstrap."
166
- : "Done.",
167
- configFile: normalizeSlash(projectConfigPath),
168
- configAction,
169
- configSource,
170
- legacyExampleFile: normalizeSlash(legacyExamplePath),
171
- legacyExampleStatus,
172
- agentPromptFile: normalizeSlash(agentDest),
173
- colleagueGuideFile: normalizeSlash(colleagueDest),
174
- colleagueGuideSynced: !colleagueSameFile,
175
- colleagueGuideNote: colleagueSameFile
176
- ? "colleague-guide-zh.md already at package path (toolchain dev tree); no copy."
177
- : "colleague-guide-zh.md refreshed under FIGMA_CACHE_DIR/docs (default figma-cache/docs/).",
178
- agentPromptNote:
179
- "AGENT-SETUP-PROMPT.md is refreshed every run. Next: @ it in Cursor; after Agent finishes, run npm run figma:cache:init (or npx figma-cache init if scripts are missing).",
180
- npmPackageName: npmPkg,
181
- },
182
- null,
183
- 2
184
- )
185
- );
186
- console.log(
187
- "\n" +
188
- "================================================================\n" +
189
- "下一步(请按顺序):\n" +
190
- "1) 在 Cursor 对话中输入 @AGENT-SETUP-PROMPT.md,并说明「按该文档执行」\n" +
191
- " (每次 cursor init 都会刷新该文件;无需再整篇粘贴。)\n" +
192
- "2) 待 Agent 完成后,在项目根初始化本地缓存索引:\n" +
193
- " npm run figma:cache:init\n" +
194
- " 若尚未补全 npm scripts,请改用:npx figma-cache init\n" +
195
- "================================================================\n"
196
- );
197
- }
198
-
199
- module.exports = {
200
- copyCursorBootstrap,
201
- };
202
-
1
+ /* eslint-disable no-console */
2
+
3
+ function readUtf8IfExists(fs, absPath) {
4
+ if (!fs.existsSync(absPath)) {
5
+ return "";
6
+ }
7
+ return fs.readFileSync(absPath, "utf8");
8
+ }
9
+
10
+ function loadManagedManifest({ fs, path, CURSOR_BOOTSTRAP_DIR, normalizeSlash }) {
11
+ const manifestPath = path.join(CURSOR_BOOTSTRAP_DIR, "managed-files.json");
12
+ if (!fs.existsSync(manifestPath)) {
13
+ console.error(`[figma-cache] missing managed files manifest: ${normalizeSlash(manifestPath)}`);
14
+ process.exit(1);
15
+ }
16
+
17
+ let parsed;
18
+ try {
19
+ parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
20
+ } catch (error) {
21
+ console.error(`[figma-cache] invalid managed files manifest: ${error.message}`);
22
+ process.exit(1);
23
+ }
24
+
25
+ const { managedFiles, retiredFiles } = parsed || {};
26
+ if (!Array.isArray(managedFiles) || managedFiles.length === 0) {
27
+ console.error("[figma-cache] managed-files.json must contain non-empty managedFiles");
28
+ process.exit(1);
29
+ }
30
+
31
+ const pairs = managedFiles.map((item, index) => {
32
+ if (!item || typeof item.from !== "string" || typeof item.to !== "string") {
33
+ console.error(`[figma-cache] invalid managedFiles[${index}] item`);
34
+ process.exit(1);
35
+ }
36
+ return {
37
+ from: item.from,
38
+ to: item.to,
39
+ };
40
+ });
41
+
42
+ const retired = Array.isArray(retiredFiles)
43
+ ? retiredFiles.filter((item) => typeof item === "string" && item.trim())
44
+ : [];
45
+
46
+ return { pairs, retired };
47
+ }
48
+
49
+ function copyCursorBootstrap(options, deps) {
50
+ const {
51
+ fs,
52
+ path,
53
+ ROOT,
54
+ CACHE_DIR,
55
+ CURSOR_BOOTSTRAP_DIR,
56
+ normalizeSlash,
57
+ readSelfNpmPackageName,
58
+ packageDir,
59
+ } = deps;
60
+ const {
61
+ overwrite = false,
62
+ legacyForce = false,
63
+ } = options || {};
64
+
65
+ const { pairs, retired } = loadManagedManifest({ fs, path, CURSOR_BOOTSTRAP_DIR, normalizeSlash });
66
+
67
+ if (!fs.existsSync(CURSOR_BOOTSTRAP_DIR)) {
68
+ console.error(
69
+ `[figma-cache] cursor-bootstrap not found at ${normalizeSlash(CURSOR_BOOTSTRAP_DIR)} (broken package install?)`
70
+ );
71
+ process.exit(1);
72
+ }
73
+
74
+ let copied = 0;
75
+ let skipped = 0;
76
+ pairs.forEach(({ from: relFrom, to: relTo }) => {
77
+ const absFrom = path.join(CURSOR_BOOTSTRAP_DIR, relFrom);
78
+ const absTo = path.join(ROOT, relTo);
79
+ if (!fs.existsSync(absFrom)) {
80
+ console.error(`[figma-cache] missing template file: ${normalizeSlash(absFrom)}`);
81
+ process.exit(1);
82
+ }
83
+ fs.mkdirSync(path.dirname(absTo), { recursive: true });
84
+ if (fs.existsSync(absTo) && !overwrite) {
85
+ skipped += 1;
86
+ return;
87
+ }
88
+ fs.copyFileSync(absFrom, absTo);
89
+ copied += 1;
90
+ });
91
+
92
+ const requiredExampleTemplates = [
93
+ "examples/ui-adapter.contract.template.json",
94
+ "examples/ui-1to1-preflight.template.md",
95
+ "examples/ui-override.template.json",
96
+ "examples/ui-execution-template.fast.md",
97
+ "examples/ui-execution-template.strict.md",
98
+ ];
99
+ requiredExampleTemplates.forEach((relPath) => {
100
+ const absFrom = path.join(CURSOR_BOOTSTRAP_DIR, relPath);
101
+ const absTo = path.join(ROOT, "cursor-bootstrap", relPath);
102
+ if (!fs.existsSync(absFrom)) {
103
+ console.error(`[figma-cache] missing template file: ${normalizeSlash(absFrom)}`);
104
+ process.exit(1);
105
+ }
106
+ fs.mkdirSync(path.dirname(absTo), { recursive: true });
107
+ if (fs.existsSync(absTo) && !overwrite) {
108
+ skipped += 1;
109
+ return;
110
+ }
111
+ fs.copyFileSync(absFrom, absTo);
112
+ copied += 1;
113
+ });
114
+ const retiredDeleted = retired
115
+ .map((relPath) => {
116
+ const abs = path.join(ROOT, relPath);
117
+ if (!fs.existsSync(abs)) {
118
+ return null;
119
+ }
120
+ fs.unlinkSync(abs);
121
+ return normalizeSlash(relPath);
122
+ })
123
+ .filter(Boolean);
124
+
125
+ const configTemplatePath = path.join(CURSOR_BOOTSTRAP_DIR, "figma-cache.config.example.js");
126
+ const projectConfigPath = path.join(ROOT, "figma-cache.config.js");
127
+ const legacyExamplePath = path.join(ROOT, "figma-cache.config.example.js");
128
+
129
+ if (!fs.existsSync(configTemplatePath)) {
130
+ console.error(`[figma-cache] missing template file: ${normalizeSlash(configTemplatePath)}`);
131
+ process.exit(1);
132
+ }
133
+
134
+ const hadProjectConfig = fs.existsSync(projectConfigPath);
135
+ const hadLegacyExample = fs.existsSync(legacyExamplePath);
136
+ const configTemplateBody = fs.readFileSync(configTemplatePath, "utf8");
137
+
138
+ let configAction = "skipped";
139
+ let configSource = "existing";
140
+ if (hadProjectConfig && !overwrite) {
141
+ configAction = "skipped";
142
+ configSource = "existing";
143
+ } else if (!hadProjectConfig && hadLegacyExample && !overwrite) {
144
+ fs.copyFileSync(legacyExamplePath, projectConfigPath);
145
+ configAction = "created";
146
+ configSource = "legacy-example";
147
+ } else {
148
+ fs.writeFileSync(projectConfigPath, configTemplateBody, "utf8");
149
+ configAction = hadProjectConfig ? "overwritten" : "created";
150
+ configSource = "template";
151
+ }
152
+
153
+ let legacyExampleStatus = "not-found";
154
+ if (fs.existsSync(legacyExamplePath)) {
155
+ const legacyBody = readUtf8IfExists(fs, legacyExamplePath);
156
+ const projectBody = readUtf8IfExists(fs, projectConfigPath);
157
+ const sameAsTemplate = legacyBody === configTemplateBody;
158
+ const sameAsProject = projectBody && legacyBody === projectBody;
159
+ if (sameAsTemplate || sameAsProject) {
160
+ fs.unlinkSync(legacyExamplePath);
161
+ legacyExampleStatus = "deleted";
162
+ } else {
163
+ legacyExampleStatus = "kept-customized";
164
+ }
165
+ }
166
+
167
+ const agentSrc = path.join(CURSOR_BOOTSTRAP_DIR, "AGENT-SETUP-PROMPT.md");
168
+ const agentDest = path.join(ROOT, "AGENT-SETUP-PROMPT.md");
169
+ if (!fs.existsSync(agentSrc)) {
170
+ console.error(`[figma-cache] missing ${normalizeSlash(agentSrc)}`);
171
+ process.exit(1);
172
+ }
173
+
174
+ let agentBody = fs.readFileSync(agentSrc, "utf8");
175
+ const npmPkg = readSelfNpmPackageName();
176
+ agentBody = agentBody.replace(/\{\{NPM_PACKAGE_NAME\}\}/g, npmPkg);
177
+ fs.writeFileSync(agentDest, agentBody, "utf8");
178
+
179
+ const colleagueSrc = path.join(packageDir, "docs", "colleague-guide-zh.md");
180
+ const colleagueDest = path.join(CACHE_DIR, "docs", "colleague-guide-zh.md");
181
+ if (!fs.existsSync(colleagueSrc)) {
182
+ console.error(`[figma-cache] missing ${normalizeSlash(colleagueSrc)} (broken package install?)`);
183
+ process.exit(1);
184
+ }
185
+ const colleagueSameFile = path.resolve(colleagueSrc) === path.resolve(colleagueDest);
186
+ if (!colleagueSameFile) {
187
+ fs.mkdirSync(path.dirname(colleagueDest), { recursive: true });
188
+ fs.copyFileSync(colleagueSrc, colleagueDest);
189
+ }
190
+
191
+ console.log(
192
+ JSON.stringify(
193
+ {
194
+ ok: true,
195
+ root: normalizeSlash(ROOT),
196
+ copied,
197
+ skipped,
198
+ overwrite,
199
+ legacyForce,
200
+ retiredDeleted,
201
+ hint: skipped
202
+ ? "Some template files were skipped (default safe mode keeps existing files)."
203
+ : overwrite
204
+ ? "Done. Existing .cursor templates were overwritten by latest bootstrap."
205
+ : "Done.",
206
+ configFile: normalizeSlash(projectConfigPath),
207
+ configAction,
208
+ configSource,
209
+ legacyExampleFile: normalizeSlash(legacyExamplePath),
210
+ legacyExampleStatus,
211
+ agentPromptFile: normalizeSlash(agentDest),
212
+ colleagueGuideFile: normalizeSlash(colleagueDest),
213
+ colleagueGuideSynced: !colleagueSameFile,
214
+ colleagueGuideNote: colleagueSameFile
215
+ ? "colleague-guide-zh.md already at package path (toolchain dev tree); no copy."
216
+ : "colleague-guide-zh.md refreshed under FIGMA_CACHE_DIR/docs (default figma-cache/docs/).",
217
+ agentPromptNote:
218
+ "AGENT-SETUP-PROMPT.md is refreshed every run. Next: @ it in Cursor; after Agent finishes, run npm run figma:cache:init (or npx figma-cache init if scripts are missing).",
219
+ npmPackageName: npmPkg,
220
+ },
221
+ null,
222
+ 2
223
+ )
224
+ );
225
+ console.log(
226
+ "\n" +
227
+ "================================================================\n" +
228
+ "下一步(请按顺序):\n" +
229
+ "1) 在 Cursor 对话中输入 @AGENT-SETUP-PROMPT.md,并说明「按该文档执行」\n" +
230
+ " (每次 cursor init 都会刷新该文件;无需再整篇粘贴。)\n" +
231
+ "2) 待 Agent 完成后,在项目根初始化本地缓存索引:\n" +
232
+ " npm run figma:cache:init\n" +
233
+ " 若尚未补全 npm scripts,请改用:npx figma-cache init\n" +
234
+ "================================================================\n"
235
+ );
236
+ }
237
+
238
+ module.exports = {
239
+ copyCursorBootstrap,
240
+ };