@qilitt-mickey/vue3-temp-skill 1.1.43 → 1.1.44
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/SKILL.md +1 -0
- package/bin/cli.js +1 -1
- package/package.json +1 -1
- package/parser/fixtures/changeset-registry-gap.json +29 -0
- package/parser/fixtures/changeset-v32-fields.json +8 -1
- package/parser/fixtures/registry-bad.json +11 -0
- package/parser/fixtures/registry-sample.json +15 -0
- package/parser/index.js +400 -15
- package/parser/regression.js +29 -5
- package/references/code-quality.md +8 -3
- package/references/design-apply.md +66 -5
package/SKILL.md
CHANGED
|
@@ -93,6 +93,7 @@ tags: [vue3, typescript, element-plus, pinia, vite, unocss, crud, demo, code-qua
|
|
|
93
93
|
- 对照 `references/code-quality.md` 检查类型、组件复用、API、路由、权限、安全、异常反馈、响应式和可演进性。
|
|
94
94
|
- 在目标项目执行 `pnpm lint:fix`、`pnpm type-check`;改动构建配置时再执行 `pnpm build`。
|
|
95
95
|
- **设计文件落地时追加渲染态验收**:变更集含 `oracle`(或效果图带 `data-sid`)时,跑 `node parser/index.js verify`——抽取项目同名 `data-sid` 元素的 computed style,与效果图期望值逐点 diff;缺口按三类归因(选择器落空 / 权重被压 / 语境错位)修正,禁止用 `!important` 掩盖。
|
|
96
|
+
- **设计文件落地时核对登记表覆盖率**:变更集带登记表(`coverage.registry` 或 `--registry`)时,跑 `node parser/index.js match <cs.json> --registry <restore-registry.json>`——`C = A` 且无缺口才可交付;parser **当场以登记表为权威重算**,设计侧自报的 `coverage` 数字不参与。单独核验登记表对词典的通道就绪度用 `node parser/index.js registry <restore-registry.json>`(硬缺口 exit 1、通道缺口列出待补字段)。
|
|
96
97
|
- 最终说明:命中能力组、修改文件、功能验证、命令结果、未解决问题和下一步。
|
|
97
98
|
|
|
98
99
|
## 出厂中性 · 变更集驱动(design-apply 架构红线)
|
package/bin/cli.js
CHANGED
|
@@ -263,7 +263,7 @@ function installSkills(targetKey, projectDir) {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
// design-apply 能力组依赖:mapping/ 词典 + parser/ 解析器(变更集落地的运行时依赖)
|
|
266
|
-
// parser/fixtures/ 为作者侧回归夹具(A-
|
|
266
|
+
// parser/fixtures/ 为作者侧回归夹具(A-O 回归步骤的测试数据),不随技能分发,保留在仓库
|
|
267
267
|
for (const runtimeDir of ["mapping", "parser"]) {
|
|
268
268
|
const src = path.join(SKILL_DIR, runtimeDir);
|
|
269
269
|
if (fs.existsSync(src)) copyDir(src, path.join(dest, runtimeDir), ["fixtures"]);
|
package/package.json
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "回归夹具 · 登记表覆盖率欠声明负例。配 registry-sample.json 使用(--registry):夹具只声明 sample#1/sample#2 两条,sample#3(金额列)与 sample#4(状态列)虽有 oracle.sids 却未声明 → 预期 match exit 1,输出含「登记表覆盖率缺口」。",
|
|
3
|
+
"schema": "design.changeset/1.0",
|
|
4
|
+
"changeset_version": "1.0.0",
|
|
5
|
+
"mapping_version": "1.0.0",
|
|
6
|
+
"scene": "admin",
|
|
7
|
+
"changes": [
|
|
8
|
+
{
|
|
9
|
+
"id": "admin.content.table.header_cell",
|
|
10
|
+
"style": { "bg": "钴蓝-9", "padding": "16px" },
|
|
11
|
+
"layer": { "bg": "host", "padding": "host" }
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "admin.content.table.cell",
|
|
15
|
+
"style": { "padding": "16px" },
|
|
16
|
+
"layer": { "padding": "host" }
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"oracle": {
|
|
20
|
+
"mockup": "运单管理-钴蓝.html",
|
|
21
|
+
"sids": [
|
|
22
|
+
"admin.content.table.header_cell",
|
|
23
|
+
"admin.content.table.cell",
|
|
24
|
+
"admin.content.table.cell_amount",
|
|
25
|
+
"admin.content.table.cell_status"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"notes": "本夹具只用于回归登记表覆盖率细闸,不是真实设计交付件。"
|
|
29
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$comment": "V3.2 三声明维度正例(apply_via / layer / structure_ops
|
|
2
|
+
"$comment": "V3.2 三声明维度正例(apply_via / layer / structure_ops)+ coverage 覆盖率记账。预期:match exit 0;输出含「结构施工 structure_ops」「覆写优先级 priority」「inner 层」「覆盖率 coverage」且覆盖率校验通过(C = A、gaps 为空)。",
|
|
3
3
|
"schema": "design.changeset/1.0",
|
|
4
4
|
"changeset_version": "1.0.0",
|
|
5
5
|
"mapping_version": "1.0.0",
|
|
@@ -52,5 +52,12 @@
|
|
|
52
52
|
"admin.content.table.cell_amount"
|
|
53
53
|
]
|
|
54
54
|
},
|
|
55
|
+
"coverage": {
|
|
56
|
+
"registry": "restore-registry.json",
|
|
57
|
+
"registrable": 62,
|
|
58
|
+
"applicable": 3,
|
|
59
|
+
"covered": 3,
|
|
60
|
+
"gaps": []
|
|
61
|
+
},
|
|
55
62
|
"notes": "本夹具只用于回归 V3.2 三个声明维度的解析与输出,不是真实设计交付件。"
|
|
56
63
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "回归夹具 · 登记表硬缺口负例。registry-sample.json 的姊妹负例:引用了一个词典不存在、又没标 pending 的点位 → 预期 registry 子命令 exit 1,输出含「硬缺口」。",
|
|
3
|
+
"schema": "design.restore-registry/1.0",
|
|
4
|
+
"design_scheme": "default",
|
|
5
|
+
"scene": "admin",
|
|
6
|
+
"counts": { "total": 2 },
|
|
7
|
+
"items": [
|
|
8
|
+
{ "rule": "bad#1", "group": "样例·表格区", "sid": "admin.content.table.header_cell", "dims": ["padding"], "channel": ["style"], "source": "both", "always": true },
|
|
9
|
+
{ "rule": "bad#2", "group": "样例·不存在的点位", "sid": "admin.layout.nonexistent_zone", "dims": ["bg"], "channel": ["style"], "source": "spec", "always": true }
|
|
10
|
+
]
|
|
11
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "回归夹具 · 登记表样例(不是真实交付件)。6 条登记项示范粒度与字段口径:4 条 always(恒存在,必登)、1 条 pending(点位待补词典)、1 条条件项。用于 M/N 两步:M 校验覆盖率重算能报出欠声明并 exit 1,N 校验通道核验能跑通。",
|
|
3
|
+
"schema": "design.restore-registry/1.0",
|
|
4
|
+
"design_scheme": "default",
|
|
5
|
+
"scene": "admin",
|
|
6
|
+
"counts": { "total": 6 },
|
|
7
|
+
"items": [
|
|
8
|
+
{ "rule": "sample#1", "group": "样例·表格区", "sid": "admin.content.table.header_cell", "dims": ["height", "padding"], "channel": ["style", "layer"], "source": "both", "always": true },
|
|
9
|
+
{ "rule": "sample#2", "group": "样例·表格区", "sid": "admin.content.table.cell", "dims": ["padding", "padding_y"], "channel": ["style", "layer"], "source": "both", "always": true },
|
|
10
|
+
{ "rule": "sample#3", "group": "样例·金额列", "sid": "admin.content.table.cell_amount", "dims": ["align"], "channel": ["style", "structure_ops"], "source": "spec", "always": true },
|
|
11
|
+
{ "rule": "sample#4", "group": "样例·状态列", "sid": "admin.content.table.cell_status", "dims": ["form", "bg"], "channel": ["form", "structure_ops"], "source": "spec", "always": true },
|
|
12
|
+
{ "rule": "sample#5", "group": "样例·选择列", "sid": "admin.content.table.selection_column", "dims": ["column_width"], "channel": ["style"], "source": "spec", "always": false, "pending": true },
|
|
13
|
+
{ "rule": "sample#6", "group": "样例·状态列", "sid": "component.tag.status", "dims": ["bg_color"], "channel": ["style"], "source": "spec", "always": false }
|
|
14
|
+
]
|
|
15
|
+
}
|
package/parser/index.js
CHANGED
|
@@ -20,10 +20,11 @@
|
|
|
20
20
|
* node parser/index.js query <semantic.id> # 查询单个 ID 映射详情
|
|
21
21
|
* node parser/index.js audit --project <dir> [<cs…>] # 挂载点存在性 + 死通道静态审计(V3.2)
|
|
22
22
|
* node parser/index.js verify --oracle <o> --actual <a> # 渲染态逐点验收:computed style 期望 vs 实测 diff(V3.2)
|
|
23
|
+
* node parser/index.js registry <registry.json> # 登记表通道核验:登记项 sid / dims 是否在词典有落点(V3.2)
|
|
23
24
|
* node parser/watch.js # 开发态监听 mapping/ 自动重编译(§3.4.3)
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
|
-
import { readFileSync, statSync, readdirSync } from "node:fs";
|
|
27
|
+
import { readFileSync, statSync, readdirSync, existsSync } from "node:fs";
|
|
27
28
|
import path from "node:path";
|
|
28
29
|
import { fileURLToPath } from "node:url";
|
|
29
30
|
import { buildMappingDataset } from "./loader.js";
|
|
@@ -84,6 +85,8 @@ function main() {
|
|
|
84
85
|
return cmdAudit(args, dataset);
|
|
85
86
|
case "verify":
|
|
86
87
|
return cmdVerify(args);
|
|
88
|
+
case "registry":
|
|
89
|
+
return cmdRegistry(args, dataset);
|
|
87
90
|
default:
|
|
88
91
|
printHelp();
|
|
89
92
|
}
|
|
@@ -95,10 +98,14 @@ function printHelp() {
|
|
|
95
98
|
用法:
|
|
96
99
|
node parser/index.js build [--dry] 校验映射表并生成 dist/ 产物
|
|
97
100
|
node parser/index.js match <changeset.json...> 对设计变更集执行三级匹配(可多文件)
|
|
101
|
+
node parser/index.js match <cs.json> --registry <reg.json>
|
|
102
|
+
匹配 + 登记表覆盖率当场重算(子项级细闸,有缺口 exit 1)
|
|
98
103
|
node parser/index.js match --batch <dir> 项目级批量匹配:目录下全部变更集 + 跨文件汇总
|
|
99
104
|
node parser/index.js audit --project <dir> [<cs.json…>] 挂载点存在性 + 死通道静态审计(需目标项目源码)
|
|
100
105
|
node parser/index.js verify --oracle <o.json> --actual <a.json> [--sids a,b]
|
|
101
106
|
渲染态逐点验收:computed style 期望 vs 实测 diff(V3.2)
|
|
107
|
+
node parser/index.js registry <registry.json> [--verbose]
|
|
108
|
+
登记表通道核验:登记项 sid 是否在词典、dims 是否有落点(V3.2)
|
|
102
109
|
node parser/index.js list [--scene admin] [--verbose] 列出全部语义 ID(--verbose 含字段与形态,供下发词汇约束)
|
|
103
110
|
node parser/index.js query <semantic.id> 查询单个 ID 的映射详情
|
|
104
111
|
node parser/watch.js 开发态监听 mapping/ 变更自动重编译
|
|
@@ -120,7 +127,11 @@ function cmdBuild(args, warnings) {
|
|
|
120
127
|
|
|
121
128
|
// ---- match:三级匹配 ----
|
|
122
129
|
function cmdMatch(args, dataset) {
|
|
123
|
-
|
|
130
|
+
// --registry <path>:登记表路径(子项级覆盖率细闸)。缺省时回落到 changeset.coverage.registry / oracle.registry。
|
|
131
|
+
const regIdx = args.indexOf("--registry");
|
|
132
|
+
const registryArg = regIdx >= 0 ? args[regIdx + 1] : null;
|
|
133
|
+
const consumed = new Set(registryArg ? [registryArg] : []);
|
|
134
|
+
const files = args.filter(a => !a.startsWith("--") && !consumed.has(a));
|
|
124
135
|
if (files.length === 0) {
|
|
125
136
|
console.error("✖ 缺少变更集文件参数:node parser/index.js match <changeset.json...|--batch <dir>>");
|
|
126
137
|
process.exit(1);
|
|
@@ -151,19 +162,23 @@ function cmdMatch(args, dataset) {
|
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
// 跨文件汇总(项目级模式对账用)
|
|
154
|
-
const batchSummary = { files: 0, l1: 0, l2: 0, l3: 0, pendingIds: [], fieldPending: [], invalid: [] };
|
|
165
|
+
const batchSummary = { files: 0, l1: 0, l2: 0, l3: 0, pendingIds: [], fieldPending: [], invalid: [], registryGaps: 0 };
|
|
155
166
|
|
|
156
167
|
for (const target of targets) {
|
|
157
168
|
batchSummary.files++;
|
|
158
169
|
if (targets.length > 1)
|
|
159
170
|
console.log(`\n════ 变更集 ${batchSummary.files}/${targets.length}:${path.basename(target)} ════`);
|
|
160
|
-
matchOne(target, dataset, batchSummary);
|
|
171
|
+
matchOne(target, dataset, batchSummary, { registryArg, filePath: target, verbose: args.includes("--verbose") });
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
// 任一文件被拒(schema/版本/禁词校验失败)→ 进程退出码 1(v1.0 修正:单文件模式此前误返回 0)
|
|
164
175
|
if (batchSummary.invalid.length > 0)
|
|
165
176
|
process.exitCode = 1;
|
|
166
177
|
|
|
178
|
+
// 登记表覆盖率有缺口(子项被静默丢失)→ 也是「交付不完整」,退出码 1
|
|
179
|
+
if (batchSummary.registryGaps > 0)
|
|
180
|
+
process.exitCode = 1;
|
|
181
|
+
|
|
167
182
|
// 项目级汇总输出
|
|
168
183
|
if (targets.length > 1) {
|
|
169
184
|
console.log(`\n════════ 项目级汇总(${targets.length} 份变更集)════════`);
|
|
@@ -182,11 +197,13 @@ function cmdMatch(args, dataset) {
|
|
|
182
197
|
for (const f of batchSummary.fieldPending)
|
|
183
198
|
console.log(` - ${f.id}: ${f.fields.join("、")}`);
|
|
184
199
|
}
|
|
200
|
+
if (batchSummary.registryGaps > 0)
|
|
201
|
+
console.log(`✖ 登记表覆盖率缺口合计 ${batchSummary.registryGaps} 条(子项级欠声明,详见各文件段落)`);
|
|
185
202
|
}
|
|
186
203
|
}
|
|
187
204
|
|
|
188
205
|
/** 单份变更集匹配(cmdMatch 内部复用) */
|
|
189
|
-
function matchOne(file, dataset, batchSummary) {
|
|
206
|
+
function matchOne(file, dataset, batchSummary, opts = {}) {
|
|
190
207
|
let changeset;
|
|
191
208
|
try {
|
|
192
209
|
changeset = JSON.parse(readFileSync(file, "utf-8"));
|
|
@@ -315,16 +332,12 @@ function matchOne(file, dataset, batchSummary) {
|
|
|
315
332
|
}
|
|
316
333
|
|
|
317
334
|
// oracle 验收基线(V3.2:设计侧给的效果图 + 待比对点位;项目侧落地后跑 verify 逐点 diff)
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
console.log(`\n提示:变更集未提供 oracle 验收基线(场景还原应给)—— 落地后无法做 computed style 逐点比对,还原度不可证明,建议回流设计侧补齐。`);
|
|
327
|
-
}
|
|
335
|
+
checkOracle(changeset, plan, valueDict);
|
|
336
|
+
checkCoverage(changeset);
|
|
337
|
+
// 登记表覆盖率(子项级细闸):以登记表为权威、当场重算 A/C,设计侧自报的 coverage 不参与
|
|
338
|
+
const regGaps = checkRegistryCoverage(changeset, plan, opts);
|
|
339
|
+
if (batchSummary)
|
|
340
|
+
batchSummary.registryGaps += regGaps;
|
|
328
341
|
|
|
329
342
|
// 输出转换计划
|
|
330
343
|
console.log(`\n=== 三级匹配结果(scene=${changeset.scene},${plan.length} 条)===`);
|
|
@@ -605,7 +618,379 @@ function printApplyPlan(entry, change, idLabel, index) {
|
|
|
605
618
|
|
|
606
619
|
if (entry.priority && entry.priority.strategy) {
|
|
607
620
|
console.log(` ▸ 覆写优先级 priority:${entry.priority.strategy}${entry.priority.reason ? ` —— ${entry.priority.reason}` : ""}`);
|
|
621
|
+
console.log(` 落地要求:${priorityRequirement(entry.priority.strategy)}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* 覆写优先级 → 落地写法要求(V3.2)
|
|
627
|
+
* 该维度不是「提示」,是硬要求:写错写法 = 规则写了也不生效(同权重被组件 scoped 后注入覆盖,或被出厂 !important 压制)。
|
|
628
|
+
*/
|
|
629
|
+
function priorityRequirement(strategy) {
|
|
630
|
+
switch (strategy) {
|
|
631
|
+
case "scope_qualified":
|
|
632
|
+
return "词典 selector 已含作用域限定,按原样落;若实测仍被覆盖,按 deep_qualified 处理。";
|
|
633
|
+
case "deep_qualified":
|
|
634
|
+
return "禁止裸类名。必须 ≥3 层作用域限定(如 html .a .b)或叠加属性/伪类,否则与组件 scoped 平手、被后注入覆盖 → 选择器“写了不生效”。";
|
|
635
|
+
case "self_plus_important":
|
|
636
|
+
return "对手带 !important,本点位也必须 !important(或改走通道变量);只抬特异度无法取胜。";
|
|
637
|
+
default:
|
|
638
|
+
return "(未知策略,按 deep_qualified 保守处理)";
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* oracle 验收基线校验(V3.2)
|
|
644
|
+
* 两类事故都出在「基线本身不可用」,而不是落地写法:
|
|
645
|
+
* ① 主题错配:效果图是 A 主题、变更集是 B 主题 → 逐点比对必然全红(搬错基线)。
|
|
646
|
+
* ② 覆盖度缺口:效果图里有、changes[] 却未声明的点位 → 永远不可能被还原(欠声明);
|
|
647
|
+
* 反之声明了却不在 sids 里的点位 → 落地后无法机器验收。
|
|
648
|
+
* 两者都属「交付不完整」,与颜色红线同级,必须回流设计侧补齐。
|
|
649
|
+
*/
|
|
650
|
+
function checkOracle(changeset, plan, valueDict) {
|
|
651
|
+
const oracle = changeset.oracle;
|
|
652
|
+
if (!oracle || typeof oracle !== "object") {
|
|
653
|
+
console.log(`\n提示:变更集未提供 oracle 验收基线(场景还原应给)—— 落地后无法做 computed style 逐点比对,还原度不可证明,建议回流设计侧补齐。`);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
const sids = Array.isArray(oracle.sids) ? oracle.sids : [];
|
|
658
|
+
const mockup = typeof oracle.mockup === "string" ? oracle.mockup : "";
|
|
659
|
+
console.log(`\n=== 验收基线 oracle(落地后跑 verify 逐点比对)===`);
|
|
660
|
+
console.log(` 效果图:${mockup || "(未给 mockup 路径)"}`);
|
|
661
|
+
console.log(` 待比对点位:${sids.length} 条${sids.length ? ` —— ${sids.join("、")}` : ""}`);
|
|
662
|
+
|
|
663
|
+
const issues = [];
|
|
664
|
+
|
|
665
|
+
// ① 主题一致性:效果图文件名中的主题词 vs global.brand_primary 的主题词
|
|
666
|
+
const themes = new Set(
|
|
667
|
+
Object.keys(valueDict || {})
|
|
668
|
+
.map(k => /^(.+?)-\d+$/.exec(k))
|
|
669
|
+
.filter(Boolean)
|
|
670
|
+
.map(m => m[1]),
|
|
671
|
+
);
|
|
672
|
+
const themeFromValue = (v) => {
|
|
673
|
+
const m = typeof v === "string" ? /^(.+?)-\d+$/.exec(v.trim()) : null;
|
|
674
|
+
return m ? m[1] : null;
|
|
675
|
+
};
|
|
676
|
+
const declaredTheme = themeFromValue(changeset.global && changeset.global.brand_primary);
|
|
677
|
+
const mockupTheme = [...themes].find(t => mockup.includes(t)) || null;
|
|
678
|
+
if (declaredTheme && mockupTheme && declaredTheme !== mockupTheme) {
|
|
679
|
+
issues.push(`主题错配:效果图是「${mockupTheme}」、本变更集主色是「${declaredTheme}」—— 跨主题复用效果图会让 verify 逐点比对必然全红,请换成本主题自己的效果图。`);
|
|
680
|
+
}
|
|
681
|
+
if (!mockupTheme && mockup)
|
|
682
|
+
issues.push(`效果图文件名不含任何已知主题词(${[...themes].join("/")}),无法确认主题一致性 —— 请核对是否复用了其它主题的效果图。`);
|
|
683
|
+
|
|
684
|
+
// ② 覆盖度(双向)
|
|
685
|
+
const declaredIds = plan.map(p => p.id);
|
|
686
|
+
const sidSet = new Set(sids);
|
|
687
|
+
const sidNotDeclared = sids.filter(s => !declaredIds.includes(s));
|
|
688
|
+
const declaredWithoutSid = declaredIds.filter(id => !sidSet.has(id));
|
|
689
|
+
if (sidNotDeclared.length > 0)
|
|
690
|
+
issues.push(`欠声明 ${sidNotDeclared.length} 处:效果图里有、changes[] 未声明 → 该点位永远不可能被还原。\n ${sidNotDeclared.join("、")}`);
|
|
691
|
+
if (declaredWithoutSid.length > 0)
|
|
692
|
+
issues.push(`无基线 ${declaredWithoutSid.length} 处:changes[] 已声明、效果图 sids 未覆盖 → 落地后无法机器验收。\n ${declaredWithoutSid.join("、")}`);
|
|
693
|
+
|
|
694
|
+
if (issues.length === 0) {
|
|
695
|
+
console.log(` ✔ oracle 校验通过:主题一致、覆盖度双向闭合`);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
console.log(`\n⚠ oracle 基线问题(${issues.length} 类):`);
|
|
699
|
+
for (const it of issues)
|
|
700
|
+
console.log(` ✘ ${it}`);
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* 覆盖率校验(场景还原子项级细闸)
|
|
705
|
+
* 设计侧用「登记表」把规范里每条带样式结论的子项变成必登项,交付时以 coverage 记账。
|
|
706
|
+
* 这里只做结构一致性校验:covered 必须等于 applicable、gaps 必须为空。
|
|
707
|
+
* 缺 coverage 时无法判断「是否有组内子项被静默丢失」——属交付完整性的信号缺口。
|
|
708
|
+
*/
|
|
709
|
+
function checkCoverage(changeset) {
|
|
710
|
+
const cov = changeset.coverage;
|
|
711
|
+
if (!cov || typeof cov !== "object") {
|
|
712
|
+
console.log(`\n提示:变更集未提供 coverage 覆盖率记账(场景还原应给)—— 无法判断是否有规范子项被静默丢失,完备性不可数,建议回流设计侧补齐。`);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const num = v => (typeof v === "number" && Number.isFinite(v) ? v : null);
|
|
716
|
+
const R = num(cov.registrable);
|
|
717
|
+
const A = num(cov.applicable);
|
|
718
|
+
const C = num(cov.covered);
|
|
719
|
+
const gaps = Array.isArray(cov.gaps) ? cov.gaps : [];
|
|
720
|
+
console.log(`\n=== 覆盖率 coverage(子项级细闸)===`);
|
|
721
|
+
console.log(` 登记表:${cov.registry || "(未给路径)"}`);
|
|
722
|
+
console.log(` 登记项 R=${R ?? "?"} / 适用 A=${A ?? "?"} / 已兑现 C=${C ?? "?"}`);
|
|
723
|
+
|
|
724
|
+
const issues = [];
|
|
725
|
+
if (A === null || C === null)
|
|
726
|
+
issues.push("缺 applicable / covered 计数,无法核验完备性。");
|
|
727
|
+
else if (C < A)
|
|
728
|
+
issues.push(`欠声明:已兑现 ${C} < 适用 ${A} —— 有 ${A - C} 条登记项未落进 changes[] / visible / pending。`);
|
|
729
|
+
if (gaps.length > 0)
|
|
730
|
+
issues.push(`gaps 非空(${gaps.length} 条):${gaps.join("、")}`);
|
|
731
|
+
|
|
732
|
+
if (issues.length === 0) {
|
|
733
|
+
console.log(` ✔ 覆盖率校验通过:C = A 且无缺口`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
console.log(`\n⚠ 覆盖率问题(${issues.length} 类):`);
|
|
737
|
+
for (const it of issues)
|
|
738
|
+
console.log(` ✘ ${it}`);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/** 依次尝试候选路径,返回首个存在者(相对路径基准:变更集所在目录 → 当前工作目录 → 技能仓库根) */
|
|
742
|
+
function resolveFirstExisting(raw, filePath) {
|
|
743
|
+
if (!raw || typeof raw !== "string")
|
|
744
|
+
return null;
|
|
745
|
+
if (path.isAbsolute(raw))
|
|
746
|
+
return existsSync(raw) ? raw : null;
|
|
747
|
+
const bases = [];
|
|
748
|
+
if (filePath)
|
|
749
|
+
bases.push(path.dirname(filePath));
|
|
750
|
+
bases.push(process.cwd(), ROOT);
|
|
751
|
+
for (const base of bases) {
|
|
752
|
+
const candidate = path.resolve(base, raw);
|
|
753
|
+
if (existsSync(candidate))
|
|
754
|
+
return candidate;
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** 词典条目的可用字段集:css_vars + custom_style_map + forms 键 + 结构/存在维度 */
|
|
760
|
+
function entryFieldSet(entry) {
|
|
761
|
+
const fields = new Set(["form", "visible", "position"]);
|
|
762
|
+
for (const bag of ["css_vars", "custom_style_map", "forms"]) {
|
|
763
|
+
for (const k of Object.keys(entry?.[bag] || {}))
|
|
764
|
+
fields.add(k);
|
|
765
|
+
}
|
|
766
|
+
return fields;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** 登记项引用的语义 ID(支持单 sid 与多 sids 两种写法) */
|
|
770
|
+
function registrySids(item) {
|
|
771
|
+
if (Array.isArray(item?.sids))
|
|
772
|
+
return item.sids;
|
|
773
|
+
return item?.sid ? [item.sid] : [];
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/** 登记表里唯一允许的「非词典点位」:全局令牌通道(对应变更集顶层 global 节点,无 css selector 故不入词典) */
|
|
777
|
+
const GLOBAL_PSEUDO_ID = "global";
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* 登记表覆盖率(子项级细闸,V3.2)
|
|
781
|
+
* 组级台账(restore-checklist)只能防「整组忘光」;组内子项是否被静默丢失,必须由登记表
|
|
782
|
+
* (规范里每条带样式结论的子项 → 一条登记项)来计数。这里以登记表为权威**当场重算**:
|
|
783
|
+
* 适用 A = always 子项 ∪ 效果图已标注同名 data-sid 的子项
|
|
784
|
+
* 兑现 C = A 中在 changes[] / visible:false / pending[].proposed_id 出现者
|
|
785
|
+
* 设计侧自报的 coverage 数字不参与计算,因此无法用「把 applicable 写小」蒙混过关。
|
|
786
|
+
* 有缺口 → 返回缺口数(调用方置退出码 1)。
|
|
787
|
+
*/
|
|
788
|
+
function checkRegistryCoverage(changeset, plan, opts = {}) {
|
|
789
|
+
const raw = opts.registryArg || changeset.coverage?.registry || changeset.oracle?.registry;
|
|
790
|
+
const registryPath = resolveFirstExisting(raw, opts.filePath);
|
|
791
|
+
if (!registryPath) {
|
|
792
|
+
if (opts.registryArg) {
|
|
793
|
+
console.error(`\n✖ 登记表文件不存在:${opts.registryArg}`);
|
|
794
|
+
return 1;
|
|
795
|
+
}
|
|
796
|
+
console.log(`\n提示:未提供登记表(--registry 或 coverage.registry)—— 组级台账只防「整组忘光」,组内子项被静默丢失无法计数,建议场景还原带登记表。`);
|
|
797
|
+
return 0;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
let registry;
|
|
801
|
+
try {
|
|
802
|
+
registry = JSON.parse(readFileSync(registryPath, "utf-8"));
|
|
803
|
+
}
|
|
804
|
+
catch (e) {
|
|
805
|
+
console.error(`\n✖ 登记表读取/解析失败:${registryPath} —— ${e.message}`);
|
|
806
|
+
return 1;
|
|
807
|
+
}
|
|
808
|
+
const items = Array.isArray(registry.items) ? registry.items : [];
|
|
809
|
+
if (items.length === 0) {
|
|
810
|
+
console.error(`\n✖ 登记表不含 items:${registryPath}`);
|
|
811
|
+
return 1;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// 兑现集合 = changes[] ∪ visible:false ∪ pending[].proposed_id ∪ 顶层 global 节点
|
|
815
|
+
const declared = new Set(plan.map(p => p.id));
|
|
816
|
+
for (const p of plan) {
|
|
817
|
+
if (p.change && p.change.visible === false)
|
|
818
|
+
declared.add(p.id);
|
|
819
|
+
}
|
|
820
|
+
for (const p of Array.isArray(changeset.pending) ? changeset.pending : []) {
|
|
821
|
+
if (p && p.proposed_id)
|
|
822
|
+
declared.add(p.proposed_id);
|
|
823
|
+
}
|
|
824
|
+
if (changeset.global && Object.keys(changeset.global).length > 0)
|
|
825
|
+
declared.add(GLOBAL_PSEUDO_ID);
|
|
826
|
+
const mockupSids = new Set(Array.isArray(changeset.oracle?.sids) ? changeset.oracle.sids : []);
|
|
827
|
+
|
|
828
|
+
const applicable = items.filter(it => it.always === true || registrySids(it).some(s => mockupSids.has(s)));
|
|
829
|
+
const gaps = applicable.filter(it => !registrySids(it).every(s => declared.has(s)));
|
|
830
|
+
|
|
831
|
+
console.log(`\n=== 登记表覆盖率(子项级细闸 · 当场重算)===`);
|
|
832
|
+
console.log(` 登记表:${registryPath}(登记项 R=${items.length})`);
|
|
833
|
+
console.log(` 适用 A=${applicable.length} / 已兑现 C=${applicable.length - gaps.length} / 缺口 ${gaps.length}`);
|
|
834
|
+
|
|
835
|
+
// 效果图标注完整性:恒存在的子项必须能在效果图上找到同名 data-sid,否则基线根本抽不出来
|
|
836
|
+
const unannotated = items.filter(it => it.always === true && !registrySids(it).some(s => mockupSids.has(s)));
|
|
837
|
+
if (unannotated.length > 0) {
|
|
838
|
+
const bySid = new Map();
|
|
839
|
+
for (const it of unannotated) {
|
|
840
|
+
for (const s of registrySids(it))
|
|
841
|
+
bySid.set(s, (bySid.get(s) || 0) + 1);
|
|
842
|
+
}
|
|
843
|
+
console.log(` ⚠ 效果图标注缺口 ${unannotated.length} 条:这些子项恒存在,却无同名 data-sid → 基线抽取不全`);
|
|
844
|
+
console.log(` 涉及点位:${[...bySid.entries()].map(([s, n]) => `${s}(${n})`).join("、")}`);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
if (gaps.length === 0) {
|
|
848
|
+
console.log(` ✔ 覆盖率通过:C = A,无子项被静默丢失`);
|
|
849
|
+
return 0;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const byGroup = new Map();
|
|
853
|
+
for (const it of gaps) {
|
|
854
|
+
const g = it.group || "(未分组)";
|
|
855
|
+
if (!byGroup.has(g))
|
|
856
|
+
byGroup.set(g, []);
|
|
857
|
+
byGroup.get(g).push(it);
|
|
858
|
+
}
|
|
859
|
+
console.log(`\n✘ 登记表覆盖率缺口(子项级欠声明,共 ${gaps.length} 条 → exit 1):`);
|
|
860
|
+
for (const [group, list] of byGroup) {
|
|
861
|
+
console.log(` · ${group}(${list.length})`);
|
|
862
|
+
const shown = opts.verbose ? list : list.slice(0, 6);
|
|
863
|
+
for (const it of shown)
|
|
864
|
+
console.log(` ${it.rule} ${registrySids(it).join(" + ")} [${(it.dims || []).join(",")}]`);
|
|
865
|
+
if (!opts.verbose && list.length > shown.length)
|
|
866
|
+
console.log(` …(同组其余 ${list.length - shown.length} 条,加 --verbose 展开)`);
|
|
867
|
+
}
|
|
868
|
+
return gaps.length;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* 登记表通道核验(V3.2)
|
|
873
|
+
* 与 match 的覆盖率校验互补:不需要变更集,直接拿登记表对词典,回答一件事——
|
|
874
|
+
* 「这张登记表里的必登项,有多少在项目侧真有承载字段」。这是 1:1 对接的**通道缺口清单**:
|
|
875
|
+
* · sid 词典无此 ID 且未标 pending → 硬缺口(登记表引用了不存在的点位,exit 1)
|
|
876
|
+
* · dims 是设计侧通用维度(adjustable-elements 口径),词典无对应字段 → 通道缺口(列出,供补词典)
|
|
877
|
+
*/
|
|
878
|
+
function cmdRegistry(args, dataset) {
|
|
879
|
+
const verbose = args.includes("--verbose");
|
|
880
|
+
const file = args.find(a => !a.startsWith("--"));
|
|
881
|
+
if (!file) {
|
|
882
|
+
console.error("✖ 缺少登记表文件参数:node parser/index.js registry <restore-registry.json> [--verbose]");
|
|
883
|
+
process.exit(1);
|
|
884
|
+
}
|
|
885
|
+
const p = path.resolve(file);
|
|
886
|
+
let registry;
|
|
887
|
+
try {
|
|
888
|
+
registry = JSON.parse(readFileSync(p, "utf-8"));
|
|
889
|
+
}
|
|
890
|
+
catch (e) {
|
|
891
|
+
console.error(`✖ 登记表读取/解析失败:${e.message}`);
|
|
892
|
+
process.exit(1);
|
|
893
|
+
}
|
|
894
|
+
const items = Array.isArray(registry.items) ? registry.items : [];
|
|
895
|
+
if (items.length === 0) {
|
|
896
|
+
console.error(`✖ 登记表不含 items:${file}`);
|
|
897
|
+
process.exit(1);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const index = buildMergedIndex(dataset);
|
|
901
|
+
const SOURCES = new Set(["spec", "mockup", "both", "none"]);
|
|
902
|
+
const CHANNELS = new Set(["form", "style", "layer", "structure_ops", "apply_via"]);
|
|
903
|
+
|
|
904
|
+
const hard = [];
|
|
905
|
+
const chanMissing = [];
|
|
906
|
+
const seenRule = new Set();
|
|
907
|
+
const pendingIds = new Set();
|
|
908
|
+
let sidOk = 0;
|
|
909
|
+
let dimTotal = 0;
|
|
910
|
+
let dimOk = 0;
|
|
911
|
+
|
|
912
|
+
for (const it of items) {
|
|
913
|
+
const rule = it.rule || "(无 rule)";
|
|
914
|
+
if (seenRule.has(rule))
|
|
915
|
+
hard.push(`rule 重复:${rule}`);
|
|
916
|
+
seenRule.add(rule);
|
|
917
|
+
if (it.source && !SOURCES.has(it.source))
|
|
918
|
+
hard.push(`${rule}: source "${it.source}" 非法(应为 ${[...SOURCES].join("/")})`);
|
|
919
|
+
for (const c of Array.isArray(it.channel) ? it.channel : []) {
|
|
920
|
+
if (!CHANNELS.has(c))
|
|
921
|
+
hard.push(`${rule}: channel "${c}" 非法(应为 ${[...CHANNELS].join("/")})`);
|
|
922
|
+
}
|
|
923
|
+
const sids = registrySids(it);
|
|
924
|
+
if (sids.length === 0)
|
|
925
|
+
hard.push(`${rule}: 未给 sid / sids`);
|
|
926
|
+
for (const sid of sids) {
|
|
927
|
+
if (sid === GLOBAL_PSEUDO_ID) {
|
|
928
|
+
sidOk++;
|
|
929
|
+
continue; // 全局令牌通道:对应变更集顶层 global 节点,不入词典
|
|
930
|
+
}
|
|
931
|
+
if (it.pending) {
|
|
932
|
+
pendingIds.add(sid);
|
|
933
|
+
if (index[sid])
|
|
934
|
+
hard.push(`${rule}: ${sid} 已标 pending,但词典已有该 ID → 应撤掉 pending 标注`);
|
|
935
|
+
}
|
|
936
|
+
const entry = index[sid];
|
|
937
|
+
if (!entry) {
|
|
938
|
+
if (!it.pending)
|
|
939
|
+
hard.push(`${rule}: ${sid} 词典无此 ID 且未标 pending → 须补词典或改标 pending`);
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
sidOk++;
|
|
943
|
+
const fields = entryFieldSet(entry);
|
|
944
|
+
for (const d of Array.isArray(it.dims) ? it.dims : []) {
|
|
945
|
+
dimTotal++;
|
|
946
|
+
if (fields.has(d))
|
|
947
|
+
dimOk++;
|
|
948
|
+
else
|
|
949
|
+
chanMissing.push(`${rule} ${sid}.${d}`);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
console.log(`=== 登记表通道核验(${path.basename(p)})===`);
|
|
955
|
+
console.log(` 登记项 ${items.length} 条 / 引用点位 ${sidOk} 个命中词典${pendingIds.size ? ` / ${pendingIds.size} 个标 pending(待补词典)` : ""}`);
|
|
956
|
+
console.log(` 维度落点:${dimOk}/${dimTotal} 在词典有对应字段`);
|
|
957
|
+
|
|
958
|
+
if (pendingIds.size > 0) {
|
|
959
|
+
console.log(`\n 待补词典的新点位(设计侧须 changes[] + pending 双写):`);
|
|
960
|
+
for (const id of pendingIds)
|
|
961
|
+
console.log(` - ${id}`);
|
|
962
|
+
}
|
|
963
|
+
if (chanMissing.length > 0) {
|
|
964
|
+
const bySid = new Map();
|
|
965
|
+
for (const line of chanMissing) {
|
|
966
|
+
const sid = line.split(/\s+/)[1].split(".").slice(0, -1).join(".") || "?";
|
|
967
|
+
if (!bySid.has(sid))
|
|
968
|
+
bySid.set(sid, []);
|
|
969
|
+
bySid.get(sid).push(line.split(".").pop());
|
|
970
|
+
}
|
|
971
|
+
console.log(`\n 通道缺口 ${chanMissing.length} 处(设计侧通用维度在词典无字段 → 须补字段或换用已有字段):`);
|
|
972
|
+
for (const [sid, dims] of bySid) {
|
|
973
|
+
const uniq = [...new Set(dims)];
|
|
974
|
+
console.log(` - ${sid}: ${uniq.slice(0, verbose ? uniq.length : 8).join("、")}${!verbose && uniq.length > 8 ? ` …+${uniq.length - 8}` : ""}`);
|
|
975
|
+
}
|
|
976
|
+
if (verbose) {
|
|
977
|
+
console.log(`\n 明细:`);
|
|
978
|
+
for (const line of chanMissing)
|
|
979
|
+
console.log(` ${line}`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (hard.length === 0 && chanMissing.length === 0) {
|
|
984
|
+
console.log(`\n✔ 登记表通道核验通过:点位全部命中、维度全部有落点`);
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (hard.length > 0) {
|
|
988
|
+
console.error(`\n✖ 硬缺口 ${hard.length} 处(登记表引用了不存在的点位 / 元数据非法)→ exit 1:`);
|
|
989
|
+
for (const h of hard)
|
|
990
|
+
console.error(` ✘ ${h}`);
|
|
991
|
+
process.exit(1);
|
|
608
992
|
}
|
|
993
|
+
console.log(`\n⚠ 存在通道缺口(属「项目侧补词典」事项,不阻断匹配;补齐后还原度才可达 100%)。`);
|
|
609
994
|
}
|
|
610
995
|
|
|
611
996
|
/** 输出结构施工声明(structure_ops):数据 / 结构驱动点位,只写 CSS 是无效声明 */
|
package/parser/regression.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A-
|
|
4
|
+
* A-O 端到端一键回归(作者侧维护工具,不随技能分发)
|
|
5
5
|
*
|
|
6
6
|
* 用法(cwd 任意,脚本内部已固定为 skill 根目录执行):
|
|
7
7
|
* node parser/regression.js
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
* J match V3.2 三声明维度 → exit 0 且输出含「结构施工 structure_ops」
|
|
21
21
|
* K audit 挂载点 / 死通道审计 → exit 1(夹具项目故意存在挂载点缺失 / 死通道)
|
|
22
22
|
* L verify 渲染态逐点验收 → exit 1 且三类归因各计其数(夹具含故意缺口)
|
|
23
|
+
* M match 登记表覆盖率细闸 → exit 1(夹具故意欠声明两条恒存在子项)
|
|
24
|
+
* N registry 登记表通道核验 → exit 0 且输出含「登记表通道核验」
|
|
25
|
+
* O registry 硬缺口负例 → exit 1(引用了不存在且未标 pending 的点位)
|
|
23
26
|
*
|
|
24
27
|
* 任一步骤失败 → 本脚本 exit 1,并回显该步骤完整输出便于定位。
|
|
25
28
|
*/
|
|
@@ -109,10 +112,10 @@ const steps = [
|
|
|
109
112
|
},
|
|
110
113
|
{
|
|
111
114
|
key: "J",
|
|
112
|
-
name: "match V3.2
|
|
115
|
+
name: "match V3.2 三声明维度 + coverage 覆盖率(apply_via / layer / structure_ops / coverage)",
|
|
113
116
|
script: INDEX,
|
|
114
117
|
args: ["match", path.join(FIXTURES, "changeset-v32-fields.json")],
|
|
115
|
-
expect: { code: 0, includes: ["结构施工 structure_ops", "验收基线 oracle"] },
|
|
118
|
+
expect: { code: 0, includes: ["结构施工 structure_ops", "验收基线 oracle", "覆盖率 coverage"] },
|
|
116
119
|
},
|
|
117
120
|
{
|
|
118
121
|
key: "K",
|
|
@@ -128,12 +131,33 @@ const steps = [
|
|
|
128
131
|
args: ["verify", "--oracle", path.join(FIXTURES, "verify", "oracle.json"), "--actual", path.join(FIXTURES, "verify", "actual.json")],
|
|
129
132
|
expect: { code: 1, includes: "选择器落空 1 / 权重被压 1 / 语境错位 2" },
|
|
130
133
|
},
|
|
134
|
+
{
|
|
135
|
+
key: "M",
|
|
136
|
+
name: "match 登记表覆盖率细闸(夹具故意欠声明两条恒存在子项 → exit 1)",
|
|
137
|
+
script: INDEX,
|
|
138
|
+
args: ["match", "--registry", path.join(FIXTURES, "registry-sample.json"), path.join(FIXTURES, "changeset-registry-gap.json")],
|
|
139
|
+
expect: { code: 1, includes: ["登记表覆盖率", "缺口 2"] },
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: "N",
|
|
143
|
+
name: "registry 登记表通道核验(点位全部命中 → exit 0)",
|
|
144
|
+
script: INDEX,
|
|
145
|
+
args: ["registry", path.join(FIXTURES, "registry-sample.json")],
|
|
146
|
+
expect: { code: 0, includes: ["登记表通道核验", "登记项 6 条"] },
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
key: "O",
|
|
150
|
+
name: "registry 硬缺口负例(引用不存在且未标 pending 的点位 → exit 1)",
|
|
151
|
+
script: INDEX,
|
|
152
|
+
args: ["registry", path.join(FIXTURES, "registry-bad.json")],
|
|
153
|
+
expect: { code: 1, includes: "硬缺口" },
|
|
154
|
+
},
|
|
131
155
|
];
|
|
132
156
|
|
|
133
157
|
const results = [];
|
|
134
158
|
let failed = 0;
|
|
135
159
|
|
|
136
|
-
console.log("=== A-
|
|
160
|
+
console.log("=== A-O 端到端一键回归 ===\n");
|
|
137
161
|
|
|
138
162
|
for (const step of steps) {
|
|
139
163
|
const { status, stdout, stderr } = spawnSync(
|
|
@@ -170,4 +194,4 @@ if (failed > 0) {
|
|
|
170
194
|
console.error(`\n✖ ${failed} 步未通过,见上方详情`);
|
|
171
195
|
process.exit(1);
|
|
172
196
|
}
|
|
173
|
-
console.log("\n✔ A-
|
|
197
|
+
console.log("\n✔ A-O 全部通过,mapping/parser 可提交或发版");
|
|
@@ -22,7 +22,7 @@ tags: [code-review, quality, checklist, lint, typescript, vue3, security, perfor
|
|
|
22
22
|
- 闸门:已执行 code-quality(生成后)
|
|
23
23
|
- 改动文件:`path/a`、`path/b`、…
|
|
24
24
|
- 清单:选型 ✅ / 类型 ✅ / 组件 ✅ / API ✅ / 安全 ✅ / 业务 ✅ / 列表→详情 ✅或N/A / 列表布局 ✅或N/A / 风格 ✅
|
|
25
|
-
- 设计落地:通道活性 ✅ / 锚点契约 ✅ / 结构施工 ✅或N/A / 覆写提权 ✅ / 渲染态验收 ✅或N/A(无 oracle 时 N/A)
|
|
25
|
+
- 设计落地:通道活性 ✅ / 锚点契约 ✅ / 结构施工 ✅或N/A / 覆写提权 ✅ / 登记表覆盖率 ✅或N/A / 渲染态验收 ✅或N/A(无 oracle 时 N/A)
|
|
26
26
|
- 硬失败:无 | 已拦截并修正:…
|
|
27
27
|
- 已修正:…(无则写「无」)
|
|
28
28
|
- 命令:`pnpm lint:fix` → …;`pnpm type-check` → …
|
|
@@ -47,6 +47,8 @@ lint / type-check **过了也不能放过**下列项。发现即改代码,禁
|
|
|
47
47
|
| 新菜单路由 | 父级无 `icon` 或业务页二次挂 `Layout` | `icons` + `core-kernel` |
|
|
48
48
|
| 列表查询区 `el-form` / 查询按钮 | 写了 `size="small"`(紧凑主题用户未要求) | 不写 size,用 default=`--control-height` |
|
|
49
49
|
| 本轮新增 addon 并引用对应 `Re*` / Vant / 微信 | 已装包但 `tsconfig exclude` 仍含该路径,或 `addon-enable` 要求的 build/src 未改 | 按 **`addon-enable.md`** 对应章节恢复后重跑 type-check |
|
|
50
|
+
| 设计层(`design-shell.scss` / 页面 `scoped`)新增规则 | 与对手**同权重**(对手为组件 `<style scoped>` 时平手即失效——组件样式在全局之后注入),或对手带 `!important` 而本条没带 | 按 `priority.strategy` 提权;**数权重,不许目测** |
|
|
51
|
+
| 变更集带 `oracle` | `oracle.mockup` 与本次主题/页面不一致(跨主题复用效果图),或 `oracle.sids` 与 `changes[]` 未双向闭合(存在欠声明 / 无基线点位) | 回流设计侧补齐后再开工,禁止直接施工 |
|
|
50
52
|
| `element-plus.scss` / `sidebar.scss` 等样式消费层 | `var(--font-size-*)` 用于 `line-height` / `height` 等非字号属性 | 查 TSB:`classifyToken` + `PROPERTY_FORBIDDEN`;`line-height` → `--line-height` 或 `--el-menu-item-height` |
|
|
51
53
|
| `element-plus.scss` 内视觉规则(shadow/hover/transition) | 出现非令牌引用的裸 hex / px / rgba 值 | 全部改为 `var()` 引用令牌 |
|
|
52
54
|
| 视觉规则新增 | `dark.scss` 缺少对应暗色覆盖 | 每条新规则在 `html.dark` 内有对应覆盖 |
|
|
@@ -54,6 +56,7 @@ lint / type-check **过了也不能放过**下列项。发现即改代码,禁
|
|
|
54
56
|
| 同上 | `apply_via` 为 `class_name` / `attribute` / `slot_content` / `data_source` 之一,却只写了 CSS 而没改结构/绑定 | 按 `structure_ops` 施工(改模板 / 绑 `class-name` / 改 formatter / 挂 `data-*`),CSS 换不掉数据驱动点位 |
|
|
55
57
|
| 同上 | 全局覆写与组件 `scoped` 同权重生效失败(收口 `visible:false` / opacity / 几何覆盖不生效) | 按 `priority` 声明提权(`scope_qualified` / `self_plus_important` / `deep_qualified`),不得靠「后置加载」赌注入顺序 |
|
|
56
58
|
| 同上 | 词典 `selector` / `css_vars` 指向的选择器在真实 DOM 不存在(死通道 / 死令牌) | 跑 `node parser/index.js audit` 复核;死令牌双删、死通道补消费者挂载点 |
|
|
59
|
+
| 同上 | 变更集带登记表时 `C < A`(有规范子项未落进 `changes[]` / `visible` / `pending`)——组级台账过了但组内子项被静默丢失 | 跑 `node parser/index.js match --registry`;缺口逐条点名回流设计侧,**禁止采信自报的 `coverage` 数字**(parser 当场重算) |
|
|
57
60
|
|
|
58
61
|
## 选型与硬约束
|
|
59
62
|
|
|
@@ -142,7 +145,7 @@ lint / type-check **过了也不能放过**下列项。发现即改代码,禁
|
|
|
142
145
|
- [ ] 变更集外点位零改动(diff 只含变更集提到的文件/选择器)
|
|
143
146
|
- [ ] `visible: false` 模块代码仍存在(只隐藏不删除)
|
|
144
147
|
- [ ] 未修改 `src/components/` Re* 组件库源码(底线一);未改业务逻辑层 API/状态/事件(底线二)
|
|
145
|
-
- [ ] 两层样式全部落在条目 `style_scope`
|
|
148
|
+
- [ ] 两层样式全部落在条目 `style_scope` 作用域内,无全局泄漏;`!important` 仅用于 `priority.strategy=self_plus_important` 或「对手为组件 scoped 且提权后实测仍平手」两种情形,且带理由注释
|
|
146
149
|
- [ ] 无内联硬编码色值;全部走 CSS 变量(css_vars 层)或 `:deep()` 隔离(custom_style_map 层)
|
|
147
150
|
- [ ] 新增视觉规则在 `dark.scss` `html.dark` 有对应覆盖
|
|
148
151
|
- [ ] 能力缺口(词典缺 ID / 缺字段枚举 / 缺 form)已当场落地并写临时映射,未进清单;`缺口清单` 只含意图缺口(设计侧未给值 / 值非法)且已注明回流设计侧;mapping 临时新增后 `node parser/index.js build --dry` 通过
|
|
@@ -150,11 +153,13 @@ lint / type-check **过了也不能放过**下列项。发现即改代码,禁
|
|
|
150
153
|
- [ ] **映射通道活性**:本轮用到的词典条目,其 `selector` / `css_vars` 在真实 DOM 可命中、CSS 变量有消费者(已跑 `audit` 或逐条核对,无死通道 / 死令牌)
|
|
151
154
|
- [ ] **锚点契约**:变更集 `anchor` 声明的锚点存在;`layer=inner` 的点位已解析到内层文本盒(`.cell` / 文本节点宿主),未错落在宿主盒上
|
|
152
155
|
- [ ] **结构施工**:`apply_via` 非 `style_override` 的点位已按 `structure_ops` 改结构/绑定(`class-name` / formatter / `data-*` / 渲染分支),未只用 CSS 冒充落地;申报了结构增量施工
|
|
153
|
-
- [ ]
|
|
156
|
+
- [ ] **覆写提权 / 同权重失效检查**:`priority` 声明的每条规则都**数过权重**——对手为组件 `<style scoped>` 时本条必须严格更高(**平手即判失效**并提权);对手带 `!important` 时本条必须同样 `!important`,或改走通道变量。收口(`display:none` 等)与几何覆盖必须实测生效,未被组件 `scoped` 反压
|
|
154
157
|
- [ ] **模板源码注释中性**:对模板文件的改动中,注释只描述「这段代码做什么」;语义 ID / 协议步骤号 / 缺口·自举·申报等面向 AI 的纪律条款不得写入模板源码注释(一律留在技能侧文档)
|
|
155
158
|
|
|
156
159
|
### 渲染态验收(变更集含 `oracle` / 效果图有 `data-sid` 时强制)
|
|
157
160
|
|
|
161
|
+
- [ ] **oracle 基线可用**:`oracle.mockup` 与本次主题、页面一致(未复用异主题效果图);`oracle.sids` 与 `changes[]` 双向闭合(无欠声明点位、无「已声明却无基线」点位)——`parser match` 已自动报出,未报错才算通过
|
|
162
|
+
- [ ] **登记表覆盖率闭合**(变更集带登记表时):`match --registry` 输出 `C = A`、无缺口,且无「效果图标注缺口」(恒存在子项在效果图上都有同名 `data-sid`)
|
|
158
163
|
- [ ] 项目同名 `data-sid` 元素已渲染,抽取 computed style 与效果图期望值逐点 diff
|
|
159
164
|
- [ ] diff 结果无未解释缺口;有缺口者已按三类(选择器落空 / 权重被压 / 语境错位)归因并修正,未用 `!important` 掩盖
|
|
160
165
|
- [ ] 验收结果写入最终回复(命中 / 缺口 / 归因 / 修正),不得只报「已完成」
|
|
@@ -145,7 +145,8 @@ node parser/index.js match --batch <changesets-dir>
|
|
|
145
145
|
| `▸ 前置条件 preconditions` | 值成立依赖的宿主前提 | 施工前必须满足,否则「值对了也不生效」 |
|
|
146
146
|
| `▸ 覆写优先级 priority` | 该点位的提权策略 | 按策略取胜,禁止逐点硬堆 `!important` |
|
|
147
147
|
|
|
148
|
-
**验收基线输出**:转换计划末尾另打印 `=== 验收基线 oracle ===`(效果图路径 +
|
|
148
|
+
**验收基线输出**:转换计划末尾另打印 `=== 验收基线 oracle ===`(效果图路径 + 待比对点位)与 `=== 覆盖率 coverage ===`(登记项 R / 适用 A / 已兑现 C)。有 `oracle` → 落地后必须跑第 6 步 `verify`;缺 `oracle` / `coverage` → 输出提示,把「补基线 / 补登记覆盖率」回流设计侧(场景还原应给)。
|
|
149
|
+
**覆盖率校验**:`coverage.covered` 必须等于 `coverage.applicable`、`gaps` 为空,否则打印缺口清单——`C < A` 即**欠声明**(有规范子项未落进 `changes[]`/`visible`/`pending`),属设计侧交付不完整,必须回流,不得在项目侧"猜着补"。
|
|
149
150
|
|
|
150
151
|
### 第 1 步附加 · 挂载点审计(V3.2,静态查三类隐性缺口)
|
|
151
152
|
|
|
@@ -163,6 +164,30 @@ node parser/index.js audit --project <目标项目根目录> [<changeset.json…
|
|
|
163
164
|
|
|
164
165
|
审计报错 = **能力缺口**:当场按 `structure_ops` 补挂载 / 补消费者,交付说明单列申报;不得以「词典没建」为由跳过。审计通过 ≠ 还原达标——**静态审计查不出「值对了、位置不对」**,那要靠第 6 步渲染态验收。
|
|
165
166
|
|
|
167
|
+
### 第 1 步附加(之二)· 登记表覆盖率(子项级细闸 · V3.2)
|
|
168
|
+
|
|
169
|
+
组级台账(`restore-checklist`)只能防「整组忘光」;**组内子项**是否被静默丢失,用登记表计数。
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
node parser/index.js match <cs.json> --registry <restore-registry.json> # 匹配 + 覆盖率当场重算
|
|
173
|
+
node parser/index.js registry <restore-registry.json> [--verbose] # 只核验登记表对词典的通道就绪度
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
| 量 | 定义 | 谁重算 |
|
|
177
|
+
|---|---|---|
|
|
178
|
+
| **R 登记项** | 登记表条数(= 规范里带样式结论的子项数) | 读表 |
|
|
179
|
+
| **A 适用** | `always:true` 的子项 ∪ 效果图已标注同名 `data-sid` 的子项 | parser 当场算 |
|
|
180
|
+
| **C 兑现** | A 中在 `changes[]` / `visible:false` / `pending[].proposed_id` 出现者 | parser 当场算 |
|
|
181
|
+
| **缺口** | `A − C`,逐条列出 `rule + sid + dims`(同组超过 6 条需加 `--verbose` 展开) | parser 当场算 |
|
|
182
|
+
|
|
183
|
+
**关键口径**:A/C **由 parser 以登记表为权威当场重算,设计侧自报的 `coverage` 数字不参与**——所以「把 applicable 写小」蒙混不过去。有缺口 → `exit 1`,属**交付不完整**,回流设计侧补齐后重跑。
|
|
184
|
+
|
|
185
|
+
另有一项**效果图标注缺口**告警:`always:true` 的子项若在效果图里找不到同名 `data-sid`,基线根本抽不出来(效果图标注粒度必须与登记表同分辨率,否则第 6 步的 oracle 必然残缺)。
|
|
186
|
+
|
|
187
|
+
`registry` 子命令单独回答「这张登记表里有多少必登项在项目侧真有承载字段」:
|
|
188
|
+
- **硬缺口**(引用了词典不存在的点位且未标 `pending`)→ `exit 1`;
|
|
189
|
+
- **通道缺口**(设计侧维度在词典无对应字段)→ 列出,属**项目侧补词典**事项,补齐后还原度才可达 100%。
|
|
190
|
+
|
|
166
191
|
### 第 2 步 · 结构先行(visible / position / form)
|
|
167
192
|
|
|
168
193
|
**先改结构,后改样式**——结构变了选择器才稳定,样式才有处可落。
|
|
@@ -242,7 +267,16 @@ node parser/index.js audit --project <目标项目根目录> [<changeset.json…
|
|
|
242
267
|
|
|
243
268
|
### 第 3 步附加 · 设计层落点文件(强制)
|
|
244
269
|
|
|
245
|
-
所有「新增的 CSS 规则」一律写入 **`src/styles/design-shell.scss`**(出厂空文件,经 `index.scss` 末位 `@use`
|
|
270
|
+
所有「新增的 CSS 规则」一律写入 **`src/styles/design-shell.scss`**(出厂空文件,经 `index.scss` 末位 `@use` 加载),或写入页面级 `<style scoped>`;**不得**写进 `sidebar.scss` / `element-plus.scss` / `theme.scss` / `lay-*.vue` 等出厂文件去表达某张效果图的结论。
|
|
271
|
+
|
|
272
|
+
⚠ **「末位加载」只决定「同源、同特异度」时的先后,压不过两类对手**——这是「规则明明写了却不生效」的头号成因:
|
|
273
|
+
|
|
274
|
+
| 对手 | 为什么本文件会输 | 取胜方式 |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| **组件 `<style scoped>`** | Vite 下组件样式在全局样式**之后**动态注入;scoped 选择器末元素带 `[data-v-x]`(按一个类计权)。同特异度时后注入者胜 → 本文件出局 | 提权(≥3 层作用域限定 / 叠加属性伪类)——**不是靠文件顺序** |
|
|
277
|
+
| **出厂规则里的 `!important`** | `!important` 高于任何特异度,提权无效 | 本点位也必须 `!important`,或改走通道变量 |
|
|
278
|
+
|
|
279
|
+
每写一条新规则前,先按第 1 步输出的 `▸ 覆写优先级 priority` 判定对手类型与取胜方式(见下节)。
|
|
246
280
|
|
|
247
281
|
写入纪律:
|
|
248
282
|
|
|
@@ -252,6 +286,27 @@ node parser/index.js audit --project <目标项目根目录> [<changeset.json…
|
|
|
252
286
|
- 出厂 `--vts-theme-menu-active-before` 默认 `transparent` 且无消费者 → 声明 `form=indicator_*` 时必须在此文件内**同时**落地「关胶囊令牌 `menu_active_bg: transparent`」与「新建 `::after` 消费者」,缺一即未施工成功。
|
|
253
287
|
- **自举(项目被还原到出厂时必做,幂等)**:文件不存在 → 新建(仅保留文件头纪律注释,无任何设计结论)+ 在 `src/styles/index.scss` **末位**补 `@use 'design-shell';`(必须末位,靠后置压出厂)。此动作属「补通道」,交付说明单列申报。**文件缺失时径直把设计规则写进 `element-plus.scss` / `sidebar.scss` / `theme.scss` = 污染出厂文件,禁止**(这也是「设计还原度为零」的常见成因:规则无处可落)。
|
|
254
288
|
|
|
289
|
+
### 第 3 步附加(之二)· 覆写优先级消费(V3.2 硬步骤)
|
|
290
|
+
|
|
291
|
+
第 1 步输出的 `▸ 覆写优先级 priority` **不是提示,是落地写法的硬要求**。每落一条规则前先按策略决定写法;写法错了,规则就是「写了不生效」。历史缺口「面包屑 `visible:false` 写了 `display:none` 却照旧显示」「页签关闭钮 `opacity` 写了却仍要 hover 才显」全部源于此——两条规则本身都没写错,是**赢了才是硬道理**这一步没人做。
|
|
292
|
+
|
|
293
|
+
| `priority.strategy` | 对手 | 必须的写法 | 自检判据 |
|
|
294
|
+
|---|---|---|---|
|
|
295
|
+
| `scope_qualified` | 无(词典 selector 已带作用域限定) | 按词典 selector 原样落 | 若实测仍被覆盖 → 升格按 `deep_qualified` 处理 |
|
|
296
|
+
| `deep_qualified` | 组件 `<style scoped>`(平手 + 后注入) | **禁止裸类名**。≥3 层作用域限定(如 `html .a .b`)或叠加属性/伪类;仍平手才对本条补 `!important` | 写前自问:这条与组件 scoped 谁特异度高?**平手即错** |
|
|
297
|
+
| `self_plus_important` | 出厂规则带 `!important` | 本条必须 `!important` + 注释写明「对手带 `!important`」;能改走通道变量时优先改通道 | 只抬特异度无效,别白费 |
|
|
298
|
+
|
|
299
|
+
落地写法模板(每条规则标注来源 + 取胜依据):
|
|
300
|
+
|
|
301
|
+
```scss
|
|
302
|
+
/* <语义 ID> · <变更条目> · 取胜依据:deep_qualified(对手 lay-*.vue scoped) */
|
|
303
|
+
html .navbar .navbar-breadcrumb-wrap {
|
|
304
|
+
display: none; /* visible:false · 存在性收口,不删出厂代码行 */
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**`!important` 只允许出现在两种情形**:① `strategy = self_plus_important`(对手带 `!important`);② 对手是组件 scoped 且提权后**实测**仍平手。其余一律靠作用域限定解决——逐点堆 `!important` 会让点位互相耦合、副作用累积成漂移,也就是「越改越乱」的机理。
|
|
309
|
+
|
|
255
310
|
### 第 4 步 · L3 新模块处理(分级布局)
|
|
256
311
|
|
|
257
312
|
- **L3 是施工指令,不是待办**:词典未命中的点位一律当场落地、交付说明申报「补映射」,不留待确认。新模块按「定制布局」三步走:① 基于全局规范生成结构(继承命名/作用域/组件规范)② 原子层字段(色彩/圆角/组件级)从 `basic/` + `component/` 词典借力 ③ 场景专属点位生成后**立即写入 mapping/ 对应场景域**(临时映射),累计复用 ≥3 次建议升级标准(由人工评审)。
|
|
@@ -263,7 +318,7 @@ node parser/index.js audit --project <目标项目根目录> [<changeset.json…
|
|
|
263
318
|
|
|
264
319
|
- [ ] 变更集外的任何点位**零改动**(diff 只含变更集提到的文件/选择器)
|
|
265
320
|
- [ ] `visible: false` 的模块代码仍存在(只是隐藏)
|
|
266
|
-
- [ ]
|
|
321
|
+
- [ ] **`!important` 使用合规**:新增 `!important` 仅允许两种情形——① 词典 `priority.strategy = self_plus_important`(对手带 `!important`);② 对手为组件 scoped 且提权后**实测**仍平手;且每条带理由注释。其余一律靠作用域限定解决。无内联 style 硬编码色值(全部走变量)
|
|
267
322
|
- [ ] 未修改 `src/components/`(Re* 组件库源码——**底线一**)
|
|
268
323
|
- [ ] 未改动业务逻辑层(API/状态/事件——**底线二**;样式与结构通道内的 DOM 调整除外)
|
|
269
324
|
- [ ] 两层样式全部落在 `style_scope` 作用域内,无全局泄漏
|
|
@@ -286,9 +341,12 @@ node parser/index.js audit --project <目标项目根目录> [<changeset.json…
|
|
|
286
341
|
- 表头 / 行 / 分页都在视口内,仅表格体内滚动,无外层滚动条、无拉伸 / 塌陷 / 双滚动条,`tableHeight > 0`;
|
|
287
342
|
- **结构层 diff 为空**:`lay-content/index.vue` 的 `.main-content[data-content-mode]` 与 `element-plus.scss` 的 `[data-fill]` 未被改动;同时确认没有「为保高度而放弃设计还原」(出厂卡片 / 内边距 / 内部排布原样未动而变更集声明了改动)——两个方向任一命中即返工
|
|
288
343
|
- [ ] **映射通道活性检查(杂必查)**:样式落地引用的每个 CSS 变量必须在项目样式中**存在消费者**(`变量名` 有 `var(--xxx)` 引用点);只定义不消费的变量 = 通道死路,翻译再准也不生效(menu_active_indicator 曾因 --vts-theme-menu-active-before 无消费者而「映射了却没效果」)。施工时 grep 核对,无消费者时改走消费者存在的作用域或补挂载点并申报。
|
|
344
|
+
- [ ] **同权重失效检查(V3.2 · 专查「写了不生效」)**:逐条核对设计层新增规则是否真能取胜——① 对手是组件 `<style scoped>` 时,本条特异度必须**严格高于**它(**平手即错**,因为组件样式在全局之后注入);② 对手带 `!important` 时本条必须同样 `!important`,或改走通道变量。**判定方法(必须真数,不许目测)**:对每条新规则与其对手分别数出选择器权重,权重相同一律判为「失效」并提权。历史缺口全部命中本项:面包屑 `display:none`(与组件 scoped 同为 0,2,0)→ 照旧显示;页签关闭钮 `opacity`(同为 0,5,0)→ 仍要 hover 才显;页签项 `padding`(对手带 `!important`)→ 无论怎么抬特异度都改不动
|
|
345
|
+
- [ ] **oracle 基线可用性检查**:变更集带 `oracle` 时——① `oracle.mockup` 必须与**本次主题、本次页面**一致(禁止复用异主题效果图:主题错配会让逐点比对必然全红);② `oracle.sids` 与 `changes[]` **双向闭合**:效果图里有、changes[] 未声明 = **欠声明**(该点位永远不可能被还原);changes[] 已声明、效果图无 sid = **无基线**(落地后无法机器验收)。两类出现即回流设计侧补齐,**不得直接开工**(`parser match` 已自动报出)
|
|
289
346
|
- [ ] **dark.scss 暗色同步**:新增视觉规则在 `html.dark` 有对应覆盖
|
|
290
347
|
- [ ] **缺口清单只含意图缺口**:清单每一项都必须是「设计侧未给值 / 值非法」且已注明「回流设计侧补齐」;能力缺口(词典缺 ID / 缺字段枚举 / 缺 form)不得出现在清单里——出现即把可落地项搁置
|
|
291
348
|
- [ ] **台账逐行兑现对账(对账闸门)**:把设计侧交付的提取台账逐行过一遍,每行必须有落地结论——已落地(写明落在哪个文件/选择器)或已入缺口清单(写明缺失字段)。存在「台账有行、落地无结论」的行 = 交付不完整,与红线同级
|
|
349
|
+
- [ ] **登记表覆盖率对账(子项级细闸,V3.2)**:带登记表交付时必须跑 `match --registry`——`C = A` 且无缺口才可交付;同时确认无「效果图标注缺口」(`always:true` 子项在效果图上都有同名 `data-sid`)。**禁止采信设计侧自报的 `coverage` 数字**(parser 会当场重算,自报数不参与);缺口逐条点名回流设计侧
|
|
292
350
|
- [ ] **零搁置交付**:最终回复不含「待确认」类停顿项;交付说明的「补映射 / 补通道 / 缺口回流」三段齐全(无内容写「无」)
|
|
293
351
|
- [ ] `node parser/index.js build --dry` 通过(mapping 若有临时新增)
|
|
294
352
|
|
|
@@ -334,7 +392,7 @@ node parser/index.js audit --project <目标项目根目录> [<changeset.json…
|
|
|
334
392
|
## 端到端回归(改 parser/mapping 后必跑)
|
|
335
393
|
|
|
336
394
|
```bash
|
|
337
|
-
node parser/regression.js # 一键跑完 A-
|
|
395
|
+
node parser/regression.js # 一键跑完 A-O 十五步(自动断言退出码与关键输出,任一失败 exit 1 并回显详情)
|
|
338
396
|
npm run regression # 等价写法
|
|
339
397
|
```
|
|
340
398
|
|
|
@@ -353,9 +411,12 @@ node parser/index.js build --dry # I. 产物链自检
|
|
|
353
411
|
node parser/index.js match parser/fixtures/changeset-v32-fields.json # J. V3.2 声明维:exit 0;输出「结构施工 structure_ops」与 layer / apply_via
|
|
354
412
|
node parser/index.js audit --project parser/fixtures/fake-project parser/fixtures/changeset-v32-fields.json # K. 静态审计:夹具项目存在挂载点缺失/死通道 → exit 1
|
|
355
413
|
node parser/index.js verify --oracle parser/fixtures/verify/oracle.json --actual parser/fixtures/verify/actual.json # L. 渲染态验收:夹具含三类故意缺口 → exit 1;归因计数 选择器落空1/权重被压1/语境错位2
|
|
414
|
+
node parser/index.js match --registry parser/fixtures/registry-sample.json parser/fixtures/changeset-registry-gap.json # M. 登记表覆盖率细闸:夹具欠声明 2 条恒存在子项 → exit 1
|
|
415
|
+
node parser/index.js registry parser/fixtures/registry-sample.json # N. 登记表通道核验:点位全部命中 → exit 0;输出含「登记表通道核验」
|
|
416
|
+
node parser/index.js registry parser/fixtures/registry-bad.json # O. 登记表硬缺口:引用不存在且未标 pending 的点位 → exit 1
|
|
356
417
|
```
|
|
357
418
|
|
|
358
|
-
通过标准:regression.js 输出 `A✔ B✔ …
|
|
419
|
+
通过标准:regression.js 输出 `A✔ B✔ … O✔` 且 exit 0;分步跑时各步符合注释预期。夹具含义见各文件 `$comment`。
|
|
359
420
|
|
|
360
421
|
## 配套工具(作者/维护侧)
|
|
361
422
|
|