@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,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// gen-primer.mjs — 从设计系统确定性生成 primer.md(v0.55.0,设计 §4.1.2 / §8.2.2)
|
|
3
|
+
//
|
|
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 的段),项目既有页面规范无法进入流程。
|
|
13
|
+
//
|
|
14
|
+
// Usage:
|
|
15
|
+
// node scripts/gen-primer.mjs <base.md path> [--out <primer path>]
|
|
16
|
+
// 生成 primer(默认写 base.md 同目录 primer.md);同目录端变体自动参与
|
|
17
|
+
// node scripts/gen-primer.mjs <base.md path> --check
|
|
18
|
+
// 校验同目录 primer.md 的 digest 与"base + 变体"是否一致
|
|
19
|
+
// exit 0 = 一致;exit 2 = 过期或缺失(供 builder gate 判定)
|
|
20
|
+
//
|
|
21
|
+
// 配套测试:"生成物 == 源表"(tests/lib/gen-primer.test.mjs)
|
|
22
|
+
|
|
23
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
24
|
+
import { createHash } from 'node:crypto';
|
|
25
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
26
|
+
import {
|
|
27
|
+
parseComponentsTable,
|
|
28
|
+
parseContract,
|
|
29
|
+
parseA1Tokens,
|
|
30
|
+
parseAntiPatterns,
|
|
31
|
+
parsePageArchetype,
|
|
32
|
+
subsectionBodyRaw,
|
|
33
|
+
COMPONENT_COUNT_PASS,
|
|
34
|
+
COMPONENT_COUNT_WARN,
|
|
35
|
+
} from './lib/ds-parse.mjs';
|
|
36
|
+
import { resolveInputs, readFileOrNull } from './lib/ds-inputs.mjs';
|
|
37
|
+
|
|
38
|
+
const argv = process.argv.slice(2);
|
|
39
|
+
const CHECK = argv.includes('--check');
|
|
40
|
+
const outIdx = argv.indexOf('--out');
|
|
41
|
+
const outArg = outIdx !== -1 ? argv[outIdx + 1] : null;
|
|
42
|
+
const positional = argv.filter((a, i) => !a.startsWith('--') && !(outIdx !== -1 && i === outIdx + 1));
|
|
43
|
+
const basePath = positional[0];
|
|
44
|
+
|
|
45
|
+
if (!basePath) {
|
|
46
|
+
console.error('Usage: node scripts/gen-primer.mjs <base.md path> [--out <primer path>] [--check]');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const baseAbs = resolve(basePath);
|
|
51
|
+
if (!existsSync(baseAbs)) {
|
|
52
|
+
console.error(`base.md not found: ${baseAbs}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const baseContent = readFileSync(baseAbs, 'utf-8');
|
|
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');
|
|
66
|
+
const digestShort = digest.slice(0, 16);
|
|
67
|
+
const primerPath = outArg ? resolve(outArg) : join(dirname(baseAbs), 'primer.md');
|
|
68
|
+
const DIGEST_RE = /sha256:([0-9a-f]{16,})/;
|
|
69
|
+
const sourceLabel = ['base.md', ...variants.map(v => basename(v.file))].join(' + ');
|
|
70
|
+
|
|
71
|
+
// ── --check 模式:校验 primer 新鲜度(builder gate 调用)──
|
|
72
|
+
|
|
73
|
+
if (CHECK) {
|
|
74
|
+
if (!existsSync(primerPath)) {
|
|
75
|
+
console.error(`STALE: primer.md 不存在(${primerPath})——请运行 design-system iterate 生成`);
|
|
76
|
+
process.exit(2);
|
|
77
|
+
}
|
|
78
|
+
const primerContent = readFileSync(primerPath, 'utf-8');
|
|
79
|
+
const m = primerContent.match(DIGEST_RE);
|
|
80
|
+
if (!m) {
|
|
81
|
+
console.error('STALE: primer.md 无 digest 头部(格式不符)——请重新生成');
|
|
82
|
+
process.exit(2);
|
|
83
|
+
}
|
|
84
|
+
if (m[1] !== digestShort && !digest.startsWith(m[1])) {
|
|
85
|
+
console.error(`STALE: primer.md 已过期(记录 ${m[1]},当前 ${sourceLabel} ${digestShort})——请运行 design-system iterate 重新生成`);
|
|
86
|
+
process.exit(2);
|
|
87
|
+
}
|
|
88
|
+
console.log(`OK: primer.md digest 一致(${digestShort})`);
|
|
89
|
+
process.exit(0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── 生成模式 ──
|
|
93
|
+
|
|
94
|
+
const table = parseComponentsTable(baseContent);
|
|
95
|
+
if (!table) {
|
|
96
|
+
console.error('base.md 的 components 段未检测到组件契约表(需 | 组件 | 类型 | variants | sizes | states | 用途 | 禁止 | 表格)。');
|
|
97
|
+
console.error('存量系统请先运行 design-system iterate 补全契约表。');
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const contract = parseContract(baseContent);
|
|
102
|
+
const a1 = parseA1Tokens(baseContent);
|
|
103
|
+
const antiPatterns = parseAntiPatterns(baseContent);
|
|
104
|
+
const componentRows = table.rows;
|
|
105
|
+
const count = componentRows.length;
|
|
106
|
+
const countLabel = count >= COMPONENT_COUNT_PASS
|
|
107
|
+
? 'PASS(完整)'
|
|
108
|
+
: count >= COMPONENT_COUNT_WARN
|
|
109
|
+
? 'WARN(可用,建议补全)'
|
|
110
|
+
: 'FAIL(低于起步线)';
|
|
111
|
+
|
|
112
|
+
// 白名单行:Name(variants · sizes)
|
|
113
|
+
const whitelistLines = componentRows.map(r => {
|
|
114
|
+
const variants_ = r.hasVariants ? r.variants.replace(/\s*\/\s*/g, '|') : '—';
|
|
115
|
+
const sizes = r.sizes && r.sizes !== '—' ? r.sizes.replace(/\s*\/\s*/g, '|') : '—';
|
|
116
|
+
const typeMark = r.type === '豁免' ? '(展示类)' : r.type === '轻量' ? '(轻量)' : '';
|
|
117
|
+
return `- ${r.name}${typeMark}: variants=${variants_} · sizes=${sizes}`;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Token 速查行
|
|
121
|
+
const tokenKeys = ['--bg', '--surface', '--fg', '--muted', '--border', '--accent'];
|
|
122
|
+
const tokenLine = tokenKeys
|
|
123
|
+
.filter(k => a1[k])
|
|
124
|
+
.map(k => `${k} ${a1[k]}`)
|
|
125
|
+
.join(' / ');
|
|
126
|
+
const fontDisplay = a1['--font-display'] ? `\n- --font-display: ${a1['--font-display']}` : '';
|
|
127
|
+
const fontBody = a1['--font-body'] ? `\n- --font-body: ${a1['--font-body']}` : '';
|
|
128
|
+
|
|
129
|
+
const hardRules = [
|
|
130
|
+
'1. 只使用上方白名单组件与 token;若需系统外元素 → 记录为新组件需求(ds_increment),不自造',
|
|
131
|
+
'2. 数据展示须覆盖 Loading / Empty / Error / Populated / Edge 五状态(craft/state-coverage.md)',
|
|
132
|
+
...antiPatterns.map((p, i) => `${i + 3}. ${p}`),
|
|
133
|
+
];
|
|
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
|
+
|
|
167
|
+
const primer = `# AI Primer — 原型生成约束
|
|
168
|
+
|
|
169
|
+
> 本文件由 scripts/gen-primer.mjs 从设计系统确定性生成,请勿手工编辑。
|
|
170
|
+
> 生成时间:${new Date().toISOString()} | 来源:${sourceLabel} @ sha256:${digestShort}
|
|
171
|
+
> 契约表:${count} 类组件(${countLabel}) | contract=${contract || 'unset'}
|
|
172
|
+
|
|
173
|
+
## 可用组件白名单(只用这些)
|
|
174
|
+
|
|
175
|
+
${whitelistLines.join('\n')}
|
|
176
|
+
|
|
177
|
+
## Token 速查
|
|
178
|
+
|
|
179
|
+
${tokenLine || '(base.md 未内联 A1 token 值,请以 design-tokens.css 为准)'}${fontDisplay}${fontBody}
|
|
180
|
+
|
|
181
|
+
## 硬规则
|
|
182
|
+
|
|
183
|
+
${hardRules.join('\n')}
|
|
184
|
+
|
|
185
|
+
## 页面范式
|
|
186
|
+
|
|
187
|
+
${pageArchetypeSection()}
|
|
188
|
+
`;
|
|
189
|
+
|
|
190
|
+
mkdirSync(dirname(primerPath), { recursive: true });
|
|
191
|
+
writeFileSync(primerPath, primer, 'utf-8');
|
|
192
|
+
console.log(`primer 已生成:${primerPath}`);
|
|
193
|
+
console.log(` 组件白名单:${count} 类(${countLabel})`);
|
|
194
|
+
if (variants.length) console.log(` 参与 digest 的变体:${variants.map(v => basename(v.file)).join(' / ')}`);
|
|
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
|
}
|