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

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,35 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.11.5] - 2026-06-21
4
+
5
+ ### Fixed
6
+
7
+ - **全仓库文档滞后清零**:v2.11.0 目录从 `.github/` 迁移到 `.wl-skills/`、v2.11.2 规则从 R1~R12/R1~R7 扩展到 R1~R14、Skill 从 10→11、规范从 13→14 后,多处当前生效文档/代码注释未同步:
8
+ - `files/.wl-skills/guides/usage.md` 项目结构树整体仍用 `.github/` + 13 条/10 个 → 改为 `.wl-skills/` + 14 条/11 个
9
+ - 代码注释滞后:`lib/ast-rules.js`/`bin/wl-skills.js`/`mcp/tools/projectTools.js` 仍写"R1~R7"/"7 条语义规则" → R1~R14
10
+ - `files/.wl-skills/copilot-instructions-full.md`/`page-codegen SKILL.md` 豁免说明"R1~R12" → R1~R14
11
+ - `kit-internal/README.md`/`convention-audit USAGE.md`/`docs/全盘分析`/`docs/ai全景分析`/`AI工作流演进` 中"13 条规范"/"10 个 Skill" → 14 条/11 个
12
+ - CHANGELOG 历史条目与 `kit-internal/history/` 归档文件保留原样(记录当时状态)
13
+
14
+ ## [2.11.4] - 2026-06-21
15
+
16
+ ### Added
17
+
18
+ - **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 空规则
19
+ - **validate 端到端集成测试(tests/validate.test.js)**:通过真实 CLI 二进制跑完整 validate 流程,覆盖正则级(agGrid/cid/defineColumns)+ AST 级(R3/R13)+ 合规页 + 豁免配置。补齐 runValidate 主体的零覆盖缺口("自吃狗粮")
20
+ - **convention-audit 豁免项复核**:审计步骤新增"读取 .wl-skills-validate.json 列出所有豁免条目",供人工逐条确认豁免是否仍需保留(豁免审计闭环)
21
+
22
+ ### Changed
23
+
24
+ - **kit 自身代码质量门禁修复**:修复 13 个 eslint error(8 处未用变量 + 4 处嵌套过深 + 1 处死代码),`lint-staged` 匹配范围从无效的 `src/**` 改为 `bin/lib/scripts/**`,`pnpm lint` 纳入 `verify`/`prepublishOnly`。kit 自身代码不再绕开规范
25
+ - **runAstRules 重构降复杂度**:抽出 `checkR8FileSeparation`/`checkR13Complexity` 独立 helper,降低主函数圈复杂度(84→更低),消除 R9 死代码(hasInterfaceTable/hasEntityDef)
26
+
27
+ ### Fixed
28
+
29
+ - **jenkins-pipeline.md 滞后**:3 处 `.github/` → `.wl-skills/`(v2.11.0 目录迁移遗留未同步);Typecheck 独立 stage 合并进 `validate --strict --typecheck`(R14 不再割裂)
30
+ - **R14 配置错误误报**:`vue-tsc` 退出码非 0 但无标准 TS error 行时,区分 tsconfig 配置问题(warn,非类型错误)与未知失败(error),避免误导用户改类型
31
+ - **lint-skills printFixSuggestions 残留**:`.github/standards/` 引用修正为 `.wl-skills/`
32
+
3
33
  ## [2.11.3] - 2026-06-21
4
34
 
5
35
  ### 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.5** — 一键将 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.5
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 复制模板
@@ -1259,7 +1309,7 @@ function runValidate() {
1259
1309
  }
1260
1310
 
1261
1311
  // ── AST 语义级规则检测(v2.10.1+)─────────────────────────────────
1262
- // 补充正则无法覆盖的 7 条语义规则(R1~R7),与正则规则合并输出
1312
+ // 补充正则无法覆盖的 AST 语义规则(R1~R14),与正则规则合并输出
1263
1313
  // 在 pre-commit 模式下复用上面已计算的 stagedSet
1264
1314
  const astStagedFiles = preCommit && stagedSet ? Array.from(stagedSet) : undefined;
