iter(g186): 固化视觉回归抽查清单

This commit is contained in:
HIsheR
2026-10-26 17:47:50 +08:00
parent 85ae90a5af
commit 5908202cd7
7 changed files with 110 additions and 7 deletions
+48 -1
View File
@@ -1235,6 +1235,52 @@ function checkPropsDoc(pkg, comps) {
return issues;
}
/*** 视觉回归抽查清单校验:ITERATION.md 的清单必须六个高频组件齐全、每条带合法维度标签、条目数达标 ***/
function checkVisualChecklist() {
const DIMS = ['边框', '阴影', '过渡', '焦点环', '暗色']; // 五个必覆盖维度
const REQUIRED = ['button', 'textField', 'select', 'modal', 'dataTable', 'toast']; // 六个代表组件
const MIN_PER_GROUP = 3; // 每个组件至少三条可勾选要点
const MIN_TOTAL = 18; // 清单总条目下限
const file = path.join(ROOT, 'ITERATION.md');
if (!fs.existsSync(file)) return ['视觉回归抽查清单:找不到 ITERATION.md'];
const lines = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n').split('\n');
const start = lines.findIndex((l) => /^##\s+视觉回归抽查清单/.test(l));
if (start === -1) return ['视觉回归抽查清单:ITERATION.md 缺少《视觉回归抽查清单》章节'];
const rest = lines.slice(start + 1);
const stop = rest.findIndex((l) => /^##\s+/.test(l)); // 下一个二级标题即章节结束
const body = stop === -1 ? rest : rest.slice(0, stop);
const issues = [];
const groups = new Map(); // 组件 id → 该段的维度标签列表
let current = null;
for (const line of body) {
const head = line.match(/^###\s+(\S+)/);
if (head) {
current = head[1];
if (!groups.has(current)) groups.set(current, []);
continue;
}
const item = line.match(/^-\s+\[[ x]\]\s+【([^】]+)】/);
if (!item) continue;
if (!current) {
issues.push(`视觉回归抽查清单:条目「${line.trim().slice(0, 24)}」没有归到任何 ### 组件段下`);
continue;
}
if (!DIMS.includes(item[1])) issues.push(`视觉回归抽查清单:${current} 的条目维度「${item[1]}」不在五类之内(${DIMS.join(' / ')})`);
groups.get(current).push(item[1]);
}
for (const id of REQUIRED) {
const items = groups.get(id);
if (!items) issues.push(`视觉回归抽查清单:缺少 ### ${id} 段`);
else if (items.length < MIN_PER_GROUP) issues.push(`视觉回归抽查清单:${id} 只有 ${items.length} 条要点,至少 ${MIN_PER_GROUP} 条`);
}
const total = [...groups.values()].reduce((sum, items) => sum + items.length, 0);
if (total < MIN_TOTAL) issues.push(`视觉回归抽查清单:合计 ${total} 条要点,至少 ${MIN_TOTAL} 条`);
const covered = new Set([...groups.values()].flat());
const missed = DIMS.filter((d) => !covered.has(d));
if (missed.length) issues.push(`视觉回归抽查清单:维度未覆盖 ${missed.join(' / ')}(五类都要有)`);
return issues;
}
/*** 清单一致性校验:组件文档块的 id / props 说明 / demo 锚点必须与代码和演示产物对齐 ***/
function cmdVerify() {
const issues = [];
@@ -1290,13 +1336,14 @@ function cmdVerify() {
checkDarkTokens(issues);
checkLlmsFull(pkgs, issues);
issues.push(...checkIterations());
issues.push(...checkVisualChecklist());
if (issues.length) {
for (const it of issues) console.error('verify: ' + it);
console.error(`${issues.length} violation(s)`);
process.exit(1);
}
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌、暗色令牌齐全,README 组件清单与分组同步,backlog ↔ 留痕一致(编号严格递增、无重复 id、留痕时间线单调且带 +08:00),组合配方结构完整,MCP 工具清单三处一致)');
console.log('verify OK (manifest ↔ docs ↔ demo 一致,文档 props ↔ 入参接口双向一致,皮肤无硬编码颜色与未定义令牌、暗色令牌齐全,README 组件清单与分组同步,backlog ↔ 留痕一致(编号严格递增、无重复 id、留痕时间线单调且带 +08:00),组合配方结构完整,MCP 工具清单三处一致,视觉回归抽查清单六组件 × 五维度齐全)');
}
/*** 校验 git 提交信息是否符合 COMMITTING.md 规范(参数为 message 文件路径或直接 message 文本),违规退出码 1 ***/
+1 -1
View File
@@ -17,7 +17,7 @@
"ua info <包名|组件id>:包名返回摘要,组件 id 返回 props 表 + 步骤 + 示例;--json 输出原始清单",
"ua docs:生成人类文档站 apps/docs/index.html + demo.html 与 Agent 全量文档 llms-full.txt",
"ua style:注释规范门禁(导出必须 /*** ***/、接口字段行尾注释、行注释中文),违规退出码 1",
"ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在)",
"ua verify:清单一致性门禁(id 唯一 / exports 对应 / props 必带 n t d / steps≥3 / demo 锚点存在 / 视觉回归抽查清单六组件各 ≥3 条且五维度齐全)",
"ua commit-lint <文件|message>:提交信息门禁(type 枚举 / scope 规则 / subject 中文祈使 ≤50 字符),规范见 COMMITTING.md,违规退出码 1",
"ua audit:visual [--json|--baseline]:视觉漂移门禁,统计皮肤包里各类硬编码视觉字面量(字号/动效时长/阴影/圆角/焦点环/内距/控件高度/字重/行高/层级/边框色)并与 .ua/visual-baseline.json 棘轮比对,超基线退出码 1;--baseline 重写基线,--json 出机器可读计数",
"ua mcp:零依赖 stdio MCP server,暴露 list_components / get_component / search_components / get_tokens / list_recipes / get_recipe"