@agile-team/wl-skills-ui 1.8.2 → 1.8.3

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/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to **@agile-team/wl-skills-ui** will be documented in this f
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
+ ## [1.8.3] - 2026-05-13
8
+
9
+ ### Added
10
+
11
+ - **scan --only / --skip**:支持规则级过滤,`--only R001,R016` 仅跑指定规则,`--skip R031-R037` 排除范围(支持连字符范围展开)。
12
+ - **exempt init 脚手架**:`wl-scan exempt init --target src` 智能扫描 src 下 big-screen/dashboard/chart 等个性化目录,自动生成 `.wl-exempt.json` 模板。
13
+
7
14
  ## [1.8.2] - 2026-05-13
8
15
 
9
16
  ### Fixed
package/README.md CHANGED
@@ -213,14 +213,15 @@ yarn add @agile-team/wl-skills-ui
213
213
 
214
214
  ## 版本亮点
215
215
 
216
- 当前 v1.8.2
216
+ 当前 v1.8.3
217
217
 
218
- - **修复**搜索区字号 13px/12px 混杂 统一 12px;必填星号 `* *` 重复 → 精确单颗控制
219
- - 新增 **scanner fixture 测试集**(16 条自动化测试,`npm test`)和 **SCSS 链路检查**(`npm run check:scss`)
220
- - `scan --baseline` 一步到位:扫描后自动对比基线输出漂移报告,CI 增量门槛
221
- - **漂移检测** `scanner/drift.mjs` + CLI `wl-scan drift` + MCP `wl_ui_drift`
218
+ - `scan --only R001,R016` / `--skip R031-R037` 规则级过滤(支持范围展开)
219
+ - `exempt init --target src` 智能扫描个性化目录,自动生成 `.wl-exempt.json`
220
+ - **修复**搜索区字号统一 12px;必填星号 `* *` 去重
221
+ - **scanner fixture 测试集**(16 条,`npm test`)+ **SCSS 链路检查**(`npm run check:scss`)
222
+ - `scan --baseline` 一步漂移对比 + **drift.mjs** / MCP `wl_ui_drift`
222
223
  - **R-rule 单一事实源** `standards/rules.json`(29 条),五向一致性守卫
223
- - **长效治理方案** `docs/governance-long-term.md`(基线 / 豁免 / 漂移 / 版本钉死 / AI 守护)
224
+ - **长效治理方案** `docs/governance-long-term.md`
224
225
 
225
226
  历史亮点(v1.7.1):
226
227
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-ui",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "description": "企业级 UI 风格对齐框架 — Vue + Element Plus 项目通用化妆/原生双模式(tokens / element / vendors / layouts / runtime / scanner / fixer / skills)",
5
5
  "type": "module",
6
6
  "main": "./es/index.js",
package/scanner/index.mjs CHANGED
@@ -4,6 +4,8 @@
4
4
  *
5
5
  * 用法:
6
6
  * wl-scan scan --target <path> # 风格扫描
7
+ * wl-scan scan --target <path> --only R001,R016
8
+ * wl-scan scan --target <path> --skip R031-R037
7
9
  * wl-scan scan --target <path> --outFile report.md
8
10
  * wl-scan scan --target <path> --output json
9
11
  * wl-scan check --project <path> # 接入完整性检查
@@ -12,6 +14,7 @@
12
14
  * wl-scan all --project <path> # 接入检查 + 风格扫描 + 报告
13
15
  * wl-scan init # 打印接入指引
14
16
  * wl-scan drift --baseline <f> --current <f> # 漂移检测
17
+ * wl-scan exempt init --target <path> # 生成 .wl-exempt.json 豁免模板
15
18
  * wl-scan snapshot list # 列出快照
16
19
  * wl-scan snapshot rollback [--id <id>] # 回退到快照(默认最新)
17
20
  * wl-scan snapshot diff [--id <id>] # 查看快照与当前差异
@@ -45,6 +48,7 @@ const SUBCOMMANDS = new Set([
45
48
  "all",
46
49
  "init",
47
50
  "drift",
51
+ "exempt",
48
52
  "snapshot",
49
53
  ]);
50
54
  let subcommand = "scan";
@@ -71,6 +75,8 @@ const { values } = parseArgs({
71
75
  exempt: { type: "string", default: "" },
72
76
  baseline: { type: "string", default: "" },
73
77
  current: { type: "string", default: "" },
78
+ only: { type: "string", default: "" },
79
+ skip: { type: "string", default: "" },
74
80
  },
75
81
  strict: false,
76
82
  });
@@ -199,6 +205,24 @@ function runScan(targetDir, excludeDirs, exemptConfig) {
199
205
  };
200
206
  }
201
207
 
