@agile-team/wl-skills-kit 2.11.3 → 2.11.4

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
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.11.4] - 2026-06-21
4
+
5
+ ### Added
6
+
7
+ - **R14 类型检查自动接线(pre-push hook)**:`init`/`update` 自动创建 `.husky/pre-push`,推送前跑 `validate --typecheck`(R14 vue-tsc 类型检查)。补齐"pre-commit 跑 R1~R13、pre-push 跑 R14、CI 跑 R1~R14"三层确定性兜底,R14 不再是 opt-in 空规则
8
+ - **validate 端到端集成测试(tests/validate.test.js)**:通过真实 CLI 二进制跑完整 validate 流程,覆盖正则级(agGrid/cid/defineColumns)+ AST 级(R3/R13)+ 合规页 + 豁免配置。补齐 runValidate 主体的零覆盖缺口("自吃狗粮")
9
+ - **convention-audit 豁免项复核**:审计步骤新增"读取 .wl-skills-validate.json 列出所有豁免条目",供人工逐条确认豁免是否仍需保留(豁免审计闭环)
10
+
11
+ ### Changed
12
+
13
+ - **kit 自身代码质量门禁修复**:修复 13 个 eslint error(8 处未用变量 + 4 处嵌套过深 + 1 处死代码),`lint-staged` 匹配范围从无效的 `src/**` 改为 `bin/lib/scripts/**`,`pnpm lint` 纳入 `verify`/`prepublishOnly`。kit 自身代码不再绕开规范
14
+ - **runAstRules 重构降复杂度**:抽出 `checkR8FileSeparation`/`checkR13Complexity` 独立 helper,降低主函数圈复杂度(84→更低),消除 R9 死代码(hasInterfaceTable/hasEntityDef)
15
+
16
+ ### Fixed
17
+
18
+ - **jenkins-pipeline.md 滞后**:3 处 `.github/` → `.wl-skills/`(v2.11.0 目录迁移遗留未同步);Typecheck 独立 stage 合并进 `validate --strict --typecheck`(R14 不再割裂)
19
+ - **R14 配置错误误报**:`vue-tsc` 退出码非 0 但无标准 TS error 行时,区分 tsconfig 配置问题(warn,非类型错误)与未知失败(error),避免误导用户改类型
20
+ - **lint-skills printFixSuggestions 残留**:`.github/standards/` 引用修正为 `.wl-skills/`
21
+
3
22
  ## [2.11.3] - 2026-06-21
4
23
 
5
24
  ### Added
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @agile-team/wl-skills-kit
2
2
 
3
- **AI Skill 模板包 v2.11.3** — 一键将 14 条规范、11 个 AI Skill、17 个 MCP Tool、编辑器 MCP 配置、文档导入 Vue 3 项目。
3
+ **AI Skill 模板包 v2.11.4** — 一键将 14 条规范、11 个 AI Skill、17 个 MCP Tool、编辑器 MCP 配置、文档导入 Vue 3 项目。
4
4
 
5
5
  让 AI 编辑器(Copilot / Cursor / Windsurf / Claude Code / Cline / Kiro / Trae / Qoder / 通用 Agents)**真正理解项目规范**,从原型/详设到完整页面代码全流程自动化。
6
6
 
package/bin/wl-skills.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * wl-skills-kit CLI v2.11.3
4
+ * wl-skills-kit CLI v2.11.4
5
5
  *
6
6
  * 命令:
7
7
  * init 全量安装(默认,向后兼容)
@@ -26,7 +26,6 @@ const crypto = require("crypto");
26
26
  const {
27
27
  runAstRules,
28
28
  getStagedFiles,
29
- hasAstAvailable,
30
29
  runTypeCheck,
31
30
  } = require("../lib/ast-rules");
32
31
 