1265
1315
  const astResult = runAstRules(TARGET_DIR, scanPath, {
@@ -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
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  ```text
12
12
  L1 提示词工程 ✅ copilot-instructions + 多编辑器适配 + standards 懒加载
13
- L2 Skills ✅ 10 个启用 Skill + registry + pre-flight(含 business-doc-extract)
13
+ L2 Skills ✅ 11 个启用 Skill + registry + pre-flight(含 business-doc-extract)
14
14
  L3 MCP ✅ 17 个 Tool(菜单/字典/权限/项目感知/页面校验/UI 体检/通知)
15
15
  L4 CLI ✅ init/update/clean/check/diff/validate/validate-page/doctor-ui/export
16
16
  L5 Agent Pipeline 🟡 已落地协议与运行手册,可进入试运行
@@ -11,7 +11,7 @@
11
11
  | 层级 | 当前状态 | 已落地能力 |
12
12
  |---|---|---|
13
13
  | L1 提示词工程 | ✅ 稳定 | `copilot-instructions.md` + 多编辑器适配 + standards 懒加载 |
14
- | L2 Skills | ✅ 稳定 | 10 个启用 Skill,覆盖原型扫描、接口契约、页面生成、审计、**业务文档抽取**、模板提取、菜单/字典/权限同步、受控修复 |
14
+ | L2 Skills | ✅ 稳定 | 11 个启用 Skill,覆盖原型扫描、接口契约、页面生成、审计、**业务文档抽取**、模板提取、菜单/字典/权限同步、受控修复 |
15
15
  | L3 MCP | ✅ 增强 | 17 个 Tool,覆盖菜单、字典、权限、项目感知、页面校验、UI 体检、Git 摘要和审计报告推送 |
16
16
  | L4 CLI | ✅ 增强 | `init` / `update` / `clean` / `check` / `diff` / `validate` / `validate-page` / `doctor-ui` / `export` |
17
17
  | L5 Agent Pipeline | 🟡 已具备基础 | 已新增 `_pipeline.md` 协议,可开始按 Skill I/O 串联 |
@@ -26,8 +26,8 @@
26
26
  |---|---|---|---|
27
27
  | core | `prototype-scan` | ✅ | 原型/详设/口述需求 → 页面清单;已补齐 Axure 具体页访问约束 |
28
28
  | core | `api-contract` | ✅ | 生成页面级 `api.md` 前后端契约 |
29
- | core | `page-codegen` | ✅ | 生成符合 13 条规范的页面骨架;统一 AGGrid、cid、`defineColumns()`、`renderOps()` 和 `navigateHidden` |
30
- | core | `convention-audit` | ✅ | 13 条规范扫描 + 结构化审计报告 |
29
+ | core | `page-codegen` | ✅ | 生成符合 14 条规范的页面骨架;统一 AGGrid、cid、`defineColumns()`、`renderOps()` 和 `navigateHidden` |
30
+ | core | `convention-audit` | ✅ | 14 条规范扫描 + 结构化审计报告 |
31
31
  | core | `business-doc-extract` | ✅ | 原型/详设/字段/字典/现有页面 → `docs/business` 业务理解文档;语义级智能触发,碎片场景不污染 |
32
32
  | core | `template-extract` | ✅ | 从成熟页面沉淀模板 |
33
33
  | sync | `menu-sync` | ✅ | 菜单基线 ↔ 后端菜单接口 |
@@ -177,7 +177,7 @@ src/views/[域]/[模块]/[子模块]/[kebab-case目录]/
177
177
  <el-table :data="dialogData">...</el-table>
178
178
  ```
179
179
 
180
- - 必须带规则编号(R1~R12),精确豁免,不支持全局豁免
180
+ - 必须带规则编号(R1~R14),精确豁免,不支持全局豁免
181
181
  - CI `--strict` 模式下豁免标记仍然生效
182
182
 
183
183
  ---
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **读者**:团队技术负责人 / wl-skills-kit 维护者 / 对体系设计感兴趣的团队成员
4
4
  > **更新方式**:重大架构变更后追加对应章节,旧章节原文保留(历史可溯)
5
- > **当前版本**:v2.11.3(2026-06-21)
5
+ > **当前版本**:v2.11.5(2026-06-21)
6
6
 
7
7
  ---
8
8
 
@@ -78,16 +78,17 @@ AI 会自动识别意图,触发对应的 Skill。
78
78
 
79
79
  ```
80
80
  你的项目/
81
- ├── .github/
82
- │ ├── copilot-instructions.md AI 主入口
83
- │ ├── standards/ 13 条模块化规范
84
- │ ├── skills/ 10 个启用 Skill + 多编辑器适配
85
- │ ├── guides/ 使用指南 + 架构设计
86
- └── reports/ AI 生成报告(SYS_MENU_INFO 等)
87
- ├── .wl-skills/docs/ 12 个组件 API 文档(jh-* / request 等)
88
- ├── .wl-skills/templates/ 13 个领域样例(生产 + 销售)
81
+ ├── .wl-skills/ 统一隔离目录(所有 Skill/规范/指南/报告/模板)
82
+ │ ├── copilot-instructions-full.md AI 主入口完整指令
83
+ │ ├── standards/ 14 条模块化规范
84
+ │ ├── skills/ 11 个启用 Skill + 多编辑器适配
85
+ │ ├── guides/ 使用指南 + 架构设计
86
+ ├── docs/ 组件 API 文档(jh-* / request 等)+ validate 豁免说明
87
+ ├── templates/ 领域样例(生产 + 销售)
88
+ │ └── reports/ AI 生成报告(SYS_MENU_INFO 等)
89
+ ├── .wl-skills-validate.json 可选:validate 项目级豁免配置(kit 不创建)
89
90
  └── src/
90
- ├── components/ 全局/局部/远程组件
91
+ ├── components/ 全局/局部/远程组件
91
92
  └── types/
92
93
  ```
93
94
 
@@ -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
 
@@ -40,7 +40,7 @@ AI:[Pre-flight]
40
40
  - reports/AUDIT_AI_mmwr-customer-archive_20260426.md
41
41
  - reports/AUDIT_HUMAN_mmwr-customer-archive_20260426.md
42
42
  [扫描中...]
43
- 完成。13 条标准通过 11 条,2 条需关注:
43
+ 完成。14 条标准通过 12 条,2 条需关注:
44
44
  ⚠ standards/data-ts.md - EColumn.cid 缺失 1 处
45
45
  ⚠ standards/scss.md - 全局样式污染(缺 :scoped)
46
46
  详见 reports/。
@@ -48,7 +48,7 @@ description: "Use when: generating complete Vue 3 page code (index.vue + data.ts
48
48
  ────────────────────────────────────────────────
49
49
  🔍 强制自检(不可跳过):
50
50
  wl-skills validate src/views/{生成的页面目录}
51
- → 同时执行 R1~R12(AST 语义)+ S1~S5(page-spec 比对)
51
+ → 同时执行 R1~R14(AST 语义)+ S1~S5(page-spec 比对)
52
52
  → 结果:{0 error / N warn} 或列出 error 待修复
53
53
  ────────────────────────────────────────────────
54
54
  📌 后续步骤:
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 {}
@@ -141,7 +133,7 @@ function stripCommentsAndStrings(code) {
141
133
  * /* wl-skills:ignore R3 * / ← 在 scss/script 中
142
134
  *
143
135
  * 用于特殊场景(如弹窗内确实需要 el-table、确实需要在 index.vue 中用 sessionStorage)。
144
- * 标记必须带规则编号(R1~R7),精确豁免,不是全局豁免。
136
+ * 标记必须带规则编号(R1~R14),精确豁免,不是全局豁免。
145
137
  */
146
138
  function hasIgnoreMarker(content, rule) {
147
139
  if (!content || !rule) return false;
@@ -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");
@@ -196,7 +196,7 @@ async function handleValidatePage(args) {
196
196
  issues.push([
197
197
  scanPath,
198
198
  "warn",
199
- "AST 引擎不可用,跳过语义级规则(R1~R7)",
199
+ "AST 引擎不可用,跳过语义级规则(R1~R14)",
200
200
  ]);
201
201
  } else {
202
202
  for (const iss of astResult.issues) {
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.5",
4
+ "description": "AI Skill 模板包 v2.11.5 — 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
  },