208
+ // ── 公共:规则范围展开(R031-R037 → R031,R032,...,R037)────────────────────
209
+ function expandRuleRange(input) {
210
+ const set = new Set();
211
+ for (const part of input.split(",").map((s) => s.trim())) {
212
+ const rangeMatch = part.match(/^(R)(\d+)-(R)?(\d+)$/i);
213
+ if (rangeMatch) {
214
+ const start = parseInt(rangeMatch[2]);
215
+ const end = parseInt(rangeMatch[4]);
216
+ for (let i = Math.min(start, end); i <= Math.max(start, end); i++) {
217
+ set.add("R" + String(i).padStart(3, "0"));
218
+ }
219
+ } else {
220
+ set.add(part.toUpperCase());
221
+ }
222
+ }
223
+ return set;
224
+ }
225
+
202
226
  // ── 公共:按 layer / vendor / mode 过滤 ─────────────────────────────────────
203
227
  function applyFilters(issues) {
204
228
  let out = issues;
@@ -216,6 +240,16 @@ function applyFilters(issues) {
216
240
  } else if (values.mode === "native") {
217
241
  // 原生模式:全部 layer
218
242
  }
243
+ // --only R001,R016 → 仅保留指定规则
244
+ if (values.only) {
245
+ const allow = expandRuleRange(values.only);
246
+ out = out.filter((i) => allow.has(i.rule));
247
+ }
248
+ // --skip R031-R037 → 排除指定规则
249
+ if (values.skip) {
250
+ const deny = expandRuleRange(values.skip);
251
+ out = out.filter((i) => !deny.has(i.rule));
252
+ }
219
253
  return out;
220
254
  }
221
255
 
@@ -267,6 +301,76 @@ if (subcommand === "check") {
267
301
  process.exit(values["fail-on-error"] && hasError ? 1 : 0);
268
302
  }
269
303
 
304
+ if (subcommand === "exempt") {
305
+ const sub = args[0] || "init";
306
+ if (sub === "init") {
307
+ const { existsSync } = await import("node:fs");
308
+ const projectRoot = resolve(values.project);
309
+ const targetDir = resolve(values.target);
310
+ const outPath = join(projectRoot, ".wl-exempt.json");
311
+ if (existsSync(outPath)) {
312
+ console.log(`⚠️ ${outPath} 已存在,跳过生成。如需重新生成请先删除。`);
313
+ process.exit(0);
314
+ }
315
+ // 智能扫描目标目录下常见的个性化子目录
316
+ const EXEMPT_KEYWORDS = [
317
+ "big-screen",
318
+ "dashboard",
319
+ "map-view",
320
+ "topology",
321
+ "flow-designer",
322
+ "report-designer",
323
+ "chart",
324
+ "3d",
325
+ "canvas",
326
+ "screen",
327
+ "monitor",
328
+ "cockpit",
329
+ "visual",
330
+ ];
331
+ const smartPaths = [];
332
+ function walkForExempt(dir, depth = 0) {
333
+ if (depth > 4) return;
334
+ try {
335
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
336
+ if (!entry.isDirectory()) continue;
337
+ if (["node_modules", "dist", ".git"].includes(entry.name)) continue;
338
+ const rel = relative(targetDir, join(dir, entry.name)).replace(
339
+ /\\/g,
340
+ "/",
341
+ );
342
+ if (
343
+ EXEMPT_KEYWORDS.some((kw) => entry.name.toLowerCase().includes(kw))
344
+ ) {
345
+ smartPaths.push(rel + "/**");
346
+ } else {
347
+ walkForExempt(join(dir, entry.name), depth + 1);
348
+ }
349
+ }
350
+ } catch {
351
+ /* ignore permission errors */
352
+ }
353
+ }
354
+ if (existsSync(targetDir)) walkForExempt(targetDir);
355
+ const { generateExemptTemplate } = await import("./exempt.mjs");
356
+ const template = JSON.parse(generateExemptTemplate());
357
+ if (smartPaths.length > 0) {
358
+ template.exemptPaths = [
359
+ ...new Set([...smartPaths, ...template.exemptPaths]),
360
+ ];
361
+ template.description += `(自动扫描 ${targetDir} 发现 ${smartPaths.length} 个候选目录)`;
362
+ }
363
+ writeFileSync(outPath, JSON.stringify(template, null, 2) + "\n", "utf8");
364
+ console.log(`✅ 已生成 ${outPath}`);
365
+ if (smartPaths.length > 0) {
366
+ console.log(` 自动发现 ${smartPaths.length} 个候选豁免目录:`);
367
+ for (const p of smartPaths) console.log(` ${p}`);
368
+ }
369
+ console.log(` 请人工审核后提交至版本库。`);
370
+ }
371
+ process.exit(0);
372
+ }
373
+
270
374
  if (subcommand === "drift") {
271
375
  const { driftFromFiles, formatDriftText, formatDriftJson } =
272
376
  await import("./drift.mjs");