@@ -382,6 +381,7 @@ function runInstall(incremental) {
382
381
  // 这样即使 early-return(同版本跳过文件复制),hook 也会被创建/更新
383
382
  if (!dryRun) {
384
383
  ensurePreCommitHook(TARGET_DIR);
384
+ ensurePrePushHook(TARGET_DIR);
385
385
  ensureEslintConfig(TARGET_DIR);
386
386
  }
387
387
 
@@ -517,21 +517,20 @@ function runInstall(incremental) {
517
517
  // v2.11 目录重构迁移:.github/skills|standards|guides|reports/ → .wl-skills/
518
518
  for (const prefix of LEGACY_DIR_PREFIXES) {
519
519
  const legacyDir = path.join(TARGET_DIR, prefix);
520
- if (fs.existsSync(legacyDir)) {
521
- const legacyFiles = walkDir(legacyDir, legacyDir);
522
- for (const f of legacyFiles) {
523
- const legacyFile = path.join(legacyDir, f);
524
- if (dryRun) {
525
- console.log(" 迁移清理 " + prefix + f + " (v2.11 目录重构)");
526
- } else {
527
- removeFileAndEmptyParents(legacyFile);
528
- }
529
- migrated++;
530
- }
531
- // 删除空目录
532
- if (!dryRun && fs.existsSync(legacyDir)) {
533
- try { fs.rmSync(legacyDir, { recursive: true, force: true }); } catch {}
520
+ if (!fs.existsSync(legacyDir)) continue;
521
+ const legacyFiles = walkDir(legacyDir, legacyDir);
522
+ for (const f of legacyFiles) {
523
+ const legacyFile = path.join(legacyDir, f);
524
+ if (dryRun) {
525
+ console.log(" 迁移清理 " + prefix + f + " (v2.11 目录重构)");
526
+ } else {
527
+ removeFileAndEmptyParents(legacyFile);
534
528
  }
529
+ migrated++;
530
+ }
531
+ // 删除空目录
532
+ if (!dryRun && fs.existsSync(legacyDir)) {
533
+ try { fs.rmSync(legacyDir, { recursive: true, force: true }); } catch {}
535
534
  }
536
535
  }
537
536
 
@@ -642,7 +641,6 @@ function runInstall(incremental) {
642
641
  */
643
642
  function ensurePreCommitHook(targetDir) {
644
643
  const huskyDir = path.join(targetDir, ".husky");
645
- const preCommitPath = path.join(huskyDir, ".husky/pre-commit");
646
644
 
647
645
  // 只有 git 仓库才创建 husky hook
648
646
  if (!fs.existsSync(path.join(targetDir, ".git"))) return;
@@ -723,6 +721,58 @@ function ensurePreCommitHook(targetDir) {
723
721
  }
724
722
  }
725
723
 
724
+ /**
725
+ * 确保 .husky/pre-push 包含 wl-skills validate --typecheck
726
+ * — pre-push 跑全量类型检查(R14),补 pre-commit 不跑 R14 的缺口
727
+ *
728
+ * 设计理由:pre-commit 跑全量 vue-tsc 太慢(拖慢日常提交),
729
+ * 但 pre-push 频率低、可接受耗时,且 CI 也跑相同命令。
730
+ * 这样 R14 在"推送到远程"和"CI"两个节点都有确定性执行器兜底。
731
+ *
732
+ * 策略同 pre-commit:不存在则创建,存在但无 marker 则追加。
733
+ */
734
+ function ensurePrePushHook(targetDir) {
735
+ const huskyDir = path.join(targetDir, ".husky");
736
+ if (!fs.existsSync(path.join(targetDir, ".git"))) return;
737
+ if (!fs.existsSync(huskyDir)) return;
738
+
739
+ const HOOK_VERSION_TAG = "# wl-skills-prepush-hook-v1";
740
+ const pushContent =
741
+ "#!/usr/bin/env sh\n" +
742
+ HOOK_VERSION_TAG + "\n" +
743
+ "# wl-skills-kit 自动管理:推送前全量类型检查(R14,error 阻断推送)\n" +
744
+ "# 含 vue-tsc/tsc --noEmit,体积较大故放 pre-push 而非 pre-commit\n" +
745
+ "# 如果 node_modules 不存在或 kit 未安装,优雅跳过,不阻断推送\n" +
746
+ 'if [ -f "node_modules/@agile-team/wl-skills-kit/bin/wl-skills.js" ]; then\n' +
747
+ ' node node_modules/@agile-team/wl-skills-kit/bin/wl-skills.js validate --typecheck\n' +
748
+ " if [ $? -ne 0 ]; then\n" +
749
+ ' echo ""\n' +
750
+ ' echo " ✖ 类型检查/规范检测未通过,推送已阻断(R14 类型错误零容忍)"\n' +
751
+ ' echo " → 修复后重新 git push,或单独 commit 后用 --no-verify 跳过(CI 仍会拦截)"\n' +
752
+ " exit 1\n" +
753
+ " fi\n" +
754
+ "else\n" +
755
+ ' echo " ⚠ wl-skills-kit 未安装,跳过推送前类型检查"\n' +
756
+ "fi\n";
757
+
758
+ const pushFile = path.join(huskyDir, "pre-push");
759
+ if (!fs.existsSync(pushFile)) {
760
+ fs.writeFileSync(pushFile, pushContent, "utf8");
761
+ try { fs.chmodSync(pushFile, 0o755); } catch {}
762
+ console.log(" ✔ 已创建 .husky/pre-push(推送前自动运行 wl-skills validate --typecheck)");
763
+ console.log(" → R14 类型检查在 pre-push 兜底(pre-commit 太慢不放这)");
764
+ console.log("");
765
+ } else {
766
+ const existing = fs.readFileSync(pushFile, "utf8");
767
+ if (existing.includes(HOOK_VERSION_TAG)) return;
768
+ const addition = "\n" + pushContent.replace("#!/usr/bin/env sh\n", "");
769
+ fs.writeFileSync(pushFile, existing.trimEnd() + "\n" + addition, "utf8");
770
+ try { fs.chmodSync(pushFile, 0o755); } catch {}
771
+ console.log(" ✔ 已在 .husky/pre-push 追加 wl-skills validate --typecheck(R14 类型检查)");
772
+ console.log("");
773
+ }
774
+ }
775
+
726
776
  /**
727
777
  * 确保业务项目有 ESLint 配置
728
778
  * 策略:如果项目根目录没有 eslint.config.cjs,从 kit 复制模板
@@ -1433,15 +1483,13 @@ function printFixSuggestions(blockingIssues) {
1433
1483
  console.log(" \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
1434
1484
 
1435
1485
  let hasAutoFix = false;
1436
- let hasUnknownIssue = false;
1437
1486
  for (const [rule, ruleIssues] of ruleGroups.entries()) {
1438
1487
  const suggestion = AST_FIX_SUGGESTIONS[rule] || findRegexSuggestion(ruleIssues[0].text);
1439
1488
  const count = ruleIssues.length;
1440
1489
  if (!suggestion) {
1441
1490
  // 免底:未知规则的阻断项也要展示,避免用户看不到任何提示
1442
- hasUnknownIssue = true;
1443
1491
  console.log(" \u2502 " + rule + "\uff08" + count + " \u5904\uff09 [\u2753\u672a\u77e5\u89c4\u5219]");
1444
- console.log(" \u2502 \u2192 \u8bf7\u67e5\u770b .github/standards/ \u76f8\u5173\u89c4\u8303\u6216\u89e6\u53d1\u89c4\u8303\u5ba1\u8ba1");
1492
+ console.log(" \u2502 \u2192 \u8bf7\u67e5\u770b .wl-skills/standards/ \u76f8\u5173\u89c4\u8303\u6216\u89e6\u53d1\u89c4\u8303\u5ba1\u8ba1");
1445
1493
  console.log(" \u2502");
1446
1494
  continue;
1447
1495
  }
@@ -1449,7 +1497,7 @@ function printFixSuggestions(blockingIssues) {
1449
1497
  if (suggestion.auto) hasAutoFix = true;
1450
1498
  console.log(" \u2502 " + rule + "\uff08" + count + " \u5904\uff09" + autoTag);
1451
1499
  console.log(" \u2502 \u2192 " + suggestion.fix);
1452
- console.log(" \u2502 \u53c2\u8003: .github/" + suggestion.ref);
1500
+ console.log(" \u2502 \u53c2\u8003: .wl-skills/" + suggestion.ref);
1453
1501
  console.log(" \u2502");
1454
1502
  }
1455
1503
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **读者**:团队技术负责人 / wl-skills-kit 维护者 / 对体系设计感兴趣的团队成员
4
4
  > **更新方式**:重大架构变更后追加对应章节,旧章节原文保留(历史可溯)
5
- > **当前版本**:v2.11.3(2026-06-21)
5
+ > **当前版本**:v2.11.4(2026-06-21)
6
6
 
7
7
  ---
8
8
 
@@ -160,8 +160,9 @@ description: "Use when: auditing project source code against the 14 modular stan
160
160
  - ESLint:是否可执行
161
161
  - TypeScript:`vue-tsc --noEmit`(回退 `tsc --noEmit`)是否可执行、是否 0 error(R14)
162
162
  - Git:当前分支 / 最近提交
163
- - Husky:`.husky/pre-commit`、`.husky/commit-msg` 是否存在
163
+ - Husky:`.husky/pre-commit`、`.husky/pre-push`、`.husky/commit-msg` 是否存在
164
164
  3. 读取 `package.json` 获取项目脚本名称
165
+ 4. **读取豁免配置**(v2.11.4+):如项目根存在 `.wl-skills-validate.json`,解析其中所有豁免条目(paths / rules / reason),在报告中单列"豁免项复核清单",供人工逐条确认是否仍需豁免
165
166
 
166
167
  ### 步骤 3:扫描源码
167
168
 
package/lib/ast-rules.js CHANGED
@@ -52,14 +52,6 @@ function ensureAst() {
52
52
  if (_astChecked) return _compilerSfc && _babelParser;
53
53
  _astChecked = true;
54
54
 
55
- // 优先从 kit 自身 node_modules 解析(开发/测试环境)
56
- const tryPaths = [
57
- // 1. kit 自身 node_modules(标准 require 解析)
58
- null,
59
- // 2. 调用方项目(CWD)的 node_modules — 业务项目通过 npx/node 运行时
60
- // kit 本身没装 @vue/compiler-sfc,但业务项目有
61
- ];
62
-
63
55
  // 尝试标准 require(从 kit 目录解析)
64
56
  try {
65
57
  _compilerSfc = require("@vue/compiler-sfc");
@@ -76,7 +68,7 @@ function ensureAst() {
76
68
  if (!_compilerSfc || !_babelParser) {
77
69
  const cwd = process.env.WL_PROJECT_ROOT || process.cwd();
78
70
  try {
79
- const createRequire = require("module").createRequire;
71
+ const {createRequire} = require("module");
80
72
  const cwdRequire = createRequire(cwd + "/package.json");
81
73
  if (!_compilerSfc) {
82
74
  try { _compilerSfc = cwdRequire("@vue/compiler-sfc"); } catch {}
@@ -430,7 +422,7 @@ function resolveFnName(node, parent) {
430
422
  return parent.key.name || parent.key.value || "(anonymous)";
431
423
  }
432
424
  if (parent.type === "AssignmentExpression" && parent.left) {
433
- const left = parent.left;
425
+ const {left} = parent;
434
426
  if (left.property) return left.property.name || left.property.value;
435
427
  if (left.name) return left.name;
436
428
  }
@@ -532,6 +524,75 @@ function isLikelyListPage(template) {
532
524
  return hasBaseTable && hasPagination && !hasForm && !hasTabs;
533
525
  }
534
526
 
527
+ // ─── 单页规则 helper(从 runAstRules 抽出,降低主函数圈复杂度)──────────
528
+
529
+ /**
530
+ * R8: 强制 3 文件分离 — 有 API 调用/大量逻辑但无 data.ts,或有 data.ts 仍泄漏 API 调用
531
+ */
532
+ function checkR8FileSeparation(scriptContent, effectiveLines, hasDataTs, pageDir, fullSource, issues) {
533
+ const hasApiCall = /getAction|postAction|putAction|deleteAction|API_CONFIG/.test(
534
+ stripCommentsAndStrings(scriptContent),
535
+ );
536
+ const ignore = hasIgnoreMarker(fullSource, "R8");
537
+ if (!hasDataTs) {
538
+ if (hasApiCall && !ignore) {
539
+ issues.push({
540
+ level: "error",
541
+ dir: pageDir,
542
+ text: "页面有接口调用但缺 data.ts(业务逻辑必须在 data.ts 中)",
543
+ rule: "R8",
544
+ });
545
+ } else if (effectiveLines > 20 && !ignore) {
546
+ issues.push({
547
+ level: "warn",
548
+ dir: pageDir,
549
+ text: "index.vue 有 " + effectiveLines + " 行逻辑但无 data.ts(建议拆分)",
550
+ rule: "R8",
551
+ });
552
+ }
553
+ }
554
+ // 有 data.ts 但 index.vue 仍然有 API 调用(逻辑泄漏)
555
+ if (hasDataTs && hasApiCall && !ignore) {
556
+ issues.push({
557
+ level: "error",
558
+ dir: pageDir,
559
+ text: "有 data.ts 但 index.vue 中仍含 API 调用(逻辑应全部在 data.ts)",
560
+ rule: "R8",
561
+ });
562
+ }
563
+ }
564
+
565
+ /**
566
+ * R13: 单函数圈复杂度 > MAX_CYCLOMATIC_COMPLEXITY → error
567
+ * 覆盖 index.vue <script> 与 data.ts 的所有函数/方法/箭头函数(嵌套函数独立计)
568
+ */
569
+ function checkR13Complexity(scriptContent, dataPath, pageDir, fullSource, issues) {
570
+ if (hasIgnoreMarker(fullSource, "R13")) return;
571
+ const maxC = CONFIG.MAX_CYCLOMATIC_COMPLEXITY;
572
+ const scanComplexity = (code, label) => {
573
+ const ast = parseScriptAst(code);
574
+ if (!ast) return;
575
+ for (const fn of collectFunctions(ast)) {
576
+ const cc = computeFunctionComplexity(fn.node);
577
+ if (cc > maxC) {
578
+ issues.push({
579
+ level: "error",
580
+ dir: pageDir,
581
+ text:
582
+ label + " 函数 " + fn.name +
583
+ "() 圈复杂度 " + cc + "(阈值 " + maxC +
584
+ "),需拆分为更小函数(standard 04)",
585
+ rule: "R13",
586
+ });
587
+ }
588
+ }
589
+ };
590
+ scanComplexity(scriptContent, "index.vue");
591
+ if (fs.existsSync(dataPath)) {
592
+ scanComplexity(fs.readFileSync(dataPath, "utf8"), "data.ts");
593
+ }
594
+ }
595
+
535
596
  // ─── 主检测函数 ────────────────────────────────────────────────────────
536
597
 
537
598
  /**
@@ -632,31 +693,7 @@ function runAstRules(targetDir, scanRel, options) {
632
693
 
633
694
  // R13: 单函数圈复杂度 ≤ MAX_CYCLOMATIC_COMPLEXITY(standard 04,Mcabe)
634
695
  // 覆盖 index.vue <script> 与 data.ts 的所有函数/方法/箭头函数
635
- if (!hasIgnoreMarker(fullSource, "R13")) {
636
- const maxC = CONFIG.MAX_CYCLOMATIC_COMPLEXITY;
637
- const scanComplexity = (code, label) => {
638
- const ast = parseScriptAst(code);
639
- if (!ast) return;
640
- for (const fn of collectFunctions(ast)) {
641
- const cc = computeFunctionComplexity(fn.node);
642
- if (cc > maxC) {
643
- issues.push({
644
- level: "error",
645
- dir: page.dir,
646
- text:
647
- label + " 函数 " + fn.name +
648
- "() 圈复杂度 " + cc + "(阈值 " + maxC +
649
- "),需拆分为更小函数(standard 04)",
650
- rule: "R13",
651
- });
652
- }
653
- }
654
- };
655
- scanComplexity(scriptContent, "index.vue");
656
- if (fs.existsSync(dataPath)) {
657
- scanComplexity(fs.readFileSync(dataPath, "utf8"), "data.ts");
658
- }
659
- }
696
+ checkR13Complexity(scriptContent, dataPath, page.dir, fullSource, issues);
660
697
 
661
698
  // R2: 禁止的 import / 全局 API
662
699
  if (scriptContent) {
@@ -813,38 +850,10 @@ function runAstRules(targetDir, scanRel, options) {
813
850
 
814
851
  // R8: 强制 3 文件分离 — 有 API 调用/大量逻辑但无 data.ts
815
852
  // 任何页面(不分类型)只要有接口调用或超过阈值行数,就应该拆出 data.ts
816
- {
817
- const hasApiCall = /getAction|postAction|putAction|deleteAction|API_CONFIG/.test(
818
- stripCommentsAndStrings(scriptContent),
819
- );
820
- const hasScss = page.names.has("index.scss");
821
- if (!page.names.has("data.ts")) {
822
- if (hasApiCall && !hasIgnoreMarker(fullSource, "R8")) {
823
- issues.push({
824
- level: "error",
825
- dir: page.dir,
826
- text: "页面有接口调用但缺 data.ts(业务逻辑必须在 data.ts 中)",
827
- rule: "R8",
828
- });
829
- } else if (effectiveLines > 20 && !hasIgnoreMarker(fullSource, "R8")) {
830
- issues.push({
831
- level: "warn",
832
- dir: page.dir,
833
- text: "index.vue 有 " + effectiveLines + " 行逻辑但无 data.ts(建议拆分)",
834
- rule: "R8",
835
- });
836
- }
837
- }
838
- // 有 data.ts 但 index.vue 仍然有 API 调用(逻辑泄漏)
839
- if (page.names.has("data.ts") && hasApiCall && !hasIgnoreMarker(fullSource, "R8")) {
840
- issues.push({
841
- level: "error",
842
- dir: page.dir,
843
- text: "有 data.ts 但 index.vue 中仍含 API 调用(逻辑应全部在 data.ts)",
844
- rule: "R8",
845
- });
846
- }
847
- }
853
+ checkR8FileSeparation(
854
+ scriptContent, effectiveLines, page.names.has("data.ts"),
855
+ page.dir, fullSource, issues,
856
+ );
848
857
 
849
858
  // R9: api.md 质量检测 — 有 API_CONFIG 时检查 api.md 结构完整性
850
859
  if (page.names.has("data.ts")) {
@@ -855,10 +864,6 @@ function runAstRules(targetDir, scanRel, options) {
855
864
  if (hasApiConfig && page.names.has("api.md")) {
856
865
  const apiMdPath = path.join(absDir, "api.md");
857
866
  const apiMdContent = fs.readFileSync(apiMdPath, "utf-8");
858
- // 检查 api.md 是否有接口列表表格(核心结构)
859
- const hasInterfaceTable = /\|\s*操作\s*\|.*\|\s*Method\s*\||\|\s*操作\s*\|.*\|\s*URL\s*\|/.test(apiMdContent);
860
- // 检查 api.md 是否有实体定义
861
- const hasEntityDef = /字段|实体|Entity|字段名/.test(apiMdContent);
862
867
  // 检查 api.md 中的 URL 是否与 data.ts API_CONFIG 一致
863
868
  // 正则匹配 URL 路径:支持多段、数字、连字符、下划线
864
869
  // 例:/mdata/mdataModel/list /api/v2/customer-archive/save /sys/user_role/list
@@ -1100,22 +1105,26 @@ function runTypeCheck(root) {
1100
1105
  if (result.status === 0) {
1101
1106
  return { issues: [], ran: true, errorCount: 0 };
1102
1107
  }
1103
- // 退出码非 0 但未解析出标准 error 行:疑似 tsconfig / 配置级错误
1108
+ // 退出码非 0 但未解析出标准 error 行:区分配置级错误与未知失败
1109
+ // tsconfig 配置问题(extends 不存在、语法错等)归为 warn(非类型错误,不阻断类型门禁)
1110
+ const isConfigError = /tsconfig|Cannot find module.*\.json|error TS6053|error TS5023|File not found/i.test(out);
1104
1111
  return {
1105
1112
  issues: [
1106
1113
  {
1107
- level: "error",
1114
+ level: isConfigError ? "warn" : "error",
1108
1115
  dir: label,
1109
1116
  text:
1110
1117
  checker +
1111
1118
  " --noEmit 退出码 " +
1112
1119
  result.status +
1113
- "(请检查 tsconfig / 类型配置,无标准 TS 错误输出)",
1120
+ (isConfigError
1121
+ ? "(疑似 tsconfig 配置问题,非类型错误,请检查 tsconfig.json)"
1122
+ : "(无标准 TS 错误输出,请检查 tsconfig / 类型配置)"),
1114
1123
  rule: "R14",
1115
1124
  },
1116
1125
  ],
1117
1126
  ran: true,
1118
- errorCount: 1,
1127
+ errorCount: isConfigError ? 0 : 1,
1119
1128
  };
1120
1129
  }
1121
1130
 
package/lib/page-spec.js CHANGED
@@ -262,7 +262,7 @@ function extractOperationSequence(dataContent) {
262
262
  for (const item of items) {
263
263
  const labelM = item.match(/(?:^|[\s,{])label\s*:\s*["'`]([^"'`]+)["'`]/);
264
264
  const typeM = item.match(/(?:^|[\s,{])type\s*:\s*["'`]([^"'`]+)["'`]/);
265
- let label = labelM ? labelM[1] : typeM ? TYPE_LABEL[typeM[1]] : null;
265
+ const label = labelM ? labelM[1] : typeM ? TYPE_LABEL[typeM[1]] : null;
266
266
  if (label) result.push({ label });
267
267
  }
268
268
  return result;
@@ -288,9 +288,6 @@ function extractBracketBody(source, openIdx) {
288
288
  function seqNames(seq) {
289
289
  return seq.map((x) => x.name).filter(Boolean);
290
290
  }
291
- function seqLabels(seq) {
292
- return seq.map((x) => x.label).filter(Boolean);
293
- }
294
291
 
295
292
  /** 数组顺序是否严格相等 */
296
293
  function arrayEq(a, b) {
@@ -314,6 +311,31 @@ function pushMissingImplementationIssue(issues, level, dir, rule, target) {
314
311
  });
315
312
  }
316
313
 
314
+ /**
315
+ * S3 颜色比对:集合一致时逐个核对 toolbar 按钮颜色(抽出降低 compareSpecToCode 复杂度)
316
+ */
317
+ function pushToolbarColorIssues(specToolbar, actualToolbar, dir, issues) {
318
+ const actualByLabel = new Map(actualToolbar.map((b) => [b.label, b]));
319
+ for (const sb of specToolbar) {
320
+ if (!sb.label || !sb.color) continue;
321
+ const ab = actualByLabel.get(sb.label);
322
+ if (ab && ab.color !== sb.color) {
323
+ issues.push({
324
+ level: "warn",
325
+ dir,
326
+ rule: "S3",
327
+ text:
328
+ '按钮"' +
329
+ sb.label +
330
+ '"颜色与原型不一致:spec=' +
331
+ sb.color +
332
+ " vs code=" +
333
+ ab.color,
334
+ });
335
+ }
336
+ }
337
+ }
338
+
317
339
  /**
318
340
  * 比对 page-spec 与 data.ts 实际实现
319
341
  * @param {object} spec page-spec.json 对象
@@ -444,25 +466,7 @@ function compareSpecToCode(spec, dataContent, dir) {
444
466
  }
445
467
  // 颜色比对(仅在集合一致时逐个核对)
446
468
  if (specLabels.length && actualLabels.length && setEq(specLabels, actualLabels)) {
447
- const actualByLabel = new Map(actual.map((b) => [b.label, b]));
448
- for (const sb of spec.toolbar) {
449
- if (!sb.label || !sb.color) continue;
450
- const ab = actualByLabel.get(sb.label);
451
- if (ab && ab.color !== sb.color) {
452
- issues.push({
453
- level: "warn",
454
- dir,
455
- rule: "S3",
456
- text:
457
- '按钮"' +
458
- sb.label +
459
- '"颜色与原型不一致:spec=' +
460
- sb.color +
461
- " vs code=" +
462
- ab.color,
463
- });
464
- }
465
- }
469
+ pushToolbarColorIssues(spec.toolbar, actual, dir, issues);
466
470
  }
467
471
  }
468
472
  }
@@ -23,8 +23,6 @@
23
23
  * ⑤ Vite 插件层(本插件) → 构建时自动执行,无需额外配置 ← 不可绕开
24
24
  */
25
25
 
26
- const path = require("path");
27
-
28
26
  let astRules = null;
29
27
  try {
30
28
  astRules = require("./ast-rules.js");
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-kit",
3
- "version": "2.11.3",
4
- "description": "AI Skill 模板包 v2.11.3 — 14 条编码规范 + 11 个 AI Skill + 17 个 MCP Tool,一条命令导入 Vue 3 项目(.wl-skills/ 统一隔离架构)",
3
+ "version": "2.11.4",
4
+ "description": "AI Skill 模板包 v2.11.4 — 14 条编码规范 + 11 个 AI Skill + 17 个 MCP Tool,一条命令导入 Vue 3 项目(.wl-skills/ 统一隔离架构)",
5
5
  "main": "./bin/wl-skills.js",
6
6
  "packageManager": "pnpm@11.5.3",
7
7
  "bin": {
@@ -48,11 +48,11 @@
48
48
  "lint:skills": "node scripts/lint-skills.js",
49
49
  "test": "vitest run",
50
50
  "version:verify": "node scripts/verify-version.js",
51
- "verify": "pnpm version:verify && pnpm lint:skills && pnpm test",
51
+ "verify": "pnpm version:verify && pnpm lint && pnpm lint:skills && pnpm test",
52
52
  "ci": "pnpm install --frozen-lockfile && pnpm verify",
53
53
  "pack:dry": "npm pack --dry-run --ignore-scripts",
54
54
  "release:check": "pnpm verify && npm pack --dry-run --ignore-scripts",
55
- "prepublishOnly": "node scripts/verify-version.js && node scripts/lint-skills.js && vitest run"
55
+ "prepublishOnly": "node scripts/verify-version.js && pnpm lint && node scripts/lint-skills.js && vitest run"
56
56
  },
57
57
  "optionalDependencies": {
58
58
  "@babel/parser": "^7.20.0",
@@ -90,7 +90,13 @@
90
90
  }
91
91
  },
92
92
  "lint-staged": {
93
- "src/**/*.{js,jsx,ts,tsx,vue}": [
93
+ "bin/**/*.js": [
94
+ "eslint --fix --no-cache"
95
+ ],
96
+ "lib/**/*.js": [
97
+ "eslint --fix --no-cache"
98
+ ],
99
+ "scripts/**/*.js": [
94
100
  "eslint --fix --no-cache"
95
101
  ]
96
102
  },