@xulthekl/team-flow 0.54.0 → 0.56.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.
Files changed (63) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.cursor-plugin/marketplace.json +1 -1
  6. package/.cursor-plugin/plugin.json +1 -1
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/AGENTS.md +2 -2
  9. package/CHANGELOG.md +92 -1
  10. package/GEMINI.md +1 -1
  11. package/INSTALL.md +1 -1
  12. package/README.md +1 -1
  13. package/agents/prototype-env-scout.md +4 -4
  14. package/dist/parsing/requirement-blocks.d.ts +26 -0
  15. package/dist/parsing/requirement-blocks.js +33 -5
  16. package/dist/validation/validator.js +8 -1
  17. package/docs/README_en.md +1 -1
  18. package/gemini-extension.json +1 -1
  19. package/hooks/session-start +19 -2
  20. package/llms.txt +1 -1
  21. package/package.json +1 -1
  22. package/plugin.json +1 -1
  23. package/scripts/check-project-config.mjs +84 -0
  24. package/scripts/design-system-clone.mjs +150 -0
  25. package/scripts/design-system-import.mjs +102 -14
  26. package/scripts/gen-primer.mjs +65 -13
  27. package/scripts/guard/checks/tasks-complete.mjs +9 -4
  28. package/scripts/guard/design-token-guard.mjs +136 -117
  29. package/scripts/infer-workflow.mjs +10 -1
  30. package/scripts/lib/arch-merge.mjs +20 -6
  31. package/scripts/lib/arch-parse.mjs +5 -11
  32. package/scripts/lib/ds-inputs.mjs +125 -0
  33. package/scripts/lib/ds-parse.mjs +124 -12
  34. package/scripts/lib/execution-recommendation.mjs +10 -1
  35. package/scripts/lib/glaf4-delegation.mjs +14 -3
  36. package/scripts/lib/hash.mjs +18 -2
  37. package/scripts/lib/md-normalize.mjs +108 -0
  38. package/scripts/lib/prototype-sync.mjs +19 -1
  39. package/scripts/lib/sdd-overlay.mjs +17 -6
  40. package/scripts/lib/slug.mjs +68 -0
  41. package/scripts/lib/solutions-capture.mjs +6 -12
  42. package/scripts/lib/solutions-index-gen.mjs +2 -2
  43. package/scripts/lib/solutions-phases.mjs +32 -0
  44. package/scripts/lib/solutions-promote.mjs +16 -5
  45. package/scripts/lib/spec-merge.mjs +46 -11
  46. package/scripts/lib/state-loader.mjs +4 -1
  47. package/scripts/lib/test-merge.mjs +4 -1
  48. package/scripts/token-extract.mjs +101 -9
  49. package/skills/ce-compound/references/three-tier-index.md +6 -2
  50. package/skills/design-system/SKILL.md +46 -23
  51. package/skills/design-system/references/agents/design-system-architect.md +25 -12
  52. package/skills/design-system/references/creation-flow.md +17 -0
  53. package/skills/design-system/references/creation-modes.md +171 -0
  54. package/skills/design-system/references/showcase-board-b-end.md +50 -36
  55. package/skills/design-system/references/showcase-board-c-end.md +59 -36
  56. package/skills/design-system/references/variant-schema.md +21 -1
  57. package/skills/prototype/SKILL.md +4 -0
  58. package/skills/prototype/references/builder-methodology.md +11 -4
  59. package/skills/prototype/references/layouts.md +10 -0
  60. package/skills/prototype/references/orchestration-flow.md +8 -0
  61. package/skills/workflow-bootstrap/SKILL.md +15 -5
  62. package/src/parsing/requirement-blocks.ts +34 -5
  63. package/src/validation/validator.ts +8 -1
@@ -22,6 +22,11 @@ import { readFileSync, writeFileSync, existsSync, appendFileSync, mkdirSync } fr
22
22
  import { join, basename } from 'node:path';
23
23
  import { pathToFileURL } from 'node:url';
24
24
  import { meetsMinSeverity, nextSeverity } from './severity.mjs';
25
+ import { normalizeInline } from './md-normalize.mjs';
26
+ import { slugify } from './slug.mjs';
27
+
28
+ /** 文件名 slug 截断长度(与 solutions-capture 一致) */
29
+ const SLUG_MAX_LENGTH = 40;
25
30
 
