@xulthekl/team-flow 0.53.0 → 0.55.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/.claude/always/phase-guard.md +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/marketplace.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/.github/plugin/marketplace.json +2 -2
- package/CHANGELOG.md +133 -0
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +1 -1
- package/agents/prototype-builder.md +6 -5
- package/agents/prototype-env-scout.md +7 -7
- package/agents/release-archivist.md +1 -0
- package/dist/parsing/requirement-blocks.d.ts +26 -0
- package/dist/parsing/requirement-blocks.js +33 -5
- package/dist/validation/validator.js +8 -1
- package/docs/README_en.md +1 -1
- package/gemini-extension.json +1 -1
- package/hooks/session-start +19 -2
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/plugin.json +2 -2
- package/scripts/check-project-config.mjs +84 -0
- package/scripts/design-system-clone.mjs +150 -0
- package/scripts/design-system-import.mjs +326 -0
- package/scripts/gen-primer.mjs +195 -0
- package/scripts/guard/checks/tasks-complete.mjs +9 -4
- package/scripts/guard/design-token-guard.mjs +317 -77
- package/scripts/infer-workflow.mjs +10 -1
- package/scripts/lib/arch-merge.mjs +20 -6
- package/scripts/lib/arch-parse.mjs +5 -11
- package/scripts/lib/ds-inputs.mjs +125 -0
- package/scripts/lib/ds-parse.mjs +236 -0
- package/scripts/lib/execution-recommendation.mjs +10 -1
- package/scripts/lib/glaf4-delegation.mjs +14 -3
- package/scripts/lib/hash.mjs +18 -2
- package/scripts/lib/md-normalize.mjs +108 -0
- package/scripts/lib/prototype-sync.mjs +19 -1
- package/scripts/lib/sdd-overlay.mjs +15 -3
- package/scripts/lib/solutions-promote.mjs +11 -4
- package/scripts/lib/spec-merge.mjs +46 -11
- package/scripts/lib/state-loader.mjs +4 -1
- package/scripts/token-extract.mjs +349 -0
- package/skills/design-system/SKILL.md +78 -9
- package/skills/design-system/references/agents/design-system-architect.md +67 -17
- package/skills/design-system/references/creation-flow.md +56 -5
- package/skills/design-system/references/creation-modes.md +171 -0
- package/skills/design-system/references/showcase-board-b-end.md +92 -0
- package/skills/design-system/references/showcase-board-c-end.md +115 -0
- package/skills/design-system/references/token-derivation.md +34 -9
- package/skills/design-system/references/variant-schema.md +35 -4
- package/skills/prototype/SKILL.md +18 -8
- package/skills/prototype/references/builder-methodology.md +72 -8
- package/skills/prototype/references/craft/anti-ai-slop.md +1 -1
- package/skills/prototype/references/craft/state-coverage.md +8 -2
- package/skills/prototype/references/layouts.md +10 -0
- package/skills/prototype/references/orchestration-flow.md +20 -3
- package/skills/prototype/references/prototype-scaffold/assets/design-tokens.css +2 -2
- package/skills/prototype/references/template.html +10 -10
- package/skills/release-archivist/SKILL.md +10 -3
- package/skills/release-archivist/references/closing-procedures.md +10 -0
- package/skills/workflow-bootstrap/SKILL.md +24 -2
- package/src/parsing/requirement-blocks.ts +34 -5
- package/src/validation/validator.ts +8 -1
- package/templates/design-systems/references/claude.md +315 -0
- package/templates/design-systems/references/linear-app.md +370 -0
- package/templates/design-systems/references/notion.md +312 -0
- package/templates/design-systems/references/posthog.md +259 -0
- package/templates/design-systems/references/sentry.md +265 -0
- package/templates/design-systems/references/stripe.md +325 -0
- package/templates/design-systems/references/supabase.md +258 -0
- package/templates/design-systems/references/vercel.md +313 -0
- package/templates/design-systems/registry.json +75 -0
- package/templates/design-systems/styles.json +576 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// check-project-config.mjs — 项目配置漂移检查(v0.55.0,设计 §8.5 N2)
|
|
3
|
+
//
|
|
4
|
+
// ── 动机 ─────────────────────────────────────────────────────────────────────
|
|
5
|
+
// 现场实测(workflow-feedback 2026-09-11)的三类漂移:
|
|
6
|
+
// ① `version` 落后于插件版本(0.48.0 vs 0.54.0)——用户以为在用新版
|
|
7
|
+
// ② `designSystemC` 指向**不存在**的 c-end.md
|
|
8
|
+
// ③ `versionWorktree` 指向**不存在**的 .worktrees/ 目录
|
|
9
|
+
// 三者都不会报错,只会在用到时静默失效。
|
|
10
|
+
//
|
|
11
|
+
// ── 检查面(P1.5 三轮校正:按"实际被消费"分级)──────────────────────────────
|
|
12
|
+
// 真调用点:`prototype.designSystem`(ce-brainstorm/references/prototype-loop.md)
|
|
13
|
+
// 声明级: `prototype.designSystemBase` / `prototype.entry`(多处文档声明、无 --get 调用点)
|
|
14
|
+
// 零消费者:`prototype.designSystemC` / `prototype.versionWorktree`(全仓无引用)
|
|
15
|
+
// → 前两类做**存在性检查**;后两类只报"未被消费"(不是错误,是配置残留)。
|
|
16
|
+
//
|
|
17
|
+
// Usage: node scripts/check-project-config.mjs [项目根,默认 cwd]
|
|
18
|
+
// 退出码:0 = 正常(含仅警告);1 = 配置存在但不可解析
|
|
19
|
+
|
|
20
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
21
|
+
import { resolve, join, dirname } from 'node:path';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
|
|
24
|
+
const PLUGIN_DIR = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
25
|
+
const root = resolve(process.argv[2] || process.cwd());
|
|
26
|
+
const cfgPath = join(root, '.team-flow', 'team-flow.config.json');
|
|
27
|
+
|
|
28
|
+
if (!existsSync(cfgPath)) {
|
|
29
|
+
console.log(`ℹ️ 未找到 ${cfgPath}(跳过配置漂移检查)`);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let cfg;
|
|
34
|
+
try {
|
|
35
|
+
cfg = JSON.parse(readFileSync(cfgPath, 'utf-8'));
|
|
36
|
+
} catch (e) {
|
|
37
|
+
console.error(`❌ 配置无法解析:${cfgPath}`);
|
|
38
|
+
console.error(` ${e.message}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const proto = cfg.prototype || {};
|
|
43
|
+
const warnings = [];
|
|
44
|
+
|
|
45
|
+
// ①–② 存在性检查(真调用点 + 声明级)
|
|
46
|
+
const CONSUMED = [
|
|
47
|
+
{ key: 'designSystem', level: '真调用点' },
|
|
48
|
+
{ key: 'designSystemBase', level: '声明级' },
|
|
49
|
+
{ key: 'entry', level: '声明级' },
|
|
50
|
+
];
|
|
51
|
+
for (const { key, level } of CONSUMED) {
|
|
52
|
+
const v = proto[key];
|
|
53
|
+
if (!v || typeof v !== 'string') continue;
|
|
54
|
+
if (!existsSync(resolve(root, v))) {
|
|
55
|
+
warnings.push(`prototype.${key}(${level})指向的路径不存在:${v}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ③ 零消费者键(配置残留,不是错误)
|
|
60
|
+
for (const key of ['designSystemC', 'versionWorktree']) {
|
|
61
|
+
if (proto[key]) {
|
|
62
|
+
warnings.push(`prototype.${key} 在插件内**零消费者**(全仓无引用)——该键不会被读取,可删除或忽略`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ④ 版本漂移
|
|
67
|
+
if (cfg.version) {
|
|
68
|
+
const pkgPath = join(PLUGIN_DIR, 'package.json');
|
|
69
|
+
const pluginVersion = existsSync(pkgPath)
|
|
70
|
+
? JSON.parse(readFileSync(pkgPath, 'utf-8')).version
|
|
71
|
+
: null;
|
|
72
|
+
if (pluginVersion && cfg.version !== pluginVersion) {
|
|
73
|
+
warnings.push(`配置 version=${cfg.version} 与插件版本 ${pluginVersion} 不一致(配置由项目侧维护,请确认是否需更新)`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (warnings.length === 0) {
|
|
78
|
+
console.log(`✅ 配置无漂移(${cfgPath})`);
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(`⚠️ 配置漂移 ${warnings.length} 项(${cfgPath})——**均为提示,不阻断**:`);
|
|
83
|
+
for (const w of warnings) console.log(` - ${w}`);
|
|
84
|
+
process.exit(0);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// design-system-clone.mjs — 设计系统移植(v0.55.0,设计 §8.2.4 形态 B / D-22)
|
|
3
|
+
//
|
|
4
|
+
// ── 为什么需要 ───────────────────────────────────────────────────────────────
|
|
5
|
+
// 设计系统就是 `.team-flow/design-system/` 下的 markdown,技术上 `cp -r` 即可。
|
|
6
|
+
// 但直接复制会留 4 个坑,本脚本把它们自动化:
|
|
7
|
+
// ① **来源与裁决记录失真**——复制后仍写着原项目的导入记录(如"从 page-gen 导入于 X"),
|
|
8
|
+
// 而目标项目并未导入过 → **追加**移植条目(**保留**原裁决历史:其裁决依据仍有参考价值)
|
|
9
|
+
// ② **primer.md 的 digest 失效**——它锚定源 base.md 内容;改一个字即 STALE,
|
|
10
|
+
// 而 prototype Step 0 对 `contract: v1` 系统会 **blocked** → **强制重生成 + 校验**
|
|
11
|
+
// ③ **业务专属组件残留**(如"协议富文本编辑器/内容预览画布")→ 输出契约表清单供调用方
|
|
12
|
+
// (design-system skill)**逐条问询**
|
|
13
|
+
// ④ **授权边界**——源系统 `references/` 下可能是企业内部资产 → 输出提示(不自动判定)
|
|
14
|
+
//
|
|
15
|
+
// 交互部分(业务组件"要不要保留")**不在本脚本**:脚本只做确定性动作 + 输出问询素材。
|
|
16
|
+
//
|
|
17
|
+
// Usage: node scripts/design-system-clone.mjs --from <源项目路径 | 源设计系统目录> --to <目标设计系统目录>
|
|
18
|
+
|
|
19
|
+
import { readFileSync, writeFileSync, existsSync, statSync, cpSync, mkdirSync } from 'node:fs';
|
|
20
|
+
import { resolve, dirname, join, basename } from 'node:path';
|
|
21
|
+
import { spawnSync } from 'node:child_process';
|
|
22
|
+
import { fileURLToPath } from 'node:url';
|
|
23
|
+
import { parseComponentsTable } from './lib/ds-parse.mjs';
|
|
24
|
+
|
|
25
|
+
const argv = process.argv.slice(2);
|
|
26
|
+
const fromIdx = argv.indexOf('--from');
|
|
27
|
+
const toIdx = argv.indexOf('--to');
|
|
28
|
+
const fromArg = fromIdx !== -1 ? argv[fromIdx + 1] : null;
|
|
29
|
+
const toArg = toIdx !== -1 ? argv[toIdx + 1] : null;
|
|
30
|
+
|
|
31
|
+
if (!fromArg || !toArg) {
|
|
32
|
+
console.error('Usage: node scripts/design-system-clone.mjs --from <源项目路径 | 源设计系统目录> --to <目标设计系统目录>');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const PLUGIN_DIR = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
37
|
+
const GUARD = join(PLUGIN_DIR, 'scripts', 'guard', 'design-token-guard.mjs');
|
|
38
|
+
const GEN_PRIMER = join(PLUGIN_DIR, 'scripts', 'gen-primer.mjs');
|
|
39
|
+
|
|
40
|
+
// ── 源定位:既接受项目根,也接受设计系统目录本身 ──
|
|
41
|
+
|
|
42
|
+
const fromAbs = resolve(fromArg);
|
|
43
|
+
if (!existsSync(fromAbs) || !statSync(fromAbs).isDirectory()) {
|
|
44
|
+
console.error(`源路径不存在或不是目录:${fromAbs}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
const nested = join(fromAbs, '.team-flow', 'design-system');
|
|
48
|
+
const srcDir = existsSync(nested) ? nested : fromAbs;
|
|
49
|
+
if (!existsSync(join(srcDir, 'base.md'))) {
|
|
50
|
+
console.error(`源目录内未找到 base.md:${srcDir}`);
|
|
51
|
+
console.error('(--from 可指向项目根,或直接指向 .team-flow/design-system)');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ── 目标:必须不存在或为空(移植是**新建**;已有设计系统请走 iterate 的 MERGE 语义)──
|
|
56
|
+
|
|
57
|
+
const dstDir = resolve(toArg);
|
|
58
|
+
if (existsSync(dstDir) && existsSync(join(dstDir, 'base.md'))) {
|
|
59
|
+
console.error(`目标目录已存在 base.md:${dstDir}`);
|
|
60
|
+
console.error('目标已有设计系统 → 请走 design-system 的 **iterate** 模式(MERGE 不 OVERWRITE),不要 clone。');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── ① 校验源系统(警告不阻断:源可能是 legacy 系统)──
|
|
65
|
+
|
|
66
|
+
console.log('① 校验源系统…');
|
|
67
|
+
const srcGuard = spawnSync('node', [GUARD, srcDir], { encoding: 'utf-8' });
|
|
68
|
+
const srcOk = srcGuard.status === 0;
|
|
69
|
+
console.log(srcOk
|
|
70
|
+
? ' 源系统硬校验通过'
|
|
71
|
+
: ` ⚠️ 源系统硬校验未通过(exit ${srcGuard.status})——移植后需一并修复`);
|
|
72
|
+
if (!srcOk && srcGuard.stdout) {
|
|
73
|
+
const failed = srcGuard.stdout.split('\n').filter(l => l.trim().startsWith('❌'));
|
|
74
|
+
failed.slice(0, 5).forEach(l => console.log(` ${l.trim()}`));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── ② 复制(排除 primer.md / pending.md / showcase/)──
|
|
78
|
+
|
|
79
|
+
const EXCLUDE = new Set(['primer.md', 'pending.md', 'showcase']);
|
|
80
|
+
mkdirSync(dstDir, { recursive: true });
|
|
81
|
+
cpSync(srcDir, dstDir, {
|
|
82
|
+
recursive: true,
|
|
83
|
+
filter: (src) => !EXCLUDE.has(basename(src)),
|
|
84
|
+
});
|
|
85
|
+
console.log(`② 已复制到 ${dstDir}(排除:primer.md / pending.md / showcase/)`);
|
|
86
|
+
|
|
87
|
+
// ── ③ 追加移植记录(保留原裁决历史)──
|
|
88
|
+
|
|
89
|
+
const basePath = join(dstDir, 'base.md');
|
|
90
|
+
let base = readFileSync(basePath, 'utf-8');
|
|
91
|
+
const stamp = new Date().toISOString().slice(0, 10);
|
|
92
|
+
const entry = `> **移植记录**:${stamp} 由 \`design-system-clone\` 从 \`${srcDir}\` 移植。`
|
|
93
|
+
+ '原「来源与裁决记录」**保留**——其裁决依据对理解本系统的取值仍有参考价值。';
|
|
94
|
+
|
|
95
|
+
if (/^##\s+来源与裁决记录\s*$/m.test(base)) {
|
|
96
|
+
base = base.replace(/^(##\s+来源与裁决记录\s*)$/m, `$1\n\n${entry}`);
|
|
97
|
+
} else {
|
|
98
|
+
// 无该段 → 在文档头部(首个 ## 段之前)创建
|
|
99
|
+
const firstSection = base.search(/^##\s+/m);
|
|
100
|
+
const insertAt = firstSection === -1 ? base.length : firstSection;
|
|
101
|
+
base = `${base.slice(0, insertAt)}## 来源与裁决记录\n\n${entry}\n\n${base.slice(insertAt)}`;
|
|
102
|
+
}
|
|
103
|
+
writeFileSync(basePath, base, 'utf-8');
|
|
104
|
+
console.log('③ 已追加移植记录(原来源与裁决记录保留)');
|
|
105
|
+
|
|
106
|
+
// ── ④ 输出业务组件清单(供调用方逐条问询)──
|
|
107
|
+
|
|
108
|
+
const table = parseComponentsTable(base);
|
|
109
|
+
const rows = table ? table.rows : [];
|
|
110
|
+
console.log(`④ 契约表共 ${rows.length} 类组件 —— **请逐条确认目标项目是否需要**(不需要的删除后重跑 guard;`);
|
|
111
|
+
console.log(' 注意组件数跌破档位会改变 L2 标签:<10 FAIL / 10-14 WARN / ≥15 PASS):');
|
|
112
|
+
for (const r of rows) console.log(` - ${r.name}(${r.type})`);
|
|
113
|
+
|
|
114
|
+
// ── ⑤ 重生成 primer + 校验(digest 锚定的是新路径下的 base 内容)──
|
|
115
|
+
|
|
116
|
+
console.log('⑤ 重生成 primer…');
|
|
117
|
+
const gen = spawnSync('node', [GEN_PRIMER, basePath], { encoding: 'utf-8' });
|
|
118
|
+
if (gen.status !== 0) {
|
|
119
|
+
console.error(` ❌ primer 生成失败:${(gen.stderr || gen.stdout || '').trim()}`);
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
const check = spawnSync('node', [GEN_PRIMER, basePath, '--check'], { encoding: 'utf-8' });
|
|
123
|
+
// v0.55.0 §8.2.4 修订:`--check` 紧跟在"刚重生成"之后运行,两者不一致 = 生成器自相矛盾
|
|
124
|
+
// (而非用户输入问题)——属于内部一致性错误,**必须参与退出码**,否则脚本报成功而产物对
|
|
125
|
+
// `contract: v1` 系统实为 blocked(prototype Step 0 会拒绝),问题被推迟到使用现场才暴露。
|
|
126
|
+
if (check.status !== 0) {
|
|
127
|
+
console.error(` ❌ primer 自检失败(exit ${check.status}):刚生成的 primer 与其输入不一致。`);
|
|
128
|
+
console.error((check.stderr || check.stdout || '').trim());
|
|
129
|
+
console.error(' 这是生成器缺陷(非用户输入问题),请上报;移植产物暂不可用。');
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
console.log(' primer --check:OK(digest 一致)');
|
|
133
|
+
|
|
134
|
+
// ── ⑥ 跑 guard 并输出结论 ──
|
|
135
|
+
|
|
136
|
+
console.log('⑥ 对移植结果跑 guard…');
|
|
137
|
+
const dstGuard = spawnSync('node', [GUARD, dstDir], { encoding: 'utf-8' });
|
|
138
|
+
if (dstGuard.stdout) {
|
|
139
|
+
const lines = dstGuard.stdout.split('\n');
|
|
140
|
+
const auditStart = lines.findIndex(l => l.startsWith('## 六层审计报告'));
|
|
141
|
+
if (auditStart !== -1) lines.slice(auditStart).forEach(l => console.log(` ${l}`));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log('');
|
|
145
|
+
console.log('移植完成。**仍需人工处理的事项**:');
|
|
146
|
+
console.log(' a) 业务组件裁剪(见 ④ 的清单)—— 删除后重跑 guard');
|
|
147
|
+
console.log(' b) 品牌适配:若目标项目主色/字体不同 → 改 base.md 的 color 段 → **重跑 gen-primer**(digest 会变)');
|
|
148
|
+
console.log(' c) 授权:源系统 references/ 下若含企业内部资产,跨企业边界复用时须自行确认授权');
|
|
149
|
+
console.log(' d) `contract`:移植产物继承源系统的取值;若源为 v1 而目标尚未达标,可考虑降为 legacy');
|
|
150
|
+
process.exit(dstGuard.status === 0 ? 0 : 1);
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// design-system-import.mjs — open-design DESIGN.md / 内置 profile → team-flow base.md 转换器(v0.55.0,设计 §4.5.2 / §8.2.4)
|
|
3
|
+
//
|
|
4
|
+
// 解析 open-design 九节 DESIGN.md,映射到 team-flow 的 9 段 schema(A1/A2/B-slot + 组件契约表 + principles/governance)。
|
|
5
|
+
// 关键约束:**转换后必须补齐完整度**(palette 阶梯 / A2 派生公式 / B-slot 别名 / 组件契约表基线)——
|
|
6
|
+
// 来源原文没有这些,转换器按 team-flow 规则生成。
|
|
7
|
+
//
|
|
8
|
+
// v0.55.0 变更(设计 §8.2.4 / D-18):
|
|
9
|
+
// - 新增 `--profile antd`:无外部源的**确定性 B 端起点**(Ant Design v5 seed token,MIT)。
|
|
10
|
+
// 与 reference 导入走同一条生成路径(合成源文本 → 复用解析)。**零 LLM、零外部资产**。
|
|
11
|
+
// - 产物 `contract` 由 `v1` 改 **`legacy`**:导入的既有资产天然未经校准,按 v1 最严档判会一落盘
|
|
12
|
+
// 即 blocked(死循环)。用户裁决达标后经 iterate 升 v1。
|
|
13
|
+
// - `layout` 段增「容器骨架」块与「页面范式」声明:内置 `template.html` 的骨架是营销向的
|
|
14
|
+
// `hero/cta/log`,**缺 B 端容器类** → 不给物料则 builder 每页要手写整套骨架 CSS。
|
|
15
|
+
// - 新增 `来源与裁决记录` 段(导入类条件必填,审计层 advisory,不进硬校验)。
|
|
16
|
+
//
|
|
17
|
+
// Usage:
|
|
18
|
+
// node scripts/design-system-import.mjs <DESIGN.md 路径> [--out <base.md 路径>] [--id <标识>]
|
|
19
|
+
// node scripts/design-system-import.mjs --profile antd [--out <base.md 路径>]
|
|
20
|
+
// 输出后建议运行:node scripts/guard/design-token-guard.mjs <base.md>
|
|
21
|
+
|
|
22
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
23
|
+
import { resolve, dirname, basename, extname, join } from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
// ── 内置 profile(v0.55.0,设计 §8.2.4 形态 A)──
|
|
27
|
+
//
|
|
28
|
+
// "全新项目且无任何规范"时的确定性起点。规格全部来自 **Ant Design v5 seed token**(MIT)
|
|
29
|
+
// 或 team-flow 自有规范——**不含任何项目补录值**(原方案的模板内容表多数条目实为某项目的
|
|
30
|
+
// 补录而非通用惯例,P1.5 反方逐条实查 page-gen 全树后推翻,见设计 §8.2.4"为何取消")。
|
|
31
|
+
//
|
|
32
|
+
// 实现方式:合成一份 open-design 形态的源文本,复用下方的既有解析路径(零新增解析逻辑)。
|
|
33
|
+
const SYNTHETIC_SOURCES = {
|
|
34
|
+
antd: `# B-end Admin (Ant Design v5 basis)
|
|
35
|
+
|
|
36
|
+
> Category: Enterprise Admin
|
|
37
|
+
> 通用 B 端后台基线——规格取自 Ant Design v5 seed token(MIT)与 team-flow 自有规范,不含项目专属值。
|
|
38
|
+
|
|
39
|
+
**Primary:** \`#1677FF\`
|
|
40
|
+
**Surface:** \`#FFFFFF\`
|
|
41
|
+
**Text:** \`#000000E0\`
|
|
42
|
+
**Secondary:** \`#8C8C8C\`
|
|
43
|
+
**Families:** primary=-apple-system, Segoe UI, system-ui; display=-apple-system, Segoe UI, system-ui; mono=ui-monospace
|
|
44
|
+
**Scale:** 12/14/16/20/24
|
|
45
|
+
**Spacing scale:** 4/8/12/16/24/32/40/48
|
|
46
|
+
`,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const argv = process.argv.slice(2);
|
|
50
|
+
const outIdx = argv.indexOf('--out');
|
|
51
|
+
const outArg = outIdx !== -1 ? argv[outIdx + 1] : null;
|
|
52
|
+
const idIdx = argv.indexOf('--id');
|
|
53
|
+
const idArg = idIdx !== -1 ? argv[idIdx + 1] : null;
|
|
54
|
+
const profileIdx = argv.indexOf('--profile');
|
|
55
|
+
const profileArg = profileIdx !== -1 ? argv[profileIdx + 1] : null;
|
|
56
|
+
const positional = argv.filter((a, i) =>
|
|
57
|
+
!a.startsWith('--') && !(outIdx !== -1 && i === outIdx + 1)
|
|
58
|
+
&& !(idIdx !== -1 && i === idIdx + 1) && !(profileIdx !== -1 && i === profileIdx + 1));
|
|
59
|
+
const srcPath = positional[0];
|
|
60
|
+
|
|
61
|
+
if (profileArg && !SYNTHETIC_SOURCES[profileArg]) {
|
|
62
|
+
console.error(`未知 profile:${profileArg}(可用:${Object.keys(SYNTHETIC_SOURCES).join(', ')})`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
if (!profileArg && !srcPath) {
|
|
66
|
+
console.error('Usage: node scripts/design-system-import.mjs <DESIGN.md 路径> [--out <base.md 路径>] [--id <标识>]');
|
|
67
|
+
console.error(' node scripts/design-system-import.mjs --profile antd [--out <base.md 路径>]');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
const srcAbs = srcPath ? resolve(srcPath) : null;
|
|
71
|
+
if (srcAbs && !existsSync(srcAbs)) {
|
|
72
|
+
console.error(`DESIGN.md not found: ${srcAbs}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const src = profileArg ? SYNTHETIC_SOURCES[profileArg] : readFileSync(srcAbs, 'utf-8');
|
|
77
|
+
// 标识:显式 --id > profile 名 > 文件名(非约定名 `DESIGN.md` 时)> 目录名(约定名的旧行为)。
|
|
78
|
+
// v0.55.0:原实现恒取目录名——内置模板库(`templates/design-systems/references/linear-app.md`)
|
|
79
|
+
// 会得到无意义的 `references`,写进「来源与裁决记录」段后无法辨认来源。
|
|
80
|
+
const id = idArg || (profileArg
|
|
81
|
+
? `profile-${profileArg}`
|
|
82
|
+
: basename(srcAbs).toLowerCase() === 'design.md'
|
|
83
|
+
? basename(dirname(srcAbs))
|
|
84
|
+
: basename(srcAbs, extname(srcAbs)));
|
|
85
|
+
|
|
86
|
+
// ── 解析 open-design 九节 ──
|
|
87
|
+
|
|
88
|
+
function grab(pattern) {
|
|
89
|
+
const m = src.match(pattern);
|
|
90
|
+
return m ? m[1].trim() : null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const title = grab(/^#\s+(.+)$/m) || `Design System Inspired by ${id}`;
|
|
94
|
+
const category = grab(/> Category:\s*(.+)/) || 'Uncategorized';
|
|
95
|
+
const summary = grab(/>\s*(?!Category)(.+\S)/) || '';
|
|
96
|
+
|
|
97
|
+
// A 类模板格式:`**Primary:** \`#hex\``
|
|
98
|
+
let primary = grab(/\*\*Primary:\*\*\s*`(#[0-9a-fA-F]{3,8})`/);
|
|
99
|
+
let surface = grab(/\*\*Surface:\*\*\s*`(#[0-9a-fA-F]{3,8})`/);
|
|
100
|
+
let text = grab(/\*\*Text:\*\*\s*`(#[0-9a-fA-F]{3,8})`/);
|
|
101
|
+
|
|
102
|
+
// B 类精细格式 fallback:找 brand/accent/primary 上下文附近的 hex(自动提取,需人工核对)
|
|
103
|
+
let autoExtracted = false;
|
|
104
|
+
if (!primary) {
|
|
105
|
+
const m = src.match(/(?:brand|accent|primary)[^\n]{0,100}?(#[0-9a-fA-F]{6})/i);
|
|
106
|
+
if (m) { primary = m[1]; autoExtracted = true; }
|
|
107
|
+
}
|
|
108
|
+
if (!surface) {
|
|
109
|
+
const m = src.match(/(?:background|surface|canvas)[^\n]{0,100}?(#[0-9a-fA-F]{6})/i);
|
|
110
|
+
surface = m ? m[1] : '#FFFFFF';
|
|
111
|
+
if (m) autoExtracted = true;
|
|
112
|
+
}
|
|
113
|
+
if (!text) {
|
|
114
|
+
const m = src.match(/(?:text|foreground)[^\n]{0,100}?(#[0-9a-fA-F]{6})/i);
|
|
115
|
+
text = m ? m[1] : '#111827';
|
|
116
|
+
if (m) autoExtracted = true;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const secondary = grab(/\*\*Secondary:\*\*\s*`(#[0-9a-fA-F]{3,8})`/);
|
|
120
|
+
const success = grab(/\*\*Success:\*\*\s*`(#[0-9a-fA-F]{3,8})`/) || '#16A34A';
|
|
121
|
+
const warning = grab(/\*\*Warning:\*\*\s*`(#[0-9a-fA-F]{3,8})`/) || '#D97706';
|
|
122
|
+
const danger = grab(/\*\*Danger:\*\*\s*`(#[0-9a-fA-F]{3,8})`/) || '#DC2626';
|
|
123
|
+
const families = grab(/\*\*Families:\*\*\s*(.+)/) || 'primary=Inter, display=Inter, mono=ui-monospace';
|
|
124
|
+
const scale = grab(/\*\*Scale:\*\*\s*(.+)/) || '12/14/16/20/24/32';
|
|
125
|
+
const spacingScale = grab(/\*\*Spacing scale:\*\*\s*(.+)/) || '4/8/12/16/24/32';
|
|
126
|
+
|
|
127
|
+
if (!primary) {
|
|
128
|
+
console.error(`未能从 ${srcAbs} 解析出 Primary 色。`);
|
|
129
|
+
console.error('A 类格式需 `**Primary:** \\`#hex\\``;B 类格式自动提取失败。');
|
|
130
|
+
console.error('B 类设计系统建议走人工路径:浏览参考文档 → 提取配色 → 用 create-from-code 或手工创建。');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
if (autoExtracted) {
|
|
134
|
+
console.warn('⚠️ B 类格式:配色为自动提取(brand/surface/text 上下文匹配),**请人工核对后再采用**。');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 字体族解析:primary=X, display=Y, mono=Z → 系统栈兜底(零依赖:不引 webfont,仅保留字体名首选 + 系统栈)
|
|
138
|
+
function fontStack(family) {
|
|
139
|
+
const f = (family || 'Inter').trim().replace(/^['"]|['"]$/g, '');
|
|
140
|
+
const isMono = /mono|code|menlo|consolas/i.test(f);
|
|
141
|
+
if (isMono) return `'${f}', ui-monospace, SFMono-Regular, Menlo, monospace`;
|
|
142
|
+
return `'${f}', -apple-system, 'Segoe UI', system-ui, 'PingFang SC', 'Microsoft YaHei', sans-serif`;
|
|
143
|
+
}
|
|
144
|
+
const famMap = Object.fromEntries(families.split(',').map(p => {
|
|
145
|
+
const [k, v] = p.split('=').map(x => (x || '').trim());
|
|
146
|
+
return [k, v];
|
|
147
|
+
}));
|
|
148
|
+
const fontDisplay = fontStack(famMap.display || famMap.primary);
|
|
149
|
+
const fontBody = fontStack(famMap.primary);
|
|
150
|
+
const fontMono = fontStack(famMap.mono || 'ui-monospace');
|
|
151
|
+
|
|
152
|
+
// 字号阶梯:open-design 的 "12/14/16/20/24/32" → type scale 名称映射
|
|
153
|
+
const scaleNums = scale.split('/').map(s => s.trim()).filter(Boolean);
|
|
154
|
+
const typeScale = scaleNums.slice(0, 7).map((v, i) => {
|
|
155
|
+
const names = ['--text-xs', '--text-sm', '--text-base', '--text-lg', '--text-xl', '--text-2xl', '--text-3xl'];
|
|
156
|
+
return `${names[i]}: ${v}px`;
|
|
157
|
+
}).join(';') || '--text-xs: 12px;--text-sm: 14px;--text-base: 16px;--text-lg: 20px;--text-xl: 24px;--text-2xl: 32px';
|
|
158
|
+
|
|
159
|
+
// ── 组件契约表基线(20 类,来自 design-system-architect.md 的完整基线)──
|
|
160
|
+
|
|
161
|
+
const CONTRACT_ROWS = [
|
|
162
|
+
'| Button | 交互 | primary / secondary / ghost / danger | sm / md / lg | default / hover / focus-visible / active / disabled / loading | 触发动作 | 用颜色单独传状态 |',
|
|
163
|
+
'| Input | 交互 | default / error / success | sm / md / lg | default / focus / disabled / readonly / error / filled | 文本录入 | 无 label |',
|
|
164
|
+
'| Select | 交互 | default / multiple / searchable | sm / md / lg | default / focus / disabled / error / open | 选项选择 | 超 10 项不用搜索 |',
|
|
165
|
+
'| Checkbox | 交互 | default / indeterminate | sm / md | default / hover / focus / disabled / checked | 多选 | — |',
|
|
166
|
+
'| Radio | 交互 | default | sm / md | default / focus / disabled / selected | 单选 | 超 7 项改用 Select |',
|
|
167
|
+
'| Switch | 交互 | default | md | default / focus / disabled / on / off | 二元开关 | 高危操作须确认 |',
|
|
168
|
+
'| Tag | 轻量 | default / success / warning / danger / info | sm / md | default / hover / closable¹ | 状态标记 | 用作按钮 |',
|
|
169
|
+
'| Icon | 豁免 | — | sm / md / lg | — | 语义图示 | emoji |',
|
|
170
|
+
'| Avatar | 轻量 | circle / square | sm / md / lg | default / fallback / loading | 用户标识 | — |',
|
|
171
|
+
'| Tooltip | 轻量 | default | — | hidden / visible | 补充说明 | 承载关键操作 |',
|
|
172
|
+
'| Modal | 交互 | default / confirm / destructive | sm / md / lg | open / loading / error / closing | 阻断式交互 | 嵌套 Modal |',
|
|
173
|
+
'| Drawer | 交互 | left / right / bottom | sm / md / lg | open / loading / error | 侧边任务面板 | — |',
|
|
174
|
+
'| Tabs | 交互 | line / pill | — | default / active / disabled / loading | 内容分组 | 超 7 个 tab |',
|
|
175
|
+
'| Table | 交互 | default / compact / striped | — | default / loading / empty / error / selected | 结构化数据 | 无分页超长列表 |',
|
|
176
|
+
'| Form | 交互 | single / two-column | — | untouched / dirty / submitted-pending / error | 字段集合 | 单页超 20 字段 |',
|
|
177
|
+
'| Card | 交互 | default / interactive / stat | — | default / hover / selected / loading | 内容容器 | 嵌套超 2 层 |',
|
|
178
|
+
'| Pagination | 交互 | default / simple | sm / md | default / disabled / loading | 分页导航 | — |',
|
|
179
|
+
'| FilterBar | 交互 | default / collapsible | — | default / expanded / applied | 筛选条件集合 | — |',
|
|
180
|
+
'| EmptyState | 豁免 | default / filtered / error | — | — | 空态承载 | 只有"暂无数据" |',
|
|
181
|
+
'| Toast | 交互 | success / warning / error / info | — | entering / visible / exiting | 轻量反馈 | 承载需用户操作的信息 |',
|
|
182
|
+
];
|
|
183
|
+
|
|
184
|
+
// ── 生成 base.md ──
|
|
185
|
+
|
|
186
|
+
const base = `# ${title}
|
|
187
|
+
|
|
188
|
+
> a11y: WCAG 2.2 AA(对比度 4.5:1 / 大字 3:1 / 焦点可见 / 键盘可达)
|
|
189
|
+
|
|
190
|
+
> 由 open-design 设计系统 \`${id}\`(Category: ${category})转换生成(scripts/design-system-import.mjs v0.54.0)。
|
|
191
|
+
> ${summary}
|
|
192
|
+
> 来源归因:open-design(nexu-io)MIT License,"inspired by" 逆向工程描述,非官方资产。
|
|
193
|
+
|
|
194
|
+
## 来源与裁决记录
|
|
195
|
+
|
|
196
|
+
> **原始来源**:${profileArg ? `内置 profile \`${profileArg}\`(Ant Design v5 seed token,MIT)` : `open-design \`${id}\`(Category: ${category})`}
|
|
197
|
+
> **导入日期**:${new Date().toISOString().slice(0, 10)}
|
|
198
|
+
> **导入方式**:转换器确定性生成(\`scripts/design-system-import.mjs\`)—— **未做语义裁决**
|
|
199
|
+
> **为什么不是直接采纳**:① **结构**——来源是描述性散文,team-flow 需 9 段 schema;② **Token 形式**——需补 A2 派生公式与 B-slot 别名;③ **消费入口**——需生成 primer 供 builder 使用
|
|
200
|
+
> **★ 主色裁决**:无(单一来源,取自 \`accent\`)${autoExtracted ? '\n> **⚠️ B 类自动提取**:原来源未以 A 类格式声明配色,\`accent\` 等值由正则自动提取——**须人工核对**' : ''}
|
|
201
|
+
> **待清理项**:组件契约表为 20 类通用基线(与来源无关),须对照项目实际增删;本段为**审计层 advisory**,不参与 guard 硬校验
|
|
202
|
+
|
|
203
|
+
## color
|
|
204
|
+
|
|
205
|
+
**A1-identity(8 必选)**
|
|
206
|
+
- \`--bg\`: color-mix(in oklab, var(--surface), var(--fg) 3%)
|
|
207
|
+
- \`--surface\`: ${surface}
|
|
208
|
+
- \`--fg\`: ${text}
|
|
209
|
+
- \`--muted\`: color-mix(in oklab, var(--fg), var(--surface) 45%)
|
|
210
|
+
- \`--border\`: color-mix(in oklab, var(--fg), var(--surface) 88%)
|
|
211
|
+
- \`--accent\`: ${primary}
|
|
212
|
+
- \`--font-display\`: ${fontDisplay}
|
|
213
|
+
- \`--font-body\`: ${fontBody}
|
|
214
|
+
|
|
215
|
+
**语义色**:success ${success} / warning ${warning} / danger ${danger}${secondary ? ` / secondary ${secondary}` : ''}
|
|
216
|
+
|
|
217
|
+
**A2-derived(color-mix 公式)**:--accent-hover / --accent-active / --focus-ring / --elev-raised(公式见 token-derivation.md)
|
|
218
|
+
|
|
219
|
+
**B-slot 别名**:--fg-2 → var(--fg) / --meta → var(--muted) / --border-soft → var(--border) / --surface-warm → var(--surface)
|
|
220
|
+
|
|
221
|
+
## typography
|
|
222
|
+
|
|
223
|
+
- 字阶:${typeScale}
|
|
224
|
+
- 行高:正文 1.5 / 标题 1.2
|
|
225
|
+
- 字重:400(正文)/ 500-600(标题)
|
|
226
|
+
|
|
227
|
+
## spacing
|
|
228
|
+
|
|
229
|
+
- 阶梯(4 基数):${spacingScale.split('/').map((v, i) => `${v.trim()}px`).join(' / ')}
|
|
230
|
+
|
|
231
|
+
## layout
|
|
232
|
+
|
|
233
|
+
- 栅格 12 列 / 断点:sm 640 / md 1024 / lg 1440 / 容器 max-width 1200px
|
|
234
|
+
|
|
235
|
+
### 容器骨架
|
|
236
|
+
|
|
237
|
+
| 容器 | 类名 | 关键 CSS 声明 |
|
|
238
|
+
|------|------|--------------|
|
|
239
|
+
| 顶栏 | \`app-header\` | \`position: sticky; top: 0; height: 64px; z-index: 100\` |
|
|
240
|
+
| 侧栏 | \`app-sider\` | \`width: 200px; flex: 0 0 200px; overflow-y: auto\` |
|
|
241
|
+
| 主区 | \`app-main\` | \`flex: 1; min-width: 0; padding: 24px\` |
|
|
242
|
+
|
|
243
|
+
> 上述类**不在**内置 \`template.html\` 中——按类清单契约在页面 \`<style>\` 内定义。
|
|
244
|
+
> (v0.55.0 说明:内置骨架是营销向的 \`hero/cta/log\`,**缺 B 端容器类**,故这里必须给出物料。)
|
|
245
|
+
|
|
246
|
+
### 页面范式
|
|
247
|
+
|
|
248
|
+
**页面范式来源**:引用内置
|
|
249
|
+
|
|
250
|
+
| 页面类型 | 骨架/容器组合 | 参考 |
|
|
251
|
+
|---------|-------------|------|
|
|
252
|
+
| 列表页 | app-header + app-sider + app-main > ds-table + stat | layouts.md 管理后台列表页 |
|
|
253
|
+
| 表单页 | app-header + app-sider + app-main > field / input / textarea | layouts.md 表单页 |
|
|
254
|
+
| 仪表盘 | app-header + app-sider + app-main > stat + grid-2-1 | layouts.md 仪表盘 |
|
|
255
|
+
|
|
256
|
+
> 第二列的组合**全部取自** \`layouts.md\` 的类清单契约 + 上方容器骨架块(\`ds-table\` / \`stat\` /
|
|
257
|
+
> \`field\` / \`input\` / \`textarea\` / \`grid-2-1\`)——不引入表外类名(v0.55.0 修正:原表用了
|
|
258
|
+
> \`filterbar\` / \`table\` / \`form\` / \`grid\`,均不在类清单契约内,builder 照抄会得到无 CSS 支撑的类)。
|
|
259
|
+
> 本表仅为**内置节奏的示意**;声明 \`引用内置\` 时 L4b 免检,实际节奏以内置 \`layouts.md\` 为准。
|
|
260
|
+
|
|
261
|
+
## components
|
|
262
|
+
|
|
263
|
+
| 组件 | 类型 | variants | sizes | states | 用途 | 禁止 |
|
|
264
|
+
|------|------|----------|-------|--------|------|------|
|
|
265
|
+
${CONTRACT_ROWS.join('\n')}
|
|
266
|
+
|
|
267
|
+
## motion
|
|
268
|
+
|
|
269
|
+
- duration 150–250ms / easing ease-out(\`cubic-bezier(0.2, 0, 0, 1)\`)
|
|
270
|
+
|
|
271
|
+
## voice
|
|
272
|
+
|
|
273
|
+
- 语气:${category} 语境的简洁专业表达——具体文案要求由项目 PRD 补充。
|
|
274
|
+
|
|
275
|
+
## brand
|
|
276
|
+
|
|
277
|
+
- 品牌主色:${primary}(accent 用法:主 CTA / 链接 / 选中态)
|
|
278
|
+
|
|
279
|
+
## anti-patterns
|
|
280
|
+
|
|
281
|
+
1. 禁止内联样式漂移
|
|
282
|
+
2. 禁止非 token 颜色
|
|
283
|
+
3. ${category} 语境:不得引入与来源风格冲突的装饰效果
|
|
284
|
+
|
|
285
|
+
## palette
|
|
286
|
+
|
|
287
|
+
- neutral / primary / success / warning / danger 各 50–900 阶梯(primary 以 ${primary} 为 500 锚点,公式见 token-derivation.md)
|
|
288
|
+
|
|
289
|
+
## principles
|
|
290
|
+
|
|
291
|
+
1. 一致性优先于局部创意——同类信息用同一呈现
|
|
292
|
+
2. 清晰优于装饰——信息层级先于视觉效果
|
|
293
|
+
3. 可访问性默认开启——所有交互元素满足 WCAG AA
|
|
294
|
+
|
|
295
|
+
## governance
|
|
296
|
+
|
|
297
|
+
- contract: legacy
|
|
298
|
+
- version: 0.1.0
|
|
299
|
+
- 负责人: {项目角色}
|
|
300
|
+
- 弃用策略: 组件弃用需在 changelog 记录 + 保留 1 个迭代周期
|
|
301
|
+
- changelog: 见文末"变更履历"段
|
|
302
|
+
|
|
303
|
+
## 变更履历
|
|
304
|
+
|
|
305
|
+
| 时间 | 变更内容 | 来源 |
|
|
306
|
+
|------|---------|------|
|
|
307
|
+
| ${new Date().toISOString().slice(0, 10)} | 从 open-design \`${id}\` 转换生成 | design-system-import.mjs |
|
|
308
|
+
`;
|
|
309
|
+
|
|
310
|
+
const outPath = outArg ? resolve(outArg) : resolve(`.team-flow/design-system/base.md`);
|
|
311
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
312
|
+
writeFileSync(outPath, base, 'utf-8');
|
|
313
|
+
|
|
314
|
+
console.log(`base.md 已生成:${outPath}`);
|
|
315
|
+
console.log(profileArg
|
|
316
|
+
? ` 来源:内置 profile ${profileArg}(Ant Design v5 seed token,MIT + team-flow 自有规范)`
|
|
317
|
+
: ` 来源:open-design ${id}(${category})`);
|
|
318
|
+
console.log(` accent=${primary} / surface=${surface} / text=${text}`);
|
|
319
|
+
console.log(` 组件契约表:20 类基线(待审阅调整)`);
|
|
320
|
+
console.log(' contract: legacy(导入类默认 —— 用户裁决达标后经 iterate 升 v1)');
|
|
321
|
+
// 提示中的脚本路径用**本脚本所在插件目录**解析(绝对路径,可直接复制执行)——
|
|
322
|
+
// 裸 `node scripts/...` 在插件零拷贝安装下不解析(v0.54.0 P4 根因)。
|
|
323
|
+
const pluginDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
324
|
+
console.log('\n下一步(由 design-system skill 的 Step 5 评审 → Step 6 落盘承接):');
|
|
325
|
+
console.log(` 1) node ${join(pluginDir, 'scripts', 'gen-primer.mjs')} ${outPath} # 生成 primer(组件白名单 + digest,缺失会阻断 prototype Step 0)`);
|
|
326
|
+
console.log(` 2) node ${join(pluginDir, 'scripts', 'guard', 'design-token-guard.mjs')} ${dirname(outPath)} # 硬校验 + 六层审计`);
|