@xulthekl/team-flow 0.54.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 +1 -1
- 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 +63 -1
- package/GEMINI.md +1 -1
- package/INSTALL.md +1 -1
- package/README.md +1 -1
- package/agents/prototype-env-scout.md +4 -4
- 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 +1 -1
- package/scripts/check-project-config.mjs +84 -0
- package/scripts/design-system-clone.mjs +150 -0
- package/scripts/design-system-import.mjs +102 -14
- package/scripts/gen-primer.mjs +65 -13
- package/scripts/guard/checks/tasks-complete.mjs +9 -4
- package/scripts/guard/design-token-guard.mjs +136 -117
- 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 +124 -12
- 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 +101 -9
- package/skills/design-system/SKILL.md +46 -23
- package/skills/design-system/references/agents/design-system-architect.md +25 -12
- package/skills/design-system/references/creation-flow.md +17 -0
- package/skills/design-system/references/creation-modes.md +171 -0
- package/skills/design-system/references/showcase-board-b-end.md +50 -36
- package/skills/design-system/references/showcase-board-c-end.md +59 -36
- package/skills/design-system/references/variant-schema.md +21 -1
- package/skills/prototype/SKILL.md +4 -0
- package/skills/prototype/references/builder-methodology.md +11 -4
- package/skills/prototype/references/layouts.md +10 -0
- package/skills/prototype/references/orchestration-flow.md +8 -0
- package/skills/workflow-bootstrap/SKILL.md +15 -5
- package/src/parsing/requirement-blocks.ts +34 -5
- package/src/validation/validator.ts +8 -1
|
@@ -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);
|
|
@@ -1,37 +1,87 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// design-system-import.mjs — open-design DESIGN.md → team-flow base.md 转换器(v0.
|
|
2
|
+
// design-system-import.mjs — open-design DESIGN.md / 内置 profile → team-flow base.md 转换器(v0.55.0,设计 §4.5.2 / §8.2.4)
|
|
3
3
|
//
|
|
4
4
|
// 解析 open-design 九节 DESIGN.md,映射到 team-flow 的 9 段 schema(A1/A2/B-slot + 组件契约表 + principles/governance)。
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// 关键约束:**转换后必须补齐完整度**(palette 阶梯 / A2 派生公式 / B-slot 别名 / 组件契约表基线)——
|
|
6
|
+
// 来源原文没有这些,转换器按 team-flow 规则生成。
|
|
7
7
|
//
|
|
8
|
-
//
|
|
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 路径>]
|
|
9
20
|
// 输出后建议运行:node scripts/guard/design-token-guard.mjs <base.md>
|
|
10
21
|
|
|
11
22
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
12
|
-
import { resolve, dirname, basename, join } from 'node:path';
|
|
23
|
+
import { resolve, dirname, basename, extname, join } from 'node:path';
|
|
13
24
|
import { fileURLToPath } from 'node:url';
|
|
14
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
|
+
|
|
15
49
|
const argv = process.argv.slice(2);
|
|
16
50
|
const outIdx = argv.indexOf('--out');
|
|
17
51
|
const outArg = outIdx !== -1 ? argv[outIdx + 1] : null;
|
|
18
52
|
const idIdx = argv.indexOf('--id');
|
|
19
53
|
const idArg = idIdx !== -1 ? argv[idIdx + 1] : null;
|
|
20
|
-
const
|
|
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));
|
|
21
59
|
const srcPath = positional[0];
|
|
22
60
|
|
|
23
|
-
if (!
|
|
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) {
|
|
24
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 路径>]');
|
|
25
68
|
process.exit(1);
|
|
26
69
|
}
|
|
27
|
-
const srcAbs = resolve(srcPath);
|
|
28
|
-
if (!existsSync(srcAbs)) {
|
|
70
|
+
const srcAbs = srcPath ? resolve(srcPath) : null;
|
|
71
|
+
if (srcAbs && !existsSync(srcAbs)) {
|
|
29
72
|
console.error(`DESIGN.md not found: ${srcAbs}`);
|
|
30
73
|
process.exit(1);
|
|
31
74
|
}
|
|
32
75
|
|
|
33
|
-
const src = readFileSync(srcAbs, 'utf-8');
|
|
34
|
-
|
|
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)));
|
|
35
85
|
|
|
36
86
|
// ── 解析 open-design 九节 ──
|
|
37
87
|
|
|
@@ -141,6 +191,15 @@ const base = `# ${title}
|
|
|
141
191
|
> ${summary}
|
|
142
192
|
> 来源归因:open-design(nexu-io)MIT License,"inspired by" 逆向工程描述,非官方资产。
|
|
143
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
|
+
|
|
144
203
|
## color
|
|
145
204
|
|
|
146
205
|
**A1-identity(8 必选)**
|
|
@@ -171,7 +230,33 @@ const base = `# ${title}
|
|
|
171
230
|
|
|
172
231
|
## layout
|
|
173
232
|
|
|
174
|
-
- 栅格 12 列 /
|
|
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\` 为准。
|
|
175
260
|
|
|
176
261
|
## components
|
|
177
262
|
|
|
@@ -209,7 +294,7 @@ ${CONTRACT_ROWS.join('\n')}
|
|
|
209
294
|
|
|
210
295
|
## governance
|
|
211
296
|
|
|
212
|
-
- contract:
|
|
297
|
+
- contract: legacy
|
|
213
298
|
- version: 0.1.0
|
|
214
299
|
- 负责人: {项目角色}
|
|
215
300
|
- 弃用策略: 组件弃用需在 changelog 记录 + 保留 1 个迭代周期
|
|
@@ -227,9 +312,12 @@ mkdirSync(dirname(outPath), { recursive: true });
|
|
|
227
312
|
writeFileSync(outPath, base, 'utf-8');
|
|
228
313
|
|
|
229
314
|
console.log(`base.md 已生成:${outPath}`);
|
|
230
|
-
console.log(
|
|
315
|
+
console.log(profileArg
|
|
316
|
+
? ` 来源:内置 profile ${profileArg}(Ant Design v5 seed token,MIT + team-flow 自有规范)`
|
|
317
|
+
: ` 来源:open-design ${id}(${category})`);
|
|
231
318
|
console.log(` accent=${primary} / surface=${surface} / text=${text}`);
|
|
232
319
|
console.log(` 组件契约表:20 类基线(待审阅调整)`);
|
|
320
|
+
console.log(' contract: legacy(导入类默认 —— 用户裁决达标后经 iterate 升 v1)');
|
|
233
321
|
// 提示中的脚本路径用**本脚本所在插件目录**解析(绝对路径,可直接复制执行)——
|
|
234
322
|
// 裸 `node scripts/...` 在插件零拷贝安装下不解析(v0.54.0 P4 根因)。
|
|
235
323
|
const pluginDir = dirname(dirname(fileURLToPath(import.meta.url)));
|
package/scripts/gen-primer.mjs
CHANGED
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// gen-primer.mjs —
|
|
2
|
+
// gen-primer.mjs — 从设计系统确定性生成 primer.md(v0.55.0,设计 §4.1.2 / §8.2.2)
|
|
3
3
|
//
|
|
4
|
-
// primer 是"AI 约束入口":组件白名单 + token 速查 +
|
|
5
|
-
// 确定性:除"生成时间"
|
|
4
|
+
// primer 是"AI 约束入口":组件白名单 + token 速查 + 硬规则 + **页面范式**,供 prototype-builder 消费。
|
|
5
|
+
// 确定性:除"生成时间"一行外,输出完全由设计系统内容决定(纯字符串拼接,零 LLM)。
|
|
6
|
+
//
|
|
7
|
+
// v0.55.0 两项修正(P1.5 实证):
|
|
8
|
+
// ① **digest 覆盖 base + 参与合并的端变体**。原先只对 base.md 做 sha256 →
|
|
9
|
+
// 变体变更不判 STALE → prototype Step 0 gate 放行 → **原型静默用旧页面范式**。
|
|
10
|
+
// 无变体时 digest 与旧实现**逐字节一致**(`[base].join('\n') === base`),既有测试不受影响。
|
|
11
|
+
// ② **「页面范式」段从设计系统派生**,含各端的页面类型表与容器骨架块(builder 的物料来源)。
|
|
12
|
+
// 原先是**硬编码**字符串(primer 里唯一不读 base.md 的段),项目既有页面规范无法进入流程。
|
|
6
13
|
//
|
|
7
14
|
// Usage:
|
|
8
15
|
// node scripts/gen-primer.mjs <base.md path> [--out <primer path>]
|
|
9
|
-
// 生成 primer(默认写 base.md 同目录 primer.md
|
|
16
|
+
// 生成 primer(默认写 base.md 同目录 primer.md);同目录端变体自动参与
|
|
10
17
|
// node scripts/gen-primer.mjs <base.md path> --check
|
|
11
|
-
// 校验同目录 primer.md 的 digest 与
|
|
18
|
+
// 校验同目录 primer.md 的 digest 与"base + 变体"是否一致
|
|
12
19
|
// exit 0 = 一致;exit 2 = 过期或缺失(供 builder gate 判定)
|
|
13
20
|
//
|
|
14
21
|
// 配套测试:"生成物 == 源表"(tests/lib/gen-primer.test.mjs)
|
|
15
22
|
|
|
16
23
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
17
24
|
import { createHash } from 'node:crypto';
|
|
18
|
-
import { dirname, join, resolve } from 'node:path';
|
|
25
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
19
26
|
import {
|
|
20
27
|
parseComponentsTable,
|
|
21
28
|
parseContract,
|
|
22
29
|
parseA1Tokens,
|
|
23
30
|
parseAntiPatterns,
|
|
31
|
+
parsePageArchetype,
|
|
32
|
+
subsectionBodyRaw,
|
|
24
33
|
COMPONENT_COUNT_PASS,
|
|
25
34
|
COMPONENT_COUNT_WARN,
|
|
26
35
|
} from './lib/ds-parse.mjs';
|
|
36
|
+
import { resolveInputs, readFileOrNull } from './lib/ds-inputs.mjs';
|
|
27
37
|
|
|
28
38
|
const argv = process.argv.slice(2);
|
|
29
39
|
const CHECK = argv.includes('--check');
|
|
@@ -44,10 +54,19 @@ if (!existsSync(baseAbs)) {
|
|
|
44
54
|
}
|
|
45
55
|
|
|
46
56
|
const baseContent = readFileSync(baseAbs, 'utf-8');
|
|
47
|
-
|
|
57
|
+
|
|
58
|
+
// ── 变体发现与 digest(v0.55.0)──
|
|
59
|
+
// 复用 guard 的输入解析(下沉在 ds-inputs.mjs,避免 import 顶层副作用的 guard 脚本)。
|
|
60
|
+
const inputs = resolveInputs(baseAbs, null);
|
|
61
|
+
const variantFiles = inputs.files.filter(f => resolve(f) !== baseAbs);
|
|
62
|
+
const variants = variantFiles.map(f => ({ file: f, name: basename(f).replace(/-end\.md$/i, ''), text: readFileOrNull(f) || '' }));
|
|
63
|
+
|
|
64
|
+
// digest 源 = base + 全部参与合并的变体(无变体时逐字节等同旧实现)
|
|
65
|
+
const digest = createHash('sha256').update([baseContent, ...variants.map(v => v.text)].join('\n'), 'utf-8').digest('hex');
|
|
48
66
|
const digestShort = digest.slice(0, 16);
|
|
49
67
|
const primerPath = outArg ? resolve(outArg) : join(dirname(baseAbs), 'primer.md');
|
|
50
68
|
const DIGEST_RE = /sha256:([0-9a-f]{16,})/;
|
|
69
|
+
const sourceLabel = ['base.md', ...variants.map(v => basename(v.file))].join(' + ');
|
|
51
70
|
|
|
52
71
|
// ── --check 模式:校验 primer 新鲜度(builder gate 调用)──
|
|
53
72
|
|
|
@@ -63,7 +82,7 @@ if (CHECK) {
|
|
|
63
82
|
process.exit(2);
|
|
64
83
|
}
|
|
65
84
|
if (m[1] !== digestShort && !digest.startsWith(m[1])) {
|
|
66
|
-
console.error(`STALE: primer.md 已过期(记录 ${m[1]},当前
|
|
85
|
+
console.error(`STALE: primer.md 已过期(记录 ${m[1]},当前 ${sourceLabel} ${digestShort})——请运行 design-system iterate 重新生成`);
|
|
67
86
|
process.exit(2);
|
|
68
87
|
}
|
|
69
88
|
console.log(`OK: primer.md digest 一致(${digestShort})`);
|
|
@@ -92,10 +111,10 @@ const countLabel = count >= COMPONENT_COUNT_PASS
|
|
|
92
111
|
|
|
93
112
|
// 白名单行:Name(variants · sizes)
|
|
94
113
|
const whitelistLines = componentRows.map(r => {
|
|
95
|
-
const
|
|
114
|
+
const variants_ = r.hasVariants ? r.variants.replace(/\s*\/\s*/g, '|') : '—';
|
|
96
115
|
const sizes = r.sizes && r.sizes !== '—' ? r.sizes.replace(/\s*\/\s*/g, '|') : '—';
|
|
97
116
|
const typeMark = r.type === '豁免' ? '(展示类)' : r.type === '轻量' ? '(轻量)' : '';
|
|
98
|
-
return `- ${r.name}${typeMark}: variants=${
|
|
117
|
+
return `- ${r.name}${typeMark}: variants=${variants_} · sizes=${sizes}`;
|
|
99
118
|
});
|
|
100
119
|
|
|
101
120
|
// Token 速查行
|
|
@@ -113,10 +132,42 @@ const hardRules = [
|
|
|
113
132
|
...antiPatterns.map((p, i) => `${i + 3}. ${p}`),
|
|
114
133
|
];
|
|
115
134
|
|
|
135
|
+
/**
|
|
136
|
+
* 「页面范式」段(v0.55.0:**忠实派生物**,不再是硬编码)。
|
|
137
|
+
*
|
|
138
|
+
* 逐端摘录 `layout` 段的 `### 页面范式` 与 `### 容器骨架` 子块原文——
|
|
139
|
+
* 前者给出"页面类型 → 骨架组合"的路由,后者给出**骨架的类名与关键 CSS**(builder 的物料来源:
|
|
140
|
+
* 内置 `template.html` 里没有 `header-bar`/`sidebar-container` 这类项目容器类)。
|
|
141
|
+
*/
|
|
142
|
+
function pageArchetypeSection() {
|
|
143
|
+
const scopes = variants.length ? variants : [{ file: baseAbs, name: null, text: baseContent }];
|
|
144
|
+
const blocks = [];
|
|
145
|
+
for (const s of scopes) {
|
|
146
|
+
const label = s.name ? `**${s.name}**` : null;
|
|
147
|
+
const arche = parsePageArchetype(s.text);
|
|
148
|
+
const archeSub = subsectionBodyRaw(s.text, 'layout', '页面范式');
|
|
149
|
+
const skelSub = subsectionBodyRaw(s.text, 'layout', '容器骨架');
|
|
150
|
+
|
|
151
|
+
if (!arche.declared) {
|
|
152
|
+
blocks.push(`${label ? `${label}:` : ''}未声明页面范式来源 —— 在 \`layout\` 段补 \`页面范式来源:引用内置\`(绿地)或 \`项目自有\`(棕地,需附页面类型表与容器骨架块)`);
|
|
153
|
+
} else if (/^引用内置$/.test(arche.source)) {
|
|
154
|
+
blocks.push(`${label ? `${label}:` : ''}引用内置 —— \`prototype/references/layouts.md\` 的${s.name ? ` ${s.name} ` : ''}节奏表`);
|
|
155
|
+
} else if (/^同/.test(arche.source)) {
|
|
156
|
+
blocks.push(`${label ? `${label}:` : ''}${arche.source}(完整性由被指向端保证)`);
|
|
157
|
+
} else {
|
|
158
|
+
blocks.push(`${label ? `${label}:` : ''}以下为设计系统的原文摘录,**以此为准**(内置 layouts.md 仅作回退默认)\n\n${(archeSub || '').trim()}`);
|
|
159
|
+
}
|
|
160
|
+
if (skelSub) {
|
|
161
|
+
blocks.push(`#### 容器骨架${s.name ? `(${s.name})` : ''}\n\n以下骨架类**不在**内置 \`template.html\` 中,须按类清单契约在页面 \`<style>\` 内定义:\n\n${skelSub.trim()}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return blocks.join('\n\n');
|
|
165
|
+
}
|
|
166
|
+
|
|
116
167
|
const primer = `# AI Primer — 原型生成约束
|
|
117
168
|
|
|
118
|
-
> 本文件由 scripts/gen-primer.mjs
|
|
119
|
-
> 生成时间:${new Date().toISOString()} |
|
|
169
|
+
> 本文件由 scripts/gen-primer.mjs 从设计系统确定性生成,请勿手工编辑。
|
|
170
|
+
> 生成时间:${new Date().toISOString()} | 来源:${sourceLabel} @ sha256:${digestShort}
|
|
120
171
|
> 契约表:${count} 类组件(${countLabel}) | contract=${contract || 'unset'}
|
|
121
172
|
|
|
122
173
|
## 可用组件白名单(只用这些)
|
|
@@ -133,11 +184,12 @@ ${hardRules.join('\n')}
|
|
|
133
184
|
|
|
134
185
|
## 页面范式
|
|
135
186
|
|
|
136
|
-
|
|
187
|
+
${pageArchetypeSection()}
|
|
137
188
|
`;
|
|
138
189
|
|
|
139
190
|
mkdirSync(dirname(primerPath), { recursive: true });
|
|
140
191
|
writeFileSync(primerPath, primer, 'utf-8');
|
|
141
192
|
console.log(`primer 已生成:${primerPath}`);
|
|
142
193
|
console.log(` 组件白名单:${count} 类(${countLabel})`);
|
|
194
|
+
if (variants.length) console.log(` 参与 digest 的变体:${variants.map(v => basename(v.file)).join(' / ')}`);
|
|
143
195
|
console.log(` digest:sha256:${digestShort}`);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import fs from 'node:fs';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { readState } from '../../lib/state-loader.mjs';
|
|
9
|
+
import { parseTaskLine } from '../../lib/md-normalize.mjs';
|
|
9
10
|
import {
|
|
10
11
|
isLegacyChange,
|
|
11
12
|
fullWorkflowSkipViolation,
|
|
@@ -49,16 +50,20 @@ export function checkTasksComplete(changeDir) {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
const content = fs.readFileSync(tasksPath, 'utf-8');
|
|
52
|
-
|
|
53
|
+
// v0.55.0 §8.4.3 横展(FB-4):识别走共享原语 `parseTaskLine`
|
|
54
|
+
// (容忍 `*` bullet / 制表符分隔等纯格式变体)。此处是**计数/存在性语义**。
|
|
55
|
+
// 注:原实现的正则无 `$` 锚且用 `[ \t]*`,本就容忍缩进——本次不放宽也不收紧缩进。
|
|
56
|
+
const taskLines = content.split('\n').map(line => parseTaskLine(line)).filter(Boolean);
|
|
57
|
+
const uncheckedCount = taskLines.filter(task => !task.checked).length;
|
|
53
58
|
|
|
54
|
-
if (
|
|
59
|
+
if (uncheckedCount > 0) {
|
|
55
60
|
return {
|
|
56
61
|
pass: false,
|
|
57
|
-
failures: [`tasks.md: ${
|
|
62
|
+
failures: [`tasks.md: ${uncheckedCount} unchecked task(s) remaining`],
|
|
58
63
|
};
|
|
59
64
|
}
|
|
60
65
|
|
|
61
|
-
const hasAny =
|
|
66
|
+
const hasAny = taskLines.some(task => task.checked);
|
|
62
67
|
if (!hasAny) {
|
|
63
68
|
return { pass: false, failures: ['tasks.md: no completed tasks found'] };
|
|
64
69
|
}
|