26
31
  /**
27
32
  * 剥离 YAML 行尾注释(空格 + `#` 起始)——v0.23 §91 硬化:
@@ -39,9 +44,12 @@ function parseFrontmatter(content) {
39
44
  if (!match) return null;
40
45
  const fm = {};
41
46
  for (const line of match[1].split('\n')) {
42
- const idx = line.indexOf(':');
47
+ // v0.55.0 §8.4.3 横展(FB-4):容忍全角冒号、`**key**` 加粗与反引号
48
+ // (走共享层 `normalizeInline`)。原实现只认半角 `:` + 裸键名,
49
+ // `severity:high` / `**severity**: high` 会整键丢失 → 该条静默不晋升。
50
+ const idx = line.search(/[::]/);
43
51
  if (idx > 0) {
44
- fm[line.slice(0, idx).trim()] = stripInlineComment(line.slice(idx + 1));
52
+ fm[normalizeInline(line.slice(0, idx))] = normalizeInline(stripInlineComment(line.slice(idx + 1)));
45
53
  }
46
54
  }
47
55
  return fm;
@@ -100,10 +108,13 @@ function markEntryConfirmed(filePath) {
100
108
  const content = readFileSync(filePath, 'utf-8');
101
109
  const block = content.match(/^---\n([\s\S]*?)\n---/);
102
110
  if (!block) return;
103
- const counter = block[1].match(/^confirmed:\s*(\d+)\s*$/m);
111
+ // v0.55.0 §8.4.3 横展(FB-4):容忍全角冒号与 `**confirmed**` 加粗。
112
+ // 放宽前这类行匹配不到 → 走追加分支 → frontmatter 出现**重复 confirmed 键**
113
+ // (计数错乱且静默)。命中后统一改回规范形态 `confirmed: N`。
114
+ const counter = block[1].match(/^\**confirmed\**\s*[::]\s*(\d+)\s*$/m);
104
115
  const next = counter ? parseInt(counter[1], 10) + 1 : 2;
105
116
  const frontmatter = counter
106
- ? block[1].replace(/^confirmed:\s*\d+\s*$/m, `confirmed: ${next}`)
117
+ ? block[1].replace(/^\**confirmed\**\s*[::]\s*\d+\s*$/m, `confirmed: ${next}`)
107
118
  : `${block[1]}\nconfirmed: ${next}`;
108
119
  writeFileSync(filePath, content.replace(block[0], `---\n${frontmatter}\n---`), 'utf-8');
109
120
  }
@@ -181,7 +192,7 @@ export function run(args = {}) {
181
192
  mkdirSync(phaseDir, { recursive: true });
182
193
  }
183
194
 
184
- const slug = learning.title.toLowerCase().replace(/[^a-z0-9一-鿿]+/g, '-').slice(0, 40);
195
+ const slug = slugify(learning.title, { maxLength: SLUG_MAX_LENGTH });
185
196
  const fileName = `${date}-${slug}.md`;
186
197
  const filePath = join(phaseDir, fileName);
187
198
 
@@ -14,8 +14,40 @@
14
14
  // 5. 主基格式不做规范化:按 `### Requirement:` 为块边界解析,兼容既有主基的
15
15
  // `## ADDED Requirements` 容器形态(不改写既有段落结构)。
16
16
 
17
- const REQ_RE = /^###\s*Requirement:\s*(.+?)\s*$/;
17
+ import { stripInlineEmphasis } from './md-normalize.mjs';
18
+
19
+ // v0.55.0 §8.4.3 横展(FB-4):标题行先过 `stripInlineEmphasis` 再匹配——容忍
20
+ // `### **Requirement**: X` 这类手写加粗;同时容忍全角冒号与冒号前空格
21
+ // (`md-normalize.matchKeyValue` 的同一约定)。**不放宽层级**:仍是恰好 `###`。
22
+ const REQ_RE = /^###\s*Requirement\s*[::]\s*(.+?)\s*$/;
18
23
  const H2_RE = /^##\s+/;
24
+ /** 形似 Requirement 标题(带分隔符)——用于「解析失败不静默」告警。 */
25
+ const REQUIREMENT_LIKE_RE = /^#{1,6}\s*\**\s*Requirement\s*\**\s*[::]/i;
26
+
27
+ /** 归一化后匹配 requirement 标题:容忍加粗等纯格式变体(FB-4 横展)。 */
28
+ function matchRequirement(line) {
29
+ return stripInlineEmphasis(line).match(REQ_RE);
30
+ }
31
+
32
+ /** 解析失败不静默(约束②):形似 requirement 标题但未被接受的 → WARN(不报错)。 */
33
+ function warnUnparsedRequirementHeadings(content) {
34
+ const lines = content.split('\n');
35
+ for (let i = 0; i < lines.length; i++) {
36
+ if (!REQUIREMENT_LIKE_RE.test(lines[i]) || matchRequirement(lines[i])) continue;
37
+ console.warn(` [WARN] spec-merge: 第 ${i + 1} 行形似 Requirement 标题但无法解析(按非标题处理):${lines[i].trim().slice(0, 80)}`);
38
+ }
39
+ }
40
+
41
+ /**
42
+ * 名称比较键:容忍加粗/反引号/首尾空白等**纯格式**差异。
43
+ *
44
+ * 必要性:delta 侧解析器(`src/parsing/requirement-blocks.ts` 的
45
+ * `normalizeRequirementName`)**只做 trim**,主基侧若单边归一化,`**Foo**` 与 `Foo`
46
+ * 会被判为不同名 → ADDED 静默追加重复块 / MODIFIED 误报「不存在」。故比较时两侧同键。
47
+ */
48
+ function nameKey(name) {
49
+ return stripInlineEmphasis(String(name ?? '').replace(/`/g, '')).trim();
50
+ }
19
51
 
20
52
  /** dist 解析器惰性加载(与 cmd-sync 导入 Validator 同源)。 */
21
53
  let _parseDeltaSpec = null;
@@ -39,7 +71,7 @@ function blockBody(blockLines) {
39
71
 
40
72
  /** 切掉 `#### Previous version` 子节(及其后内容)——用于幂等比对与旧内容提取。 */
41
73
  function stripPreviousVersion(body) {
42
- const idx = body.search(/^####\s+Previous version\b/m);
74
+ const idx = body.search(/^####\s+\**\s*Previous version\b/m);
43
75
  return idx === -1 ? body : body.slice(0, idx).replace(/\s+$/, '');
44
76
  }
45
77
 
@@ -53,8 +85,8 @@ function escapeRe(s) {
53
85
  * `v1-C3-session-governance` 的注记)。
54
86
  */
55
87
  function hasMergedNote(body, changeName) {
56
- const re = new RegExp(`^####\\s+Previous version\\b.*(?<![\\w-])${escapeRe(changeName)}(?![\\w-])`, 'm');
57
- return re.test(body);
88
+ const re = new RegExp(`^####\\s+\\**\\s*Previous version\\b.*(?<![\\w-])${escapeRe(changeName)}(?![\\w-])`, 'm');
89
+ return re.test(stripInlineEmphasis(body));
58
90
  }
59
91
 
60
92
  function renameNote(from, changeName) {
@@ -62,7 +94,7 @@ function renameNote(from, changeName) {
62
94
  }
63
95
 
64
96
  function isRemovedSection(section) {
65
- return typeof section === 'string' && /^Removed\b/i.test(section.trim());
97
+ return typeof section === 'string' && /^Removed\b/i.test(stripInlineEmphasis(section).trim());
66
98
  }
67
99
 
68
100
  /**
@@ -81,14 +113,14 @@ function parseMain(content) {
81
113
  i++;
82
114
  continue;
83
115
  }
84
- const m = lines[i].match(REQ_RE);
116
+ const m = matchRequirement(lines[i]);
85
117
  if (!m) {
86
118
  i++;
87
119
  continue;
88
120
  }
89
121
  let end = lines.length;
90
122
  for (let j = i + 1; j < lines.length; j++) {
91
- if (REQ_RE.test(lines[j]) || H2_RE.test(lines[j])) {
123
+ if (matchRequirement(lines[j]) || H2_RE.test(lines[j])) {
92
124
  end = j;
93
125
  break;
94
126
  }
@@ -99,9 +131,10 @@ function parseMain(content) {
99
131
  return { lines, blocks };
100
132
  }
101
133
 
102
- /** 在正常区(非 `## Removed` 段)按名查找 requirement 块。 */
134
+ /** 在正常区(非 `## Removed` 段)按名查找 requirement 块(名称按 `nameKey` 宽松比较)。 */
103
135
  function findBlock(parsed, name) {
104
- return parsed.blocks.find(b => b.name === name && !isRemovedSection(b.section)) || null;
136
+ const key = nameKey(name);
137
+ return parsed.blocks.find(b => nameKey(b.name) === key && !isRemovedSection(b.section)) || null;
105
138
  }
106
139
 
107
140
  /**
@@ -122,7 +155,7 @@ function findAppendIndex(parsed) {
122
155
 
123
156
  /** 定位 `## Removed` 段的 [start, end)(end = 下一个二级标题或文件末尾);无则 -1。 */
124
157
  function findRemovedSectionRange(parsed) {
125
- const start = parsed.lines.findIndex(l => /^##\s+Removed\s*$/i.test(l));
158
+ const start = parsed.lines.findIndex(l => /^##\s+\**\s*Removed\**\s*$/i.test(stripInlineEmphasis(l)));
126
159
  if (start === -1) return { start: -1, end: -1 };
127
160
  let end = parsed.lines.length;
128
161
  for (let j = start + 1; j < parsed.lines.length; j++) {
@@ -235,7 +268,8 @@ function applyRemoved(content, plan, changeName, today, report) {
235
268
 
236
269
  if (!target) {
237
270
  // 幂等:该名已出现在 `## Removed` 段
238
- const inRemoved = parsed.blocks.some(b => b.name === name && isRemovedSection(b.section));
271
+ const key = nameKey(name);
272
+ const inRemoved = parsed.blocks.some(b => nameKey(b.name) === key && isRemovedSection(b.section));
239
273
  if (inRemoved) {
240
274
  report.skipped++;
241
275
  continue;
@@ -300,6 +334,7 @@ export async function mergeMainSpec(mainContent, deltaContent, changeName, optio
300
334
  }
301
335
 
302
336
  const report = { renamed: 0, modified: 0, added: 0, removed: 0, skipped: 0, noOps: false };
337
+ warnUnparsedRequirementHeadings(mainContent);
303
338
  let content = mainContent;
304
339
  content = applyRenamed(content, plan, changeName, report);
305
340
  content = applyModified(content, plan, changeName, today, report);
@@ -249,7 +249,10 @@ function parseYaml(content) {
249
249
  for (const line of content.split('\n')) {
250
250
  const trimmed = line.trim();
251
251
  if (!trimmed || trimmed.startsWith('#')) continue;
252
- const match = trimmed.match(/^(\w[\w_]*):\s*(.*)/);
252
+ // v0.55.0 §8.4.3 横展(FB-4):只额外容忍**全角冒号**(`state:executing`)。
253
+ // 不得收紧:`line.trim()` 已容忍缩进,保持现状(约束①:缩进在 YAML 是语义,
254
+ // 本层不新增缩进容忍,也不拿掉既有 trim);字段缺失不报错(约束②)。
255
+ const match = trimmed.match(/^(\w[\w_]*)[::]\s*(.*)/);
253
256
  if (match) {
254
257
  const val = match[2].trim();
255
258
  if (val === 'null' || val === '') {
@@ -27,6 +27,7 @@
27
27
  import { readFileSync, writeFileSync, existsSync, cpSync, mkdirSync, readdirSync } from 'node:fs';
28
28
  import { join, basename, relative, resolve } from 'node:path';
29
29
  import { execSync } from 'node:child_process';
30
+ import { slugify } from './slug.mjs';
30
31
 
31
32
  /**
32
33
  * 解析 CLI 参数数组为结构化对象
@@ -197,7 +198,9 @@ export function mergeBaselines(ledgerDir, changeName, moduleSections, candidateL
197
198
  const today = new Date().toISOString().slice(0, 10);
198
199
 
199
200
  for (const [moduleName, sectionContent] of Object.entries(moduleSections)) {
200
- const safeName = moduleName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
201
+ // 统一走 slug.mjs(保留 CJK)——原 `[^a-z0-9-]` 逐字符替换会把中文标题退化成纯短横线,
202
+ // 实证 emp-auth `baselines/--------------------.md`(2026-09-12)
203
+ const safeName = slugify(moduleName);
201
204
  const baselinePath = join(baselinesDir, `${safeName}.md`);
202
205
  const moduleCandidates = candidateLedger.filter(c =>
203
206
  c.candidate.toLowerCase().includes(moduleName.toLowerCase())
@@ -32,10 +32,31 @@ const EVIDENCE = [
32
32
  { kind: 'tailwind', re: /\b(?:bg|text|border|from|to|via|ring)-(?:slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(?:50|100|200|300|400|500|600|700|800|900|950)\b/g },
33
33
  ];
34
34
 
35
+ // ── Markdown 规范树证据机(v0.55.0,设计 §8.2.1)──
36
+ //
37
+ // 定位:**证据报告器,不做取值裁决**。P1.5 反方实证——真实规范树的 token 主形态是
38
+ // **Markdown 表格行**(`| \`--color-primary\` | \`#3A94DD\` |`),而上方的代码用证据机
39
+ // (`custom-prop`)要求行尾 `;` 或 `}` → 表格行**永不匹配**,只剩裸 hex 机(有值无名);
40
+ // 且按频次排序会选出**错的主色**(现场:`#1890ff` ×155 vs 正确值 `#3a94dd` ×62 的跨期矛盾)。
41
+ // 故这里只**如实呈现证据与冲突**(见下方的 conflicts 段),语义角色由人裁决——
42
+ // 与既有原则"语义角色由人定,不由 LLM 定"一致。
43
+ const DOC_EVIDENCE = [
44
+ // 表格行里的 token 名 + 值:| `--color-primary` | `#3A94DD` | ...
45
+ { kind: 'doc-table-token', re: /\|\s*`?(--[a-zA-Z][\w-]*)`?\s*\|\s*`?([^|`]+?)`?\s*(?=\|)/g, tokenPair: true },
46
+ // 键值行:- `--color-primary`: #3A94DD
47
+ { kind: 'doc-kv-token', re: /(--[a-zA-Z][\w-]*)\s*[::]\s*([^\n|]+)/g, tokenPair: true },
48
+ // 标题(结构证据:组件名 / 页面类型候选)
49
+ { kind: 'doc-heading', re: /^#{2,4}\s+(.+?)\s*$/g },
50
+ ];
51
+
52
+ /** "看起来是值"的形态白名单——用于过滤表格里与 token 名相邻的说明文字。 */
53
+ const VALUE_LIKE_RE = /^(#[0-9a-fA-F]{3,8}|var\(|rgba?\(|hsla?\(|cubic-bezier\(|\d+(\.\d+)?(px|rem|em|%|ms|s)?$|[a-z-]+\([^)]*\)$)/;
54
+
35
55
  // ── 参数 ──
36
56
 
37
57
  const argv = process.argv.slice(2);
38
58
  const REPORT_ONLY = argv.includes('--report');
59
+ const DOCS = argv.includes('--docs'); // v0.55.0:Markdown 规范树模式(create-from-docs)
39
60
  const outIdx = argv.indexOf('--out');
40
61
  const outArg = outIdx !== -1 ? argv[outIdx + 1] : null;
41
62
  const budgetIdx = argv.indexOf('--budget-ms');
@@ -44,7 +65,14 @@ const positional = argv.filter((a, i) => !a.startsWith('--') && !(outIdx !== -1
44
65
  const rootArg = positional[0];
45
66
 
46
67
  if (!rootArg) {
47
- console.error('Usage: node scripts/token-extract.mjs <代码目录> [--out <json 路径>] [--budget-ms 60000] [--report]');
68
+ console.error('Usage: node scripts/token-extract.mjs <目录> [--out <json 路径>] [--budget-ms 60000] [--report] [--docs]');
69
+ console.error(' --docs:Markdown 规范树模式(.md 纳入扫描;输出证据报告 + 冲突呈现,不做取值裁决)');
70
+ process.exit(1);
71
+ }
72
+ // v0.55.0 修正:原实现 `positional[0]` 静默丢弃其余源路径——多仓场景下用户以为处理了全部。
73
+ if (positional.length > 1) {
74
+ console.error(`❌ 收到 ${positional.length} 个源路径,但本工具一次只支持一个:${positional.join(' / ')}`);
75
+ console.error(' (多仓请分批调用,或先合并到一个目录)');
48
76
  process.exit(1);
49
77
  }
50
78
  const root = resolve(rootArg);
@@ -53,6 +81,10 @@ if (!existsSync(root) || !statSync(root).isDirectory()) {
53
81
  process.exit(1);
54
82
  }
55
83
 
84
+ // docs 模式:纳入 .md;并把 .team-flow 加入跳过(否则产物落进被扫描树内部 → 二次运行自吞)
85
+ if (DOCS) SCAN_EXTS.add('.md');
86
+ SKIP_DIRS.add('.team-flow');
87
+
56
88
  // ── 走树(迭代式,预算控制)──
57
89
 
58
90
  const started = Date.now();
@@ -99,6 +131,8 @@ function record(kind, value, file, line) {
99
131
  }
100
132
 
101
133
  let scanned = 0;
134
+ // docs 模式追加 Markdown 证据机(结构证据 + token 对),代码证据机同时保留
135
+ const ENGINES = DOCS ? [...EVIDENCE, ...DOC_EVIDENCE] : EVIDENCE;
102
136
  for (const file of files) {
103
137
  if (Date.now() - started > budgetMs) { skipped.push({ reason: 'budget-exceeded-during-scan' }); break; }
104
138
  let content;
@@ -108,14 +142,27 @@ for (const file of files) {
108
142
  // 逐行扫(拿行号)
109
143
  for (let i = 0; i < lines.length; i++) {
110
144
  const line = lines[i];
111
- for (const ev of EVIDENCE) {
145
+ for (const ev of ENGINES) {
112
146
  ev.re.lastIndex = 0;
113
147
  let m;
114
148
  while ((m = ev.re.exec(line)) !== null) {
115
- if (ev.captureValue) {
149
+ if (ev.tokenPair) {
150
+ // v0.55.0:Markdown 的 token 对(名=值),不做语义解释。
151
+ // **值形态过滤**:真实规范树的表格列序不固定(实测 3 种,含交错列),
152
+ // 与 token 名相邻的格未必是"值"——不过滤会把说明文字("不变"/"悬停态"/"—")
153
+ // 当成 token 值,实测制造 185 条噪音冲突。故只记"看起来是值"的形态,
154
+ // 其余存为位置证据(kind=doc-table-ctx,不参与冲突检测)。
155
+ const val = m[2].trim().replace(/[;;]\s*$/, '');
156
+ if (VALUE_LIKE_RE.test(val)) record(ev.kind, `${m[1]}=${val}`, file, i + 1);
157
+ else record('doc-table-ctx', `${m[1]}~${val}`, file, i + 1);
158
+ } else if (ev.captureValue) {
116
159
  const raw = m[1] !== undefined ? m[1] : m[0];
117
- // 多值(如 padding: 8px 16px)拆开逐项
118
- for (const part of raw.split(/[\s,]+/)) {
160
+ // 多值拆分**仅适用于空格分隔的短值**(如 `padding: 8px 16px`)。
161
+ // v0.55.0 修正:含括号/逗号的值(`rgba(...)` / `calc(...)` / `color-mix(...)`)
162
+ // 是**单个值**,拆分会产生 `rgba(0` / `0.05)` 这类碎片——实测把
163
+ // `--color-bg-hover: rgba(0, 0, 0, 0.05)` 记成 3 个不同值,制造假冲突。
164
+ const parts = /[(),]/.test(raw) ? [raw] : raw.split(/[\s,]+/);
165
+ for (const part of parts) {
119
166
  const t = part.trim();
120
167
  if (!t || t === '0' || t === 'auto' || t === 'inherit' || t === 'initial') continue;
121
168
  if (ev.kind === 'custom-prop') {
@@ -226,9 +273,36 @@ const spacingCount = sourceTokens.tokens.spacing.length;
226
273
  const radiusCount = sourceTokens.tokens.radius.length;
227
274
  const fontCount = sourceTokens.tokens.font.length;
228
275
 
276
+ // ── 冲突呈现(v0.55.0,docs 模式)──
277
+ // 同一 token 名出现多个不同值 → 显式并列。这不是"工具推断哪个对",而是把**跨期/跨源矛盾**
278
+ // 摊开给人工裁决(现场实测:`primary` 同时有 `#3A94DD` ×62 与 `#1890ff` ×155 两套,
279
+ // 两期各自自洽、互未发现)。
280
+ const conflicts = [];
281
+ if (DOCS) {
282
+ const byName = new Map();
283
+ for (const e of evidence.values()) {
284
+ const idx = e.value.indexOf('=');
285
+ if (idx <= 0) continue;
286
+ const name = e.value.slice(0, idx);
287
+ const val = e.value.slice(idx + 1);
288
+ if (!byName.has(name)) byName.set(name, new Map());
289
+ const vm = byName.get(name);
290
+ vm.set(val, (vm.get(val) || 0) + e.count);
291
+ }
292
+ for (const [name, vm] of byName) {
293
+ if (vm.size > 1) {
294
+ conflicts.push({
295
+ token: name,
296
+ values: [...vm.entries()].sort((a, b) => b[1] - a[1]).map(([value, count]) => ({ value, count })),
297
+ });
298
+ }
299
+ }
300
+ conflicts.sort((a, b) => b.values[0].count - a.values[0].count);
301
+ }
302
+
229
303
  // 统计报告(stdout)
230
304
  const report = [];
231
- report.push('=== Token 提取报告(确定性,零 LLM)===');
305
+ report.push(`=== Token 提取报告(确定性,零 LLM${DOCS ? ' · Markdown 规范树模式' : ''})===`);
232
306
  report.push(`根目录:${root}`);
233
307
  report.push(`发现文件:${files.length} | 已扫描:${scanned}${skipped.length ? ` | 跳过:${skipped.length}` : ''}`);
234
308
  report.push('');
@@ -241,6 +315,15 @@ report.push(`间距:${spacingCount} 个聚类 | 圆角:${radiusCount} 个 |
241
315
  for (const c of sourceTokens.tokens.spacing.slice(0, 5)) report.push(` spacing ${c.value} ×${c.count}`);
242
316
  for (const c of sourceTokens.tokens.radius.slice(0, 4)) report.push(` radius ${c.value} ×${c.count}`);
243
317
  report.push('');
318
+ if (DOCS) {
319
+ report.push('');
320
+ report.push(`⚠️ 冲突呈现:${conflicts.length} 个 token 名存在多值(**跨期/跨源矛盾,需人工裁决**)`);
321
+ for (const c of conflicts.slice(0, 6)) {
322
+ report.push(` ${c.token}: ${c.values.map(v => `${v.value} ×${v.count}`).join(' | ')}`);
323
+ }
324
+ if (conflicts.length === 0) report.push(' (无冲突)');
325
+ }
326
+ report.push('');
244
327
  report.push('⚠️ 以上仅为「频次 + 位置」证据——语义角色(哪个是 primary / border / font-display)**需要人工策展指定**,');
245
328
  report.push(' 本工具不做自动推断(设计 §4.1.6:避免"垃圾设计系统 + 满分审计")。');
246
329
  report.push('');
@@ -250,8 +333,17 @@ report.push(' 由 design-system skill 的 create-from-code 流程补齐 A1/A2/
250
333
  console.log(report.join('\n'));
251
334
 
252
335
  if (!REPORT_ONLY) {
253
- const outPath = outArg ? resolve(outArg) : join(root, '.team-flow', 'token-extract', 'source-tokens.json');
336
+ // v0.55.0:docs 模式默认落**调用方工作目录**(workspace 根)。若沿用 `join(root, …)`,
337
+ // 而 root 恰是被扫描的规范树 → 产物落进被扫描树内部 → 二次运行自吞(实测 ×1 → ×3)。
338
+ // 同时 `.team-flow` 已加入 SKIP_DIRS 兜底。
339
+ const outPath = outArg
340
+ ? resolve(outArg)
341
+ : DOCS
342
+ ? join(process.cwd(), '.team-flow', 'token-extract', 'source-docs.json')
343
+ : join(root, '.team-flow', 'token-extract', 'source-tokens.json');
254
344
  mkdirSync(dirname(outPath), { recursive: true });
255
- writeFileSync(outPath, JSON.stringify(sourceTokens, null, 2), 'utf-8');
256
- console.log(`\nsource-tokens.json 已写入:${outPath}`);
345
+ const payload = { ...sourceTokens, mode: DOCS ? 'docs' : 'code' };
346
+ if (DOCS) payload.conflicts = conflicts;
347
+ writeFileSync(outPath, JSON.stringify(payload, null, 2), 'utf-8');
348
+ console.log(`\n${DOCS ? 'source-docs.json' : 'source-tokens.json'} 已写入:${outPath}`);
257
349
  }
@@ -5,8 +5,9 @@
5
5
  ```
6
6
  docs/solutions/
7
7
  ├── INDEX.md # L1:轻量索引(≤150行,每条一行摘要+标签)
8
- ├── requirement/ # 按阶段分目录
8
+ ├── prd/ # 按阶段分目录(枚举权威:scripts/lib/solutions-phases.mjs)
9
9
  ├── plan/
10
+ ├── architecture/
10
11
  ├── prototype/
11
12
  ├── spec/
12
13
  ├── build/
@@ -14,6 +15,9 @@ docs/solutions/
14
15
  └── cross-phase/ # 跨阶段通用经验
15
16
  ```
16
17
 
18
+ > 注意:目录树中**只有上述 phase 目录**由 `tf solutions` CLI 通道维护;`workflow-issues/`、
19
+ > `build-errors/` 等 category 目录由 `/ce-compound` 通道写入,两套词表互不兼容。
20
+
17
21
  ## INDEX.md 格式
18
22
 
19
23
  ```markdown
@@ -29,7 +33,7 @@ docs/solutions/
29
33
 
30
34
  ```yaml
31
35
  ---
32
- phase: prd # 阶段标签:prd | plan | prototype | spec | build | review | cross-phase
36
+ phase: prd # 阶段标签:prd | plan | architecture | prototype | spec | build | review | cross-phase
33
37
  domain: auth # 领域标签(与 PRD/change 的领域对应)
34
38
  type: pitfall # pitfall | pattern | decision | insight
35
39
  severity: high # critical | high | medium | low(序定义于 scripts/lib/severity.mjs)
@@ -2,8 +2,9 @@
2
2
  name: design-system
3
3
  description: >-
4
4
  设计系统独立创建与维护 skill,产出落 .team-flow/design-system/(base 品牌共享层 +
5
- B端/C端变体 + primer + 预览画廊)。三条创建入口:交互式(LLM 推荐+用户确认)、
6
- 从内置模板库导入(registry.json 8 个参考)、从既有代码逆向建库(create-from-code)。
5
+ B端/C端变体 + primer + 预览画廊)。六条创建入口:从已有项目移植(clone)、
6
+ 从内置模板库导入、通用起点(--profile antd)、从既有规范文档导入(create-from-docs)、
7
+ 从既有代码逆向建库(create-from-code)、交互式从零创建;已有设计系统走 iterate。
7
8
  支持独立调用或 prototype skill 内部编排调用。当用户需要创建设计系统、迭代设计系统、
8
9
  刷新 primer 组件白名单、或原型流程发现设计系统缺失时使用。
9
10
  不适用于:原型绘制(用 prototype)、纯架构设计(用 architecture-design)。
@@ -24,14 +25,29 @@ Do NOT invoke for:
24
25
 
25
26
  ## 交互创建流程(6 步)
26
27
 
27
- ### Step 0: 起点选择(v0.54.0)
28
-
29
- 若 `.team-flow/design-system/` 不存在(无设计系统),先询问起点:
30
-
31
- - **从模板库选择**:展示 `${CLAUDE_PLUGIN_ROOT}/templates/design-systems/registry.json`(8 个参考:linear-app / stripe / vercel / supabase / sentry / posthog / notion / claude)→ 用户选定后运行 `node ${CLAUDE_PLUGIN_ROOT}/scripts/design-system-import.mjs <reference.md> --out .team-flow/design-system/base.md` 转换 → **续跑 Step 5 评审 → Step 6 落盘**(落盘动作含 primer 生成 + guard 校验;primer 缺失会让 prototype Step 0 对 `contract: v1` 系统 blocked)
32
- - **逆向建库**:走 **create-from-code** 模式(见下方「逆向建库模式」)——从既有代码提取
33
- - **从零创建**:直接进 Step 1(交互式 6 步)
34
- - **已有设计系统** → 走 iterate 模式
28
+ ### Step 0: 起点选择(v0.55.0:7 条,全部展示)
29
+
30
+ 若 `.team-flow/design-system/` 不存在(无设计系统),先询问起点。
31
+ **每条都要展示,并带"适用场景"一句话**——能力存在但用户不知道 = 能力不存在;
32
+ 按**资产就绪度降序**排列(从最厚的已有资产到最薄的空白起点):
33
+
34
+ | # | 选项 | 适用场景(对用户的一句话) | 动作 |
35
+ |---|------|---------------------------|------|
36
+ | 1 | **移植已有设计系统** | "你们公司**另一个后台项目**已经建过设计系统 → 直接复制过来改" | `clone --from <源路径> --to <目标路径>`(**两参数均必填**,流程见 `references/creation-modes.md` §1) |
37
+ | 2 | **从模板库选择** | "想参考某个成熟产品(Linear / Stripe / Vercel…)的视觉风格" | 展示 `${CLAUDE_PLUGIN_ROOT}/templates/design-systems/registry.json`(8 个参考)→ `node ${CLAUDE_PLUGIN_ROOT}/scripts/design-system-import.mjs <reference.md> --out .team-flow/design-system/base.md` → **续跑 Step 5 评审 → Step 6 落盘** |
38
+ | 3 | **通用起点(Ant Design 规格)** | "全新项目、**没有任何规范**,先要一套像 Ant Design 的合规底座" | `node ${CLAUDE_PLUGIN_ROOT}/scripts/design-system-import.mjs --profile antd --out .team-flow/design-system/base.md` → 续跑 Step 5 → Step 6 |
39
+ | 4 | **从文档规范导入** | "手里有**一套写好的 UI 规范**(Markdown / 规范树)" | `create-from-docs`(流程见 `references/creation-modes.md` §4) |
40
+ | 5 | **从代码逆向建库** | "项目里**已有符合规范的原型代码**" | `create-from-code`(见下方「逆向建库模式」) |
41
+ | 6 | **从零交互创建** | "什么都没有,边聊边定" | 直接进 Step 1(交互式 6 步) |
42
+ | 7 | 已有设计系统 → **iterate** | (非新建) | 见下方「迭代模式」 |
43
+
44
+ > **入口同步(两处声明 + 一处事实传达,v0.55.0 校正)**:
45
+ > ① 本文件 Step 0 —— **无条件展示全部 7 条**(这是模式可达性的**唯一保证**);
46
+ > ② `workflow-bootstrap` B4.6 —— 六项 + 跳过,逐行带适用场景与委托参数;
47
+ > ③ prototype 内部编排(`orchestration-flow.md` §①b)—— 只传达 scout 简报中的**资产事实**(如"已有符合规范的原型代码"),**不预选起点**。
48
+ >
49
+ > ③ 不做 mode 预选是**有意设计**:prototype 无从判断用户资产状况,预选会让其余 6 条不可见。
50
+ > 故**新模式可达性由 ① 的无条件展示保证,不依赖各入口的正确预选**——任何入口下用户都能看到全 7 条。
35
51
 
36
52
  > B 类参考(如 linear-app)转换为**自动提取配色**,须提示用户人工核对(转换器会输出该警告)。
37
53
 
@@ -88,29 +104,31 @@ Do NOT invoke for:
88
104
 
89
105
  ## 迭代模式(iterate)
90
106
  已有 design-system → 读取 → 合并增量 → 变更履历 → 预览 → 确认 → 写入。
91
- 增量来源:① 原型阶段确认的 `ds_increment`(⑥ 路由)② **pending.md 累积待办** ③ change closing 二级确认项。
107
+ 增量来源:① 原型阶段确认的 `ds_increment`(⑥ 路由)② **pending.md 累积待办** ③ change closing 二级确认项 ④ **页面规范增量**(v0.55.0:用户提供项目页面规范,或更新既有 `页面范式来源` 声明与页面类型表;用户不提供时**默认补写** `页面范式来源:引用内置`)。
92
108
  落盘后重新生成 primer(`${CLAUDE_PLUGIN_ROOT}/scripts/gen-primer.mjs`)+ 跑 guard;涉及 token/契约变更时**可选**重新生成 showcase(用户可跳过)。
93
109
 
94
- ## 逆向建库模式(create-from-code,v0.54.0)
95
-
96
- **场景**:企业已有符合规范的原型代码 → 基于它建立设计系统。**确定性提取 + 人工策展**(不做自动语义推断——避免"垃圾设计系统 + 满分审计")。
97
-
98
- **入口**:`/team-flow:design-system create-from-code --source <代码目录>`(多仓库可传多值或指向 repo_layout 清单)
110
+ ## 逆向建库模式(create-from-code)
99
111
 
100
- **流程(5 步)**:
101
- 1. **提取**:`node ${CLAUDE_PLUGIN_ROOT}/scripts/token-extract.mjs <目录>` → `.team-flow/token-extract/source-tokens.json` + 统计报告(频次 + 位置证据,零 LLM)
102
- 2. **呈现报告**:向用户展示提取统计("47 个文件、23 个颜色、8 个间距值,高频 Top10 …")
103
- 3. **候选稿**:从高频值生成候选 token 表(标注"候选")
104
- 4. **人工策展**(多轮 AskUserQuestion):用户指定 primary / 中性色 / 语义色 / 字体栈(每项有频次+位置证据可参考)→ skill 按确定性规则补齐 A1/A2/B-slot + palette 阶梯
105
- 5. **落盘**:base.md + 变体 + primer + preview(+ 可选 showcase)→ guard 六层审计
112
+ **场景**:企业已有符合规范的原型代码 → 基于它建立设计系统。**确定性提取 + 人工策展**(不做自动语义推断——避免"垃圾设计系统 + 满分审计")。入口(**单目录**)与完整 5 步流程见 `references/creation-modes.md` §6。
106
113
 
107
- **关键约束**:绝不静默发明(候选值标注 `sources[]` 证据);**语义角色由人定,不由 LLM 定**;目标已有 base.md 时走 iterate 合并(需用户确认)。
114
+ > **三种新模式(v0.55.0)**——移植(clone)/ 文档导入(create-from-docs)/ 通用起点(`--profile antd`)的完整命令与流程,同样见 `references/creation-modes.md`。Step 0 表格已列出全部 7 条起点及各自适用场景。
108
115
 
109
116
  ## 迁移兼容
110
117
  检测旧 `prototype/design-system.md` → 提示迁移到 `.team-flow/design-system/base.md`(一次性)。
118
+ 迁移属**导入类创建** → 须补 `来源与裁决记录` 段、`contract` 默认 `legacy`。
119
+
120
+ ## 异常处理(v0.55.0)
121
+
122
+ - **源系统不可读 / `base.md` 缺失**(clone):脚本 exit 1,提示 `--from` 的两种语义(项目根自动定位 `.team-flow/design-system`,或直接给设计系统目录)
123
+ - **目标已有设计系统**(clone):exit 1 并引导走 **iterate**(MERGE 不 OVERWRITE)——不覆盖
124
+ - **primer 缺失或过期**:`gen-primer --check` **exit 2** → prototype Step 0 对 `contract: v1` 系统 **blocked**、对 `legacy` 系统 WARN 放行 → 提示用户重跑生成或 iterate
125
+ - **guard 硬校验失败**:exit 1 阻断落盘 → 按输出逐项修复(9 段 / palette / A1 / A2 公式 / B-slot)
126
+ - **用户中途放弃**:草案阶段产物在 scratch(`/tmp/ds-draft-<slug>/`),放弃确认时**整体丢弃**,不写任何正式路径(两阶段落盘 `confirmed:false → confirmed:true` 保证)
127
+ - **配置漂移**:`check-project-config.mjs` 输出 ⚠️ 但 **exit 0**(提示不阻断)
111
128
 
112
129
  ## 执行引擎
113
130
  内部子代理 `references/agents/design-system-architect.md`(唯一写者,两阶段落盘:confirmed:false 草案 → 人工确认 → confirmed:true 写入)。
131
+ **写者范围(v0.55.0)**:含 `base.md` 与**端变体的 `layout` 段**(页面范式声明 + 容器骨架块)。
114
132
 
115
133
  ## 配置
116
134
  ```json
@@ -119,3 +137,8 @@ Do NOT invoke for:
119
137
  "prototype.designSystemBase": ".team-flow/design-system/base.md"
120
138
  }
121
139
  ```
140
+
141
+ > **配置漂移检查(v0.55.0)**:`node ${CLAUDE_PLUGIN_ROOT}/scripts/check-project-config.mjs [项目根]` ——
142
+ > 检查配置指向的路径是否存在、`version` 是否与插件一致。现场实测三类漂移(version 落后 /
143
+ > 指向不存在的文件 / 零消费者键)**全部静默**,只会在用到时失效。检查面按"实际被消费"分级,
144
+ > 零消费者键只提示不报错。
@@ -1,11 +1,11 @@
1
- You are a Design System Architect. You are dispatched by the `design-system` skill to **create or iterate** the project-level `.team-flow/design-system/base.md` — the single source of truth for design tokens, components, and anti-patterns (compounded back via prototype-sync). The orchestrating design-system skill **never writes `base.md` itself — you are the sole writer**. You run in two phases: first produce a **draft** (`confirmed: false`) for orchestrator review + human confirmation; then, re-dispatched with `confirmed: true`, **you yourself write the official path**. The orchestrator only reviews, confirms, and re-dispatches — it does not Write/Edit `base.md`.
1
+ You are a Design System Architect. You are dispatched by the `design-system` skill to **create or iterate** the project-level `.team-flow/design-system/base.md` — the single source of truth for design tokens, components, and anti-patterns (compounded back via prototype-sync). The orchestrating design-system skill **never writes `base.md` itself — you are the sole writer**(v0.55.0 起写者范围另含**端变体的 `layout` 段**)。 You run in two phases: first produce a **draft** (`confirmed: false`) for orchestrator review + human confirmation; then, re-dispatched with `confirmed: true`, **you yourself write the official path**. The orchestrator only reviews, confirms, and re-dispatches — it does not Write/Edit `base.md`.
2
2
 
3
3
  ### 文件结构:base + variant
4
4
 
5
5
  architect 写入的文件采用 **base + variant** 结构:
6
6
  - **`.team-flow/design-system/base.md`**:基础设计系统(全量 token + 组件规范 + anti-patterns),是所有 variant 的公共底座。
7
7
  - **`.team-flow/design-system/variants/<name>.md`**(可选):品牌/主题变体文件,只覆写差异 token(如暗色主题、子品牌色板),继承 base 未覆写部分。
8
- - architect 在 `confirmed: true` 阶段写入 base.md;如需 variant,同时写入对应 variant 文件。
8
+ - architect 在 `confirmed: true` 阶段写入 base.md;如需 variant,同时写入对应 variant 文件。**v0.55.0 起另含端变体(`b-end.md` / `c-end.md`)的 `layout` 段**(页面范式声明 + 容器骨架块,见下方「端变体 `layout` 段写者」)。
9
9
 
10
10
  The plugin stays **generic**: do NOT hardcode any specific company/product brand. Derive tokens deterministically from the project's stated tone/brand input; when none is given, fall back to a neutral default and record the assumption.
11
11
 
@@ -15,7 +15,7 @@ The dispatch prompt provides:
15
15
  - `mode: create | iterate`.
16
16
  - `confirmed: false | true`(v0.15.0 两阶段落盘):
17
17
  - `false`(默认,草案阶段):只产草案(写 scratch 草稿路径或返回 response),**不写正式 `.team-flow/design-system/base.md`**。
18
- - `true`(人工已确认):把已确认的草案**写入正式 `.team-flow/design-system/base.md`**(你是唯一写者)。
18
+ - `true`(人工已确认):把已确认的草案**写入正式 `.team-flow/design-system/base.md`**(你是唯一写者);若本次产出 / 更新端变体的 `layout` 段(页面范式声明 + 容器骨架块),同批写入。
19
19
  - `design_system_path`: 正式目标路径(`confirmed: true` 时写入此处,默认 `.team-flow/design-system/base.md`)。
20
20
  - `draft`(`confirmed: true` 时传入):上一阶段已确认的草案内容/路径。
21
21
  - `create` inputs (as available): project tone/brand hints, PRD path (to read product domain & voice), existing scaffold `.team-flow/design-system/base.md`.
@@ -37,7 +37,8 @@ Every `.team-flow/design-system/base.md` follows this schema (9 sections + a 5-d
37
37
  | `brand` | logo / 品牌主色 |
38
38
  | `anti-patterns` | 禁止内联样式漂移 / 禁止非 token 颜色 |
39
39
  | `principles`(v0.54.0) | **设计原则 ≥3 条**(随 mood 预填,用户可调整) |
40
- | `governance`(v0.54.0) | `contract: v1`(新建固定 v1;存量升级时由 iterate 置 v1)+ version + 负责人 + 弃用策略 + changelog |
40
+ | `governance`(v0.54.0) | `contract: v1`(**新建默认 v1;来源 ∈ {`create-from-docs` / `create-from-code` / 转换器产物 / 旧文件迁移} 时写 `legacy`**,v0.55.0 设计 §8.2.1 D-18;存量升级时由 iterate 置 v1)+ version + 负责人 + 弃用策略 + changelog |
41
+ | `来源与裁决记录`(v0.55.0) | **导入类创建条件必填**(同上四类来源):原始来源 / 导入方式 / 「为什么不是直接采纳」/ 主色裁决 / 待清理项——审计层 advisory,不进 `REQUIRED_SECTIONS` |
41
42
  | `palette`(5 方向确定性调色板) | neutral / primary / success / warning / danger,各含 50–900 阶梯 |
42
43
  | `aliases`(B-slot 别名层,v0.18.0) | `--fg-2 → var(--fg)` / `--meta → var(--muted)` / `--border-soft → var(--border)` / `--surface-warm → var(--surface)`——组件引用 B-slot 永远可解析 |
43
44
  | `extensions`(C-extension 待提升清单,v0.18.0) | 品牌专有 token 名单制;提升路径:C→B(≥2 品牌需要)→A2(有全局默认值) |
@@ -99,12 +100,13 @@ When the project has no `.team-flow/design-system/base.md`:
99
100
  - **C-extension 清单**(v0.18.0):如有品牌专有 token,列入 `extensions` 段(名单制),标注提升路径。
100
101
  - **组件契约表(v0.54.0)**:按上方 20 类基线产出**至少 10 类**(`| 组件 | 类型 | variants | sizes | states | 用途 | 禁止 |`),全部 token-bound;类型列必须填写(交互/轻量/豁免)。
101
102
  - **principles 段(v0.54.0)**:≥3 条,随 mood 预填(如 professional_minimal → "一致性优先于局部创意 / 清晰优于装饰 / 可访问性默认开启")。
102
- - **governance 段(v0.54.0)**:`contract: v1`(新建固定 v1)+ version + 负责人 + 弃用策略 + changelog。
103
+ - **governance 段(v0.54.0;v0.55.0 改)**:`contract: v1`(**新建默认 v1**;**来源 ∈ {`create-from-docs`, `create-from-code`, 转换器产物, 旧文件迁移}(导入类)时写 `legacy`**——导入的既有资产未经校准,恒打 `v1` 会一落盘即 blocked)+ version + 负责人 + 弃用策略 + changelog。
104
+ - **`来源与裁决记录` 段(v0.55.0)**:来源属上列导入类时**条件必填**(原始来源 / 导入方式 / 「为什么不是直接采纳」/ 主色裁决 / 待清理项);绿地创建可选,**不进 `REQUIRED_SECTIONS`**。
103
105
  - **a11y 声明行(v0.54.0)**:文档头部 `> a11y: WCAG 2.2 AA(对比度 4.5:1 / 大字 3:1 / 焦点可见 / 键盘可达)`。
104
106
  - Fill color / typography / spacing / layout / motion / voice / brand / anti-patterns consistently with the palette.
105
107
  - `anti-patterns` MUST include "禁止内联样式漂移" and "禁止非 token 颜色".
106
108
  3. **`confirmed: false`**:Deliver the draft(写 scratch 草稿路径或返回 response),**不写正式路径**。Orchestrator 评审 + 人工确认后,**带 `confirmed: true` 重新派发你**,由你写入正式 `.team-flow/design-system/base.md`(主代理不写)。
107
- 4. **`confirmed: true`**:把传入的已确认草案 `Write` 到正式 `.team-flow/design-system/base.md`,然后执行 **预览生成步骤**,返回 `status: done` + deliverable = 正式路径。
109
+ 4. **`confirmed: true`**:把传入的已确认草案 `Write` 到正式 `.team-flow/design-system/base.md`;若本次产出端变体(`b-end.md` / `c-end.md`),同批写入其 **`layout` 段**(页面范式声明 + 容器骨架块,见下方「端变体 `layout` 段写者」),然后执行 **预览生成步骤**,返回 `status: done` + deliverable = 正式路径。
108
110
 
109
111
  #### 预览生成步骤(confirmed: true 后自动执行)
110
112
 
@@ -116,16 +118,27 @@ When the project has no `.team-flow/design-system/base.md`:
116
118
 
117
119
  ### Mode: iterate(增量更新)
118
120
 
119
- When prototype-sync (or a change) introduces new components / tokens / anti-patterns:
121
+ When prototype-sync (or a change) introduces new components / tokens / anti-patterns / **page-pattern specs(页面规范增量,v0.55.0)**:
120
122
 
121
123
  1. Read the existing `.team-flow/design-system/base.md`.
122
124
  2. **Merge** (not overwrite) the incoming increments: new components into the `components` 契约表(必须指定类型列), new tokens into the relevant section + palette, new anti-patterns into `anti-patterns`.
123
125
  - **contract 升级(v0.54.0)**:若原系统为 `legacy`/无标记,且本次补全了契约表(≥10 类)→ 将 `contract` 置为 `v1`(并记 changelog:来源"本 change 增量回流")。
124
126
  - 变体文件的 components 段只写**端特有差异**并引用契约表(不重复定义组件清单)。
125
- 3. Keep token consistency — a new component must reference existing tokens; if it needs a new token, add the token to the palette/section too (no orphan tokens).
126
- 4. Append a **变更履历** entry: 时间 / 变更内容 / 来源 change-id.
127
- 5. **`confirmed: false`**:Deliver the merged draft(草稿路径或 response)for orchestrator review + human confirmation,**不写正式路径**。
128
- 6. **`confirmed: true`**:把已确认的合并草案 `Write` 到正式 `.team-flow/design-system/base.md`(你是唯一写者),然后执行**预览生成步骤**(同上)。
127
+ 3. **页面规范增量(v0.55.0 第四类增量)**:用户提供项目页面规范,或要更新既有 `页面范式来源` 声明 / 页面类型表时 → 写对应端变体的 `layout` 段(`### 容器骨架` 块 + `### 页面范式` 子块)。**用户不提供规范时默认补写 `页面范式来源:引用内置` 一行**,使 guard L3 的 ⚠️ 收敛为 ✅(绿地默认;已知属导入类棕地时改按 `项目自有` + 容器骨架块写——见 `variant-schema.md` 三态语义)。
128
+ 4. Keep token consistency — a new component must reference existing tokens; if it needs a new token, add the token to the palette/section too (no orphan tokens).
129
+ 5. Append a **变更履历** entry: 时间 / 变更内容 / 来源 change-id.
130
+ 6. **`confirmed: false`**:Deliver the merged draft(草稿路径或 response)for orchestrator review + human confirmation,**不写正式路径**。
131
+ 7. **`confirmed: true`**:把已确认的合并草案 `Write` 到正式 `.team-flow/design-system/base.md`(你是唯一写者);若本次含页面规范增量,同批 `Write`/`Edit` 对应端变体的 `layout` 段,然后执行**预览生成步骤**(同上)。
132
+
133
+ #### 端变体 `layout` 段写者(v0.55.0 扩面,设计 §8.2.2)
134
+
135
+ **写者范围由「只写 `base.md`」扩为「`base.md` + 端变体的 `layout` 段」**——页面范式属端变体,而 architect 此前只写 base,导致该声明**无写者**、guard 的 ⚠️ 无从收敛。
136
+
137
+ - **可写**:端变体(`b-end.md` / `c-end.md`)的 `layout` 段:
138
+ - `### 容器骨架` 块——表格 `| 容器 | 类名 | 关键 CSS 声明 |`(builder 的物料来源)
139
+ - `### 页面范式` 子块——`**页面范式来源**:…` 声明 + 页面类型表
140
+ - **边界**:本次扩面**只涉及 `layout` 段**,其余段落维持既有分工,不借机扩张写面;`variants/<name>.md`(暗色等主题变体)不属本扩面。
141
+ - **取值**(三态语义见 `variant-schema.md`):`项目自有`(棕地导入,须附容器骨架块 + 页面类型表 ≥3 行)/ `同 <端>`(指向另一端)/ `引用内置`(绿地默认,只写声明)。
129
142
 
130
143
  ## Output Format
131
144
 
@@ -150,7 +163,7 @@ When prototype-sync (or a change) introduces new components / tokens / anti-patt
150
163
  ## Tool Guidance
151
164
 
152
165
  - Use `Read` for the existing base.md / PRD / scaffold; `Glob` to scan existing `components/` and `variants/`.
153
- - **`Write` 权限按 `confirmed` 阶段使用**:`confirmed: false` → 只写 scratch 草稿路径(或仅返回 response),**绝不写正式 `.team-flow/design-system/base.md`**;`confirmed: true` → 写正式路径 + 生成 preview.html(你是唯一写者,主代理不写)。
166
+ - **`Write` 权限按 `confirmed` 阶段使用**:`confirmed: false` → 只写 scratch 草稿路径(或仅返回 response),**绝不写正式 `.team-flow/design-system/base.md`**;`confirmed: true` → 写正式路径(含端变体 `layout` 段)+ 生成 preview.html(你是唯一写者,主代理不写)。
154
167
  - Keep everything token-based and offline-friendly (产物必须零外部依赖).
155
168
 
156
169
  ## Red Lines