@qilitt-mickey/vue3-temp-skill 1.1.76 → 1.2.2

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 (86) hide show
  1. package/README.md +99 -82
  2. package/SKILL.md +65 -28
  3. package/bin/cli.js +5 -6
  4. package/mapping/admin.json +60 -0
  5. package/package.json +5 -13
  6. package/references/adapters/_new-adapter.md +88 -0
  7. package/references/adapters/vue-antdv.md +110 -0
  8. package/references/adapters/vue-element-plus.md +132 -0
  9. package/references/design-apply.md +259 -122
  10. package/scripts/check-landing.mjs +419 -0
  11. package/scripts/modules.json +2 -2
  12. package/dist/mapping-type.d.ts +0 -33
  13. package/dist/theme-vars.js +0 -62
  14. package/dist/theme-vars.ts +0 -113
  15. package/dist/theme.css +0 -73
  16. package/mapping/basic/color.json +0 -35
  17. package/mapping/basic/radius.json +0 -17
  18. package/mapping/basic/semantic-values.json +0 -72
  19. package/mapping/basic/spacing.json +0 -21
  20. package/mapping/basic/typography.json +0 -21
  21. package/mapping/component/button.json +0 -90
  22. package/mapping/component/card.json +0 -31
  23. package/mapping/component/input.json +0 -42
  24. package/mapping/component/overlay.json +0 -79
  25. package/mapping/component/pagination.json +0 -34
  26. package/mapping/component/table.json +0 -42
  27. package/mapping/component/tag.json +0 -48
  28. package/mapping/layout/breakpoint.json +0 -20
  29. package/mapping/layout/shell-geometry.json +0 -26
  30. package/mapping/scenes/admin/content.json +0 -497
  31. package/mapping/scenes/admin/layout.json +0 -577
  32. package/parser/fixtures/changeset-fullpage-restore.json +0 -259
  33. package/parser/fixtures/changeset-gap-block.json +0 -24
  34. package/parser/fixtures/changeset-legacy.json +0 -11
  35. package/parser/fixtures/changeset-neg1.json +0 -8
  36. package/parser/fixtures/changeset-neg2.json +0 -10
  37. package/parser/fixtures/changeset-neg3.json +0 -22
  38. package/parser/fixtures/changeset-neg4-g8.json +0 -15
  39. package/parser/fixtures/changeset-neg5-g8-stub.json +0 -26
  40. package/parser/fixtures/changeset-neg6-g9-identity.json +0 -39
  41. package/parser/fixtures/changeset-neg7-g10-dims.json +0 -57
  42. package/parser/fixtures/changeset-neg8-g11-oracle.json +0 -57
  43. package/parser/fixtures/changeset-neg9-g6-token-chain.json +0 -34
  44. package/parser/fixtures/changeset-pos-g6-token-chain.json +0 -35
  45. package/parser/fixtures/changeset-pos-theme-neutral.json +0 -264
  46. package/parser/fixtures/changeset-pos-theme-nometa.json +0 -260
  47. package/parser/fixtures/changeset-pos.json +0 -50
  48. package/parser/fixtures/changeset-registry-gap.json +0 -29
  49. package/parser/fixtures/changeset-v32-fields.json +0 -71
  50. package/parser/fixtures/fake-project/src/styles/app.scss +0 -5
  51. package/parser/fixtures/fake-project/src/views/list.vue +0 -13
  52. package/parser/fixtures/registry-bad.json +0 -11
  53. package/parser/fixtures/registry-g6-token-chain.json +0 -10
  54. package/parser/fixtures/registry-sample.json +0 -15
  55. package/parser/fixtures/verify/actual-clean.json +0 -35
  56. package/parser/fixtures/verify/actual.json +0 -33
  57. package/parser/fixtures/verify/mockup-baseline-admin-list.html +0 -27
  58. package/parser/fixtures/verify/mockup-g6-content.html +0 -12
  59. package/parser/fixtures/verify/mockup-neutral.html +0 -26
  60. package/parser/fixtures/verify/mockup-nometa.html +0 -26
  61. package/parser/fixtures/verify/oracle.json +0 -37
  62. package/parser/fixtures/verify/snapshot-latest.json +0 -103
  63. package/parser/generator.js +0 -178
  64. package/parser/index.js +0 -2706
  65. package/parser/loader.js +0 -66
  66. package/parser/override.js +0 -119
  67. package/parser/regression.js +0 -301
  68. package/parser/validator.js +0 -206
  69. package/parser/watch.js +0 -103
  70. package/references/steps/maintenance.md +0 -31
  71. package/references/steps/regression.md +0 -49
  72. package/references/steps/step0-preflight.md +0 -58
  73. package/references/steps/step1-match.md +0 -67
  74. package/references/steps/step1b-audit.md +0 -23
  75. package/references/steps/step1c-registry.md +0 -29
  76. package/references/steps/step2-structure.md +0 -73
  77. package/references/steps/step3-style.md +0 -38
  78. package/references/steps/step3b-shell.md +0 -63
  79. package/references/steps/step3c-priority.md +0 -26
  80. package/references/steps/step4-l3.md +0 -14
  81. package/references/steps/step5-gates.md +0 -65
  82. package/references/steps/step6-verify.md +0 -77
  83. package/scripts/apply-final-gate.mjs +0 -170
  84. package/scripts/check.mjs +0 -207
  85. package/scripts/design-audit.mjs +0 -1707
  86. package/scripts/validate.mjs +0 -106
package/parser/loader.js DELETED
@@ -1,66 +0,0 @@
1
- /**
2
- * loader.js — mapping/ 目录读取与合并
3
- *
4
- * 递归遍历 mapping/ 下所有 .json 文件,按目录层级合并为完整映射数据集。
5
- * 合并顺序(后加载覆盖先加载,同 ID 时场景级扩展全局定义):
6
- * basic/ → layout/ → component/ → scenes/<scene>/
7
- */
8
-
9
- import { readdirSync, readFileSync, statSync } from "node:fs";
10
- import path from "node:path";
11
- import { fileURLToPath } from "node:url";
12
-
13
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
- export const MAPPING_ROOT = path.resolve(__dirname, "..", "mapping");
15
-
16
- /** 递归收集 JSON 文件(按目录名字典序,保证合并顺序稳定) */
17
- function collectJsonFiles(dir) {
18
- const out = [];
19
- for (const name of readdirSync(dir).sort()) {
20
- if (name.startsWith("_") || name.startsWith("."))
21
- continue;
22
- const full = path.join(dir, name);
23
- const st = statSync(full);
24
- if (st.isDirectory())
25
- out.push(...collectJsonFiles(full));
26
- else if (name.endsWith(".json"))
27
- out.push(full);
28
- }
29
- return out;
30
- }
31
-
32
- /**
33
- * 构建映射数据集。
34
- * @returns {{ files: Record<string, object>, fileCount: number, ids: Record<string, {file, meta}> }}
35
- */
36
- export function buildMappingDataset() {
37
- const files = {};
38
- const idSource = {};
39
-
40
- for (const filePath of collectJsonFiles(MAPPING_ROOT)) {
41
- const rel = path.relative(MAPPING_ROOT, filePath).replaceAll("\\", "/");
42
- let data;
43
- try {
44
- data = JSON.parse(readFileSync(filePath, "utf-8"));
45
- }
46
- catch (e) {
47
- throw new Error(`[mapping/${rel}] JSON 解析失败:${e.message}`);
48
- }
49
- files[rel] = data;
50
-
51
- if (data.mappings) {
52
- for (const id of Object.keys(data.mappings)) {
53
- if (id.startsWith("$"))
54
- continue;
55
- if (idSource[id])
56
- console.warn(` ⚠ 语义 ID「${id}」在 ${idSource[id].file} 与 ${rel} 中重复,后者覆盖`);
57
- idSource[id] = { file: rel, meta: data.meta || {} };
58
- }
59
- }
60
- }
61
-
62
- return { files, fileCount: Object.keys(files).length, idSource };
63
- }
64
-
65
- /** 供 validator 使用的别名(保持独立可测) */
66
- export { buildMappingDataset as buildMappingDatasetForValidator };
@@ -1,119 +0,0 @@
1
- /**
2
- * override.js — 业务级映射覆写 API(可选扩展)
3
- *
4
- * 用途:业务项目存在少量自定义映射需求时,通过本 API 在运行时覆写
5
- * 指定字段,无需在业务侧维护完整 mapping/ 目录。
6
- *
7
- * 设计约束(严格按方案):
8
- * 1. 浅覆写 + 深合并:覆写只落在传入的 key 上,未提及的映射原样保留。
9
- * 2. 不可变:返回新对象,不修改传入的 themeVars / 映射数据集。
10
- * 3. 白名单校验:只允许覆写词典中已存在的 key——防止业务侧拼错字段名
11
- * 造成静默失效(与 parser「守门员」职责一致:错误数据进不了链路)。
12
- * 需要放开时传 { strict: false },未知 key 将产生告警但不阻断。
13
- * 4. 本 API 属链路 A(基线注入)配套能力:服务于「空白新项目从零接入
14
- * Skill 主题体系」的场景;成熟模板项目(如 vue3-web-temp)不引入。
15
- *
16
- * 用法:
17
- * import { themeVars, overrideMapping } from "@qilitt-mickey/vue3-temp-skill/dist/theme-vars.js";
18
- * const custom = overrideMapping(themeVars, { color: { brandPrimary: "#ff6600" } });
19
- *
20
- * 或对语义 ID 映射条目覆写(parser 内部使用):
21
- * const entry = overrideMapping(baseEntry, { css_vars: { menu_bg: "--custom-var" } }, { id: "admin.layout.sidebar.menu" });
22
- */
23
-
24
- /** 递归合并覆写:override 中的值覆盖 base,对象做深合并,其余类型直接替换 */
25
- function deepMerge(base, override) {
26
- const out = Array.isArray(base) ? [...base] : { ...base };
27
- for (const [k, v] of Object.entries(override ?? {})) {
28
- if (v !== null && typeof v === "object" && !Array.isArray(v)
29
- && out[k] !== null && typeof out[k] === "object" && !Array.isArray(out[k])) {
30
- out[k] = deepMerge(out[k], v);
31
- }
32
- else {
33
- out[k] = v;
34
- }
35
- }
36
- return out;
37
- }
38
-
39
- /**
40
- * 覆写映射对象。
41
- * @param {object} base 基础映射(themeVars / 单个映射条目 / 整个 dataset)
42
- * @param {object} override 覆写片段,结构与 base 对应部分同构
43
- * @param {{ id?: string, strict?: boolean }} [opts]
44
- * - id:被覆写对象的标识(用于错误提示,如语义 ID 名)
45
- * - strict:默认 true——override 中出现 base 不存在的 key 时抛错
46
- * @returns {object} 新对象(base 不被修改)
47
- */
48
- export function overrideMapping(base, override, opts = {}) {
49
- const { id = "mapping", strict = true } = opts;
50
- if (base === null || typeof base !== "object")
51
- throw new TypeError(`overrideMapping: base 必须为对象(${id})`);
52
- if (override === null || override === undefined)
53
- return base;
54
- if (typeof override !== "object")
55
- throw new TypeError(`overrideMapping: override 必须为对象(${id})`);
56
-
57
- // 白名单校验:只允许覆写已存在的 key(拼错字段名直接暴露)
58
- for (const k of Object.keys(override)) {
59
- if (!(k in base)) {
60
- const msg = `overrideMapping: 「${k}」在 ${id} 中不存在,无法覆写${strict ? "(strict 模式,疑似字段名拼写错误)" : "(已忽略)"}`;
61
- if (strict)
62
- throw new Error(msg);
63
- console.warn(` ⚠ ${msg}`);
64
- }
65
- }
66
-
67
- return deepMerge(base, override);
68
- }
69
-
70
- /**
71
- * 便捷封装:批量覆写多个语义 ID 条目。
72
- * @param {Record<string, object>} index 合并索引(id → 条目),如 buildMergedIndex 结果
73
- * @param {Record<string, object>} overrides { 语义ID: 覆写片段 }
74
- * @param {{ strict?: boolean }} [opts]
75
- * @returns {Record<string, object>} 新索引
76
- */
77
- export function overrideMappings(index, overrides, opts = {}) {
78
- const out = { ...index };
79
- for (const [id, patch] of Object.entries(overrides)) {
80
- if (!out[id]) {
81
- const msg = `overrideMappings: 语义 ID「${id}」不在词典中,无法覆写`;
82
- if (opts.strict !== false)
83
- throw new Error(msg);
84
- console.warn(` ⚠ ${msg}`);
85
- continue;
86
- }
87
- out[id] = overrideMapping(out[id], patch, { id, strict: opts.strict !== false });
88
- }
89
- return out;
90
- }
91
-
92
- // ---- CLI 独立执行:对一份 changeset 计算覆写结果(调试用)----
93
- // 用法:node parser/override.js <overrides.json>
94
- // overrides.json 形如 { "admin.layout.sidebar.menu": { "css_vars": { "menu_bg": "--x" } } }
95
- if (process.argv[1] && process.argv[1].endsWith("override.js")) {
96
- const { readFileSync } = await import("node:fs");
97
- const { buildMappingDataset } = await import("./loader.js");
98
- const { buildMergedIndex } = await import("./validator.js");
99
- const file = process.argv[2];
100
- if (!file) {
101
- console.error("用法:node parser/override.js <overrides.json>");
102
- process.exit(1);
103
- }
104
- try {
105
- const overrides = JSON.parse(readFileSync(file, "utf-8"));
106
- const dataset = buildMappingDataset();
107
- const index = buildMergedIndex(dataset);
108
- const merged = overrideMappings(index, overrides);
109
- const changed = Object.keys(overrides).filter(id => merged[id]);
110
- console.log(`✔ 覆写成功 ${changed.length} 条:`);
111
- for (const id of changed)
112
- console.log(` - ${id}`);
113
- console.log(JSON.stringify(Object.fromEntries(changed.map(id => [id, merged[id]])), null, 2));
114
- }
115
- catch (e) {
116
- console.error(`✖ 覆写失败:${e.message}`);
117
- process.exit(1);
118
- }
119
- }
@@ -1,301 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * A-T 端到端一键回归(作者侧维护工具,不随技能分发)
5
- *
6
- * 用法(cwd 任意,脚本内部已固定为 skill 根目录执行):
7
- * node parser/regression.js
8
- * npm run regression
9
- *
10
- * 步骤与预期同 references/design-apply.md「端到端回归」一一对应:
11
- * A validator 映射词典全量校验 → exit 0
12
- * B list 场景清单与版本头 → exit 0
13
- * C match 正例 → exit 0 且输出含 #245BFF(语义值译表解析)
14
- * C2 match 缺口阻断(L3 点位 + 字段级未命中) → exit 1 且输出含「缺口清单」(缺口不静默放行)
15
- * D match 负例1(缺 schema) → exit 1
16
- * E match 负例2(mapping_version 不符) → exit 1
17
- * F match 负例3(禁词阻断) → exit 1
18
- * G match 旧协议兼容 → exit 0 且输出含 ⚠ 告警
19
- * H match 整页场景还原样例(mode=restore)→ exit 0 且输出含 #245BFF 与「G8 模式闸」(壳层+内容区全模块 L1)
20
- * P match G8 负例(mode=restore 缺 oracle/coverage)→ exit 1 且输出含「G8 模式闸」(伪还原变更集拦截)
21
- * Q match G8 强化负例(mode=restore 但 mockup/registry 为空壳)→ exit 1 且输出含「G8 模式闸」(有壳无芯伪基线拦截)
22
- * R match G9 负例(scheme=tangerine 却引 default 方案登记表 = 身份错配)→ exit 1 且输出含「G9 登记表身份闸」(分母偷换拦截)
23
- * S match G10 负例(header_cell ID 已声明但缺 text_align/nowrap 维度)→ exit 1 且输出含「G10」与「缺 dims=」(点位在细节丢拦截)
24
- * T match G11 负例(橘橙主色配钴蓝效果图且无 theme_mapping = 主题错配)→ exit 1 且输出含「G11」与「主题错配」(基线断裂拦截)
25
- * I build --dry 产物链自检 → exit 0 且输出含 dry-run
26
- * J match 三声明维度 → exit 0 且输出含「结构施工 structure_ops」
27
- * K audit 挂载点 / 死通道审计 → exit 1(夹具项目故意存在挂载点缺失 / 死通道)
28
- * L verify 渲染态逐点验收 → exit 1 且归因各计其数(夹具含故意缺口:三类渲染缺口 + G12 存在性 + G14 状态 + G15 孤儿)
29
- * M match 登记表覆盖率细闸 → exit 1(夹具故意欠声明两条恒存在子项)
30
- * N registry 登记表通道核验 → exit 0 且输出含「登记表通道核验」
31
- * O registry 硬缺口负例 → exit 1(引用了不存在且未标 pending 的点位)
32
- * U verify 残差豁免 + 收口 + 状态 + 无孤儿正例(clean 夹具)→ exit 0 且输出含「已申报残差」「已收口」「状态断言 1/1 命中」(申报点位不计缺口、收口/状态绿灯、无孤儿)
33
- * V verify 快照回归(snapshot → baseline)→ clean 快照为基线、缺口夹具为实测 → exit 1 且输出含「基线漂移」(上轮命中本轮退步必红)
34
- *
35
- * 任一步骤失败 → 本脚本 exit 1,并回显该步骤完整输出便于定位。
36
- */
37
-
38
- import { spawnSync } from "node:child_process";
39
- import path from "node:path";
40
- import { fileURLToPath } from "node:url";
41
- import { existsSync } from "node:fs";
42
-
43
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
44
- const ROOT = path.resolve(__dirname, "..");
45
- const INDEX = path.join(__dirname, "index.js");
46
- const VALIDATOR = path.join(__dirname, "validator.js");
47
- const FIXTURES = path.join(__dirname, "fixtures");
48
-
49
- // 前置自检:fixtures 缺失(如被误删或处于用户安装副本)直接说明并退出
50
- if (!existsSync(FIXTURES)) {
51
- console.error("✖ 未找到 parser/fixtures/ —— 回归夹具缺失。");
52
- console.error(" 本工具是作者侧维护工具,仅在 skill 仓库内可用;");
53
- console.error(" 若在仓库内见到此提示,请检查 fixtures 目录是否被误删。");
54
- process.exit(1);
55
- }
56
-
57
- const steps = [
58
- {
59
- key: "A",
60
- name: "validator 映射词典全量校验",
61
- script: VALIDATOR,
62
- args: [],
63
- expect: { code: 0 },
64
- },
65
- {
66
- key: "B",
67
- name: "list 场景清单与版本头",
68
- script: INDEX,
69
- args: ["list", "--scene", "admin", "--verbose"],
70
- expect: { code: 0 },
71
- },
72
- {
73
- key: "C",
74
- name: "match 正例(译表解析 + pending)",
75
- script: INDEX,
76
- args: ["match", path.join(FIXTURES, "changeset-pos.json")],
77
- expect: { code: 0, includes: "#245BFF" },
78
- },
79
- {
80
- key: "C2",
81
- name: "match 缺口阻断(词典未登记点位 L3 + 字段级未命中 → exit 1,缺口清单不静默放行)",
82
- script: INDEX,
83
- args: ["match", path.join(FIXTURES, "changeset-gap-block.json")],
84
- expect: { code: 1, includes: "缺口清单" },
85
- },
86
- {
87
- key: "D",
88
- name: "match 负例1(缺 schema → 拒绝)",
89
- script: INDEX,
90
- args: ["match", path.join(FIXTURES, "changeset-neg1.json")],
91
- expect: { code: 1 },
92
- },
93
- {
94
- key: "E",
95
- name: "match 负例2(mapping_version 不匹配 → 拒绝)",
96
- script: INDEX,
97
- args: ["match", path.join(FIXTURES, "changeset-neg2.json")],
98
- expect: { code: 1 },
99
- },
100
- {
101
- key: "F",
102
- name: "match 负例3(禁词 → 阻断)",
103
- script: INDEX,
104
- args: ["match", path.join(FIXTURES, "changeset-neg3.json")],
105
- expect: { code: 1 },
106
- },
107
- {
108
- key: "G",
109
- name: "match 旧协议兼容(exit 0 + 双告警)",
110
- script: INDEX,
111
- args: ["match", path.join(FIXTURES, "changeset-legacy.json")],
112
- expect: { code: 0, includes: "⚠" },
113
- },
114
- {
115
- key: "H",
116
- name: "match 整页场景还原样例(mode=restore 基线三件套 + 壳层+内容区全模块 L1 + 数值字面量透传)",
117
- script: INDEX,
118
- args: ["match", path.join(FIXTURES, "changeset-fullpage-restore.json")],
119
- expect: { code: 0, includes: ["#245BFF", "G8 模式闸"] },
120
- },
121
- {
122
- key: "I",
123
- name: "build --dry 产物链自检",
124
- script: INDEX,
125
- args: ["build", "--dry"],
126
- expect: { code: 0, includes: "dry-run" },
127
- },
128
- {
129
- key: "J",
130
- name: "match 三声明维度 + coverage 覆盖率(apply_via / layer / structure_ops / coverage)",
131
- script: INDEX,
132
- args: ["match", path.join(FIXTURES, "changeset-v32-fields.json")],
133
- expect: { code: 0, includes: ["结构施工 structure_ops", "验收基线 oracle", "覆盖率 coverage"] },
134
- },
135
- {
136
- key: "K",
137
- name: "audit 挂载点 / 死通道审计(夹具项目应报出问题 → exit 1)",
138
- script: INDEX,
139
- args: ["audit", "--project", path.join(FIXTURES, "fake-project"), path.join(FIXTURES, "changeset-v32-fields.json")],
140
- expect: { code: 1, includes: "挂载 / 通道问题" },
141
- },
142
- {
143
- key: "L",
144
- name: "verify 渲染态逐点验收(夹具含四类缺口 + G12 存在性 + G14 状态 + G15 孤儿 → exit 1)",
145
- script: INDEX,
146
- args: ["verify", "--oracle", path.join(FIXTURES, "verify", "oracle.json"), "--actual", path.join(FIXTURES, "verify", "actual.json")],
147
- expect: { code: 1, includes: ["选择器落空 2 / 权重被压 3 / 语境错位 2", "存在性缺口 1", "状态缺口 1", "孤儿点位 1"] },
148
- },
149
- {
150
- key: "M",
151
- script: INDEX,
152
- args: ["match", "--registry", path.join(FIXTURES, "registry-sample.json"), path.join(FIXTURES, "changeset-registry-gap.json")],
153
- expect: { code: 1, includes: ["登记表覆盖率", "缺口 2"] },
154
- },
155
- {
156
- key: "N",
157
- name: "registry 登记表通道核验(点位全部命中 → exit 0)",
158
- script: INDEX,
159
- args: ["registry", path.join(FIXTURES, "registry-sample.json")],
160
- expect: { code: 0, includes: ["登记表通道核验", "登记项 6 条"] },
161
- },
162
- {
163
- key: "O",
164
- name: "registry 硬缺口负例(引用不存在且未标 pending 的点位 → exit 1)",
165
- script: INDEX,
166
- args: ["registry", path.join(FIXTURES, "registry-bad.json")],
167
- expect: { code: 1, includes: "硬缺口" },
168
- },
169
- {
170
- key: "P",
171
- name: "match G8 模式闸负例(mode=restore 缺 oracle/coverage = 伪还原变更集 → exit 1)",
172
- script: INDEX,
173
- args: ["match", path.join(FIXTURES, "changeset-neg4-g8.json")],
174
- expect: { code: 1, includes: "G8 模式闸" },
175
- },
176
- {
177
- key: "Q",
178
- name: "match G8 强化负例(mode=restore 但 oracle.mockup / coverage.registry 为空壳 = 有壳无芯 → exit 1)",
179
- script: INDEX,
180
- args: ["match", path.join(FIXTURES, "changeset-neg5-g8-stub.json")],
181
- expect: { code: 1, includes: "G8 模式闸" },
182
- },
183
- {
184
- key: "R",
185
- name: "match G9 负例(scheme=tangerine 却引 default 方案登记表 = 身份错配 → exit 1)",
186
- script: INDEX,
187
- args: ["match", path.join(FIXTURES, "changeset-neg6-g9-identity.json")],
188
- expect: { code: 1, includes: "G9 登记表身份闸" },
189
- },
190
- {
191
- key: "S",
192
- name: "match G10 负例(header_cell ID 已声明但缺 text_align/nowrap 维度 = 点位在细节丢 → exit 1)",
193
- script: INDEX,
194
- args: ["match", path.join(FIXTURES, "changeset-neg7-g10-dims.json")],
195
- expect: { code: 1, includes: ["G10", "缺 dims="] },
196
- },
197
- {
198
- key: "T",
199
- name: "match G11 负例(橘橙主色配钴蓝效果图且无 theme_mapping = 主题错配 → exit 1)",
200
- script: INDEX,
201
- args: ["match", path.join(FIXTURES, "changeset-neg8-g11-oracle.json")],
202
- expect: { code: 1, includes: ["G11", "主题错配"] },
203
- },
204
- {
205
- key: "U",
206
- name: "verify 残差豁免 + 收口 + 状态 + 无孤儿正例(clean 夹具:申报不计缺口、absent 已收口、G14 状态命中、G15 无孤儿 → exit 0)",
207
- script: INDEX,
208
- args: ["verify", "--oracle", path.join(FIXTURES, "verify", "oracle.json"), "--actual", path.join(FIXTURES, "verify", "actual-clean.json")],
209
- expect: { code: 0, includes: ["已申报固有残差", "已收口", "状态断言 1/1 命中", "验收通过"] },
210
- },
211
- {
212
- key: "V",
213
- name: "verify 快照回归(clean 快照为基线 + L 缺口夹具为实测:上轮命中本轮退步 → exit 1 且含「基线漂移」)",
214
- script: INDEX,
215
- args: [], // 两段式:先 --snapshot 导出,再 --baseline 比对,见下方 custom 分支
216
- expect: { code: 1, includes: "基线漂移" },
217
- custom: "snapshot-drift",
218
- },
219
- {
220
- key: "W",
221
- name: "match 主题与基线图解耦正例(中性文件名 + 图内 data-mockup-theme=钴蓝,本次主题=橘橙 + theme_mapping → 走色值归一、exit 0)",
222
- script: INDEX,
223
- args: ["match", path.join(FIXTURES, "changeset-pos-theme-neutral.json")],
224
- expect: { code: 0, includes: ["changeset.theme", "图内 data-mockup-theme", "同构异色归一已声明"] },
225
- },
226
- {
227
- key: "X",
228
- name: "match 基线图缺身份元数据不阻断(中性文件名 + 无 data-mockup-theme,已声明 changeset.theme → 仅提示、exit 0)",
229
- script: INDEX,
230
- args: ["match", path.join(FIXTURES, "changeset-pos-theme-nometa.json")],
231
- expect: { code: 0, includes: "基线图未带主题身份元数据" },
232
- },
233
- ];
234
-
235
- const results = [];
236
- let failed = 0;
237
-
238
- console.log("=== A-X 端到端一键回归 ===\n");
239
-
240
- const SNAPSHOT_TMP = path.join(FIXTURES, "verify", "snapshot-latest.json");
241
-
242
- for (const step of steps) {
243
- // V 步两段式:① 用 clean 夹具导出「上一轮全绿」快照;② 用 L 缺口夹具 + --baseline 比对 → 应报基线漂移
244
- const spawnArgs = step.custom === "snapshot-drift"
245
- ? []
246
- : [step.script, ...step.args];
247
- let status, stdout, stderr;
248
- if (step.custom === "snapshot-drift") {
249
- const s1 = spawnSync(process.execPath, [
250
- INDEX, "verify",
251
- "--oracle", path.join(FIXTURES, "verify", "oracle.json"),
252
- "--actual", path.join(FIXTURES, "verify", "actual-clean.json"),
253
- "--snapshot", SNAPSHOT_TMP,
254
- ], { cwd: ROOT, encoding: "utf-8" });
255
- if (s1.status !== 0 || !existsSync(SNAPSHOT_TMP)) {
256
- status = s1.status;
257
- stdout = s1.stdout ?? "";
258
- stderr = `快照导出段失败(exit ${s1.status}):快照回归前置不成立\n${s1.stderr ?? ""}`;
259
- }
260
- else {
261
- ({ status, stdout, stderr } = spawnSync(process.execPath, [
262
- INDEX, "verify",
263
- "--oracle", path.join(FIXTURES, "verify", "oracle.json"),
264
- "--actual", path.join(FIXTURES, "verify", "actual.json"),
265
- "--baseline", SNAPSHOT_TMP,
266
- ], { cwd: ROOT, encoding: "utf-8" }));
267
- }
268
- }
269
- else {
270
- ({ status, stdout, stderr } = spawnSync(process.execPath, spawnArgs, { cwd: ROOT, encoding: "utf-8" }));
271
- }
272
- const output = `${stdout ?? ""}${stderr ?? ""}`;
273
- const code = status ?? -1;
274
-
275
- const codeOk = code === step.expect.code;
276
- const wanted = step.expect.includes === undefined
277
- ? []
278
- : (Array.isArray(step.expect.includes) ? step.expect.includes : [step.expect.includes]);
279
- const missing = wanted.filter(w => !output.includes(w));
280
- const textOk = missing.length === 0;
281
- const ok = codeOk && textOk;
282
-
283
- if (ok) results.push(`${step.key}✔`);
284
- else {
285
- failed++;
286
- results.push(`${step.key}✘`);
287
- console.error(`✖ ${step.key} · ${step.name}`);
288
- if (!codeOk) console.error(` 期望 exit ${step.expect.code},实际 exit ${code}`);
289
- if (!textOk) console.error(` 输出未包含预期片段:${missing.join("、")}`);
290
- console.error(" ───── 该步骤完整输出 ─────");
291
- for (const line of output.trimEnd().split("\n")) console.error(` ${line}`);
292
- console.error("");
293
- }
294
- }
295
-
296
- console.log(`回归结果:${results.join(" ")}`);
297
- if (failed > 0) {
298
- console.error(`\n✖ ${failed} 步未通过,见上方详情`);
299
- process.exit(1);
300
- }
301
- console.log("\n✔ A-X 全部通过,mapping/parser 可提交或发版");