@agile-team/wl-skills-kit 2.10.0 → 2.11.0

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 (192) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +7 -7
  3. package/bin/wl-skills.js +418 -29
  4. package/files/.github/copilot-instructions.md +24 -369
  5. package/files/.wl-skills/copilot-instructions-full.md +233 -0
  6. package/files/{docs → .wl-skills/docs}/jh-pagination.md +2 -2
  7. package/files/{docs → .wl-skills/docs}/page-query-hook-best-practices.md +3 -3
  8. package/files/{.github → .wl-skills}/guides/README.md +1 -1
  9. package/files/{.github → .wl-skills}/guides/architecture.md +5 -5
  10. package/files/{.github → .wl-skills}/guides/mcp-setup.md +1 -1
  11. package/files/{.github → .wl-skills}/guides/usage.md +8 -8
  12. package/files/{.github → .wl-skills}/reports/SYS_MENU_INFO.md +1 -1
  13. package/files/{.github → .wl-skills}/reports/SYS_PERMISSION_INFO.md +1 -1
  14. package/files/{.github → .wl-skills}/reports//347/273/204/344/273/266/346/217/220/345/217/226/345/273/272/350/256/256.md +1 -1
  15. package/files/{.github → .wl-skills}/skills/_best-practices.md +20 -7
  16. package/files/{.github → .wl-skills}/skills/_compat/README.md +1 -1
  17. package/files/{.github → .wl-skills}/skills/_compat/editors.json +1 -1
  18. package/files/{.github → .wl-skills}/skills/_compat/headers/cursor-mdc.txt +1 -1
  19. package/files/{.github → .wl-skills}/skills/_compat/headers/kiro.txt +1 -1
  20. package/files/{.github → .wl-skills}/skills/_compat/headers/trae.txt +1 -1
  21. package/files/{.github → .wl-skills}/skills/_pipeline.md +23 -11
  22. package/files/{.github → .wl-skills}/skills/_registry.md +10 -4
  23. package/files/{.github → .wl-skills}/skills/core/business-doc-extract/SKILL.md +27 -27
  24. package/files/{.github → .wl-skills}/skills/core/business-doc-extract/USAGE.md +22 -22
  25. package/files/{.github → .wl-skills}/skills/core/business-doc-extract/templates/business-index.md +1 -1
  26. package/files/{.github → .wl-skills}/skills/core/business-doc-extract/templates/business-open-questions.md +1 -1
  27. package/files/{.github → .wl-skills}/skills/core/business-doc-extract/templates/module-dictionary.md +1 -1
  28. package/files/{.github → .wl-skills}/skills/core/business-doc-extract/templates/module-field.md +1 -1
  29. package/files/{.github → .wl-skills}/skills/core/business-doc-extract/templates/module-index.md +2 -2
  30. package/files/{.github → .wl-skills}/skills/core/business-doc-extract/templates/module-requirement.md +2 -2
  31. package/files/{.github → .wl-skills}/skills/core/convention-audit/SKILL.md +52 -7
  32. package/files/{.github → .wl-skills}/skills/core/convention-audit/USAGE.md +1 -1
  33. package/files/{.github → .wl-skills}/skills/core/page-codegen/SKILL.md +33 -13
  34. package/files/{.github → .wl-skills}/skills/core/page-codegen/USAGE.md +1 -1
  35. package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/universal/TPL-DETAIL-TABS.md +7 -7
  36. package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/universal/TPL-DRIVEN.md +5 -5
  37. package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/universal/TPL-FORM-ROUTE.md +2 -2
  38. package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/universal/TPL-RECORD-FORM.md +1 -1
  39. package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/universal/TPL-TREE-LIST.md +1 -1
  40. package/files/{.github → .wl-skills}/skills/core/prototype-scan/SKILL.md +11 -11
  41. package/files/{.github → .wl-skills}/skills/core/prototype-scan/USAGE.md +3 -3
  42. package/files/{.github → .wl-skills}/skills/core/spec-doc-parse/SKILL.md +9 -9
  43. package/files/{.github → .wl-skills}/skills/core/spec-doc-parse/USAGE.md +6 -6
  44. package/files/{.github → .wl-skills}/skills/core/template-extract/SKILL.md +1 -1
  45. package/files/{.github → .wl-skills}/skills/core/template-extract/USAGE.md +1 -1
  46. package/files/{.github → .wl-skills}/skills/ops/code-fix/SKILL.md +135 -96
  47. package/files/{.github → .wl-skills}/skills/sync/_mcp-guardrail.md +3 -3
  48. package/files/{.github → .wl-skills}/skills/sync/dict-sync/SKILL.md +5 -5
  49. package/files/{.github → .wl-skills}/skills/sync/dict-sync/USAGE.md +2 -2
  50. package/files/{.github → .wl-skills}/skills/sync/menu-sync/SKILL.md +6 -6
  51. package/files/{.github → .wl-skills}/skills/sync/menu-sync/USAGE.md +3 -3
  52. package/files/{.github → .wl-skills}/skills/sync/menu-sync/env/guide.md +2 -2
  53. package/files/{.github → .wl-skills}/skills/sync/permission-sync/SKILL.md +3 -3
  54. package/files/{.github → .wl-skills}/skills/sync/permission-sync/USAGE.md +1 -1
  55. package/files/{src → .wl-skills/src}/components/global/C_Splitter/index.vue +2 -2
  56. package/files/{src → .wl-skills/src}/components/local/c_formModal/README.md +1 -1
  57. package/files/{src → .wl-skills/src}/components/local/c_formSections/README.md +2 -2
  58. package/files/{src → .wl-skills/src}/components/remote/BaseForm/README.md +2 -2
  59. package/files/{src → .wl-skills/src}/components/remote/BaseQuery/README.md +4 -4
  60. package/files/{src → .wl-skills/src}/components/remote/BaseTable/README.md +2 -2
  61. package/files/{src → .wl-skills/src}/components/remote/BaseToolbar/README.md +1 -1
  62. package/files/{.github → .wl-skills}/standards/02-code-structure.md +1 -1
  63. package/files/{.github → .wl-skills}/standards/08-git.md +1 -1
  64. package/files/{.github → .wl-skills}/standards/11-form-validation.md +1 -1
  65. package/files/{.github → .wl-skills}/standards/13-platform-components.md +15 -15
  66. package/files/{.github → .wl-skills}/standards/14-layout-containers.md +4 -4
  67. package/files/{.github → .wl-skills}/standards/index.md +1 -1
  68. package/files/{demo → .wl-skills/templates}/README.md +3 -3
  69. package/files/eslint.config.wl-skills.cjs +123 -0
  70. package/lib/ast-rules.js +769 -0
  71. package/lib/vite-plugin-wl-skills.js +97 -0
  72. package/mcp/tools/projectTools.js +17 -1
  73. package/package.json +19 -2
  74. /package/files/{docs → .wl-skills/docs}/jh-date-range.md +0 -0
  75. /package/files/{docs → .wl-skills/docs}/jh-date.md +0 -0
  76. /package/files/{docs → .wl-skills/docs}/jh-dept-picker.md +0 -0
  77. /package/files/{docs → .wl-skills/docs}/jh-drag-row.md +0 -0
  78. /package/files/{docs → .wl-skills/docs}/jh-file-upload.md +0 -0
  79. /package/files/{docs → .wl-skills/docs}/jh-picker.md +0 -0
  80. /package/files/{docs → .wl-skills/docs}/jh-select.md +0 -0
  81. /package/files/{docs → .wl-skills/docs}/jh-text.md +0 -0
  82. /package/files/{docs → .wl-skills/docs}/jh-textarea.md +0 -0
  83. /package/files/{docs → .wl-skills/docs}/jh-user-picker.md +0 -0
  84. /package/files/{docs → .wl-skills/docs}/mock-architecture.md +0 -0
  85. /package/files/{docs → .wl-skills/docs}/request.md +0 -0
  86. /package/files/{.github → .wl-skills}/reports/README.md +0 -0
  87. /package/files/{.github → .wl-skills}/reports/SYS_DICT_INFO.md +0 -0
  88. /package/files/{.github → .wl-skills}/reports//350/247/204/350/214/203/345/256/241/346/237/245/346/212/245/345/221/212.md" +0 -0
  89. /package/files/{.github → .wl-skills}/skills/_compat/headers/agents.txt +0 -0
  90. /package/files/{.github → .wl-skills}/skills/_compat/headers/claude-code.txt +0 -0
  91. /package/files/{.github → .wl-skills}/skills/_compat/headers/cline.txt +0 -0
  92. /package/files/{.github → .wl-skills}/skills/_compat/headers/cursor-rules.txt +0 -0
  93. /package/files/{.github → .wl-skills}/skills/_compat/headers/github-copilot.txt +0 -0
  94. /package/files/{.github → .wl-skills}/skills/_compat/headers/qoder.txt +0 -0
  95. /package/files/{.github → .wl-skills}/skills/_compat/headers/windsurf.txt +0 -0
  96. /package/files/{.github → .wl-skills}/skills/core/api-contract/SKILL.md +0 -0
  97. /package/files/{.github → .wl-skills}/skills/core/api-contract/USAGE.md +0 -0
  98. /package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/_index.md +0 -0
  99. /package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/domains/_CONTRIBUTING.md +0 -0
  100. /package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/domains/produce/TPL-OPERATION-STATION.md +0 -0
  101. /package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/domains/sale/README.md +0 -0
  102. /package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/universal/TPL-CHANGE-HISTORY.md +0 -0
  103. /package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/universal/TPL-LIST.md +0 -0
  104. /package/files/{.github → .wl-skills}/skills/core/page-codegen/templates/universal/TPL-MASTER-DETAIL.md +0 -0
  105. /package/files/{.github → .wl-skills}/skills/domain/README.md +0 -0
  106. /package/files/{.github → .wl-skills}/skills/ops/code-fix/USAGE.md +0 -0
  107. /package/files/{.github → .wl-skills}/skills/sync/env.local.json +0 -0
  108. /package/files/{.github → .wl-skills}/skills/sync/menu-sync/env/env.local.json +0 -0
  109. /package/files/{src → .wl-skills/src}/components/global/C_ParentView/index.vue +0 -0
  110. /package/files/{src → .wl-skills/src}/components/global/C_RightToolbar/data.ts +0 -0
  111. /package/files/{src → .wl-skills/src}/components/global/C_RightToolbar/index.scss +0 -0
  112. /package/files/{src → .wl-skills/src}/components/global/C_RightToolbar/index.vue +0 -0
  113. /package/files/{src → .wl-skills/src}/components/global/C_Splitter/index.scss +0 -0
  114. /package/files/{src → .wl-skills/src}/components/global/C_SvgIcon/index.scss +0 -0
  115. /package/files/{src → .wl-skills/src}/components/global/C_SvgIcon/index.vue +0 -0
  116. /package/files/{src → .wl-skills/src}/components/global/C_SvgIcon/svgicon.js +0 -0
  117. /package/files/{src → .wl-skills/src}/components/global/C_TagStatus/README.md +0 -0
  118. /package/files/{src → .wl-skills/src}/components/global/C_TagStatus/config.ts +0 -0
  119. /package/files/{src → .wl-skills/src}/components/global/C_TagStatus/index.scss +0 -0
  120. /package/files/{src → .wl-skills/src}/components/global/C_TagStatus/index.vue +0 -0
  121. /package/files/{src → .wl-skills/src}/components/global/C_TagStatus/types.ts +0 -0
  122. /package/files/{src → .wl-skills/src}/components/global/C_Tree/README.md +0 -0
  123. /package/files/{src → .wl-skills/src}/components/global/C_Tree/data.ts +0 -0
  124. /package/files/{src → .wl-skills/src}/components/global/C_Tree/index.scss +0 -0
  125. /package/files/{src → .wl-skills/src}/components/global/C_Tree/index.vue +0 -0
  126. /package/files/{src → .wl-skills/src}/components/global/C_Tree/types.ts +0 -0
  127. /package/files/{src → .wl-skills/src}/components/local/c_formModal/data.ts +0 -0
  128. /package/files/{src → .wl-skills/src}/components/local/c_formModal/index.scss +0 -0
  129. /package/files/{src → .wl-skills/src}/components/local/c_formModal/index.vue +0 -0
  130. /package/files/{src → .wl-skills/src}/components/local/c_formSections/data.ts +0 -0
  131. /package/files/{src → .wl-skills/src}/components/local/c_formSections/index.scss +0 -0
  132. /package/files/{src → .wl-skills/src}/components/local/c_formSections/index.vue +0 -0
  133. /package/files/{src → .wl-skills/src}/components/local/c_listModal/data.ts +0 -0
  134. /package/files/{src → .wl-skills/src}/components/local/c_listModal/index.scss +0 -0
  135. /package/files/{src → .wl-skills/src}/components/local/c_listModal/index.vue +0 -0
  136. /package/files/{src → .wl-skills/src}/components/local/c_spliterTitle/index.scss +0 -0
  137. /package/files/{src → .wl-skills/src}/components/local/c_spliterTitle/index.vue +0 -0
  138. /package/files/{src → .wl-skills/src}/components/remote/AGGrid/README.md +0 -0
  139. /package/files/{src → .wl-skills/src}/types/page.ts +0 -0
  140. /package/files/{.github → .wl-skills}/standards/01-toolchain.md +0 -0
  141. /package/files/{.github → .wl-skills}/standards/03-comments.md +0 -0
  142. /package/files/{.github → .wl-skills}/standards/04-coding-basics.md +0 -0
  143. /package/files/{.github → .wl-skills}/standards/05-logging.md +0 -0
  144. /package/files/{.github → .wl-skills}/standards/06-security.md +0 -0
  145. /package/files/{.github → .wl-skills}/standards/07-config.md +0 -0
  146. /package/files/{.github → .wl-skills}/standards/09-typescript.md +0 -0
  147. /package/files/{.github → .wl-skills}/standards/10-pinia.md +0 -0
  148. /package/files/{.github → .wl-skills}/standards/12-base-table.md +0 -0
  149. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-add/api.md +0 -0
  150. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-add/data.ts +0 -0
  151. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-add/index.scss +0 -0
  152. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-add/index.vue +0 -0
  153. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-add-form/data.ts +0 -0
  154. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-add-form/index.scss +0 -0
  155. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-add-form/index.vue +0 -0
  156. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change/data.ts +0 -0
  157. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change/index.scss +0 -0
  158. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change/index.vue +0 -0
  159. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change-form/data.ts +0 -0
  160. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change-form/index.scss +0 -0
  161. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change-form/index.vue +0 -0
  162. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change-history/data.ts +0 -0
  163. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change-history/index.scss +0 -0
  164. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-apply-change-history/index.vue +0 -0
  165. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-archive/api.md +0 -0
  166. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-archive/data.ts +0 -0
  167. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-archive/index.scss +0 -0
  168. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-archive/index.vue +0 -0
  169. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-detail/api.md +0 -0
  170. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-detail/data.ts +0 -0
  171. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-detail/index.scss +0 -0
  172. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-customer-detail/index.vue +0 -0
  173. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-temp-customer-archive/api.md +0 -0
  174. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-temp-customer-archive/data.ts +0 -0
  175. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-temp-customer-archive/index.scss +0 -0
  176. /package/files/{demo → .wl-skills/templates}/produce/aiflow/mmwr-temp-customer-archive/index.vue +0 -0
  177. /package/files/{demo → .wl-skills/templates}/sale/demo/add-demo/data.ts +0 -0
  178. /package/files/{demo → .wl-skills/templates}/sale/demo/add-demo/index.scss +0 -0
  179. /package/files/{demo → .wl-skills/templates}/sale/demo/add-demo/index.vue +0 -0
  180. /package/files/{demo → .wl-skills/templates}/sale/demo/billet-flame-cut-plan/data.ts +0 -0
  181. /package/files/{demo → .wl-skills/templates}/sale/demo/billet-flame-cut-plan/index.scss +0 -0
  182. /package/files/{demo → .wl-skills/templates}/sale/demo/billet-flame-cut-plan/index.vue +0 -0
  183. /package/files/{demo → .wl-skills/templates}/sale/demo/domestic-trade-order/data.ts +0 -0
  184. /package/files/{demo → .wl-skills/templates}/sale/demo/domestic-trade-order/index.scss +0 -0
  185. /package/files/{demo → .wl-skills/templates}/sale/demo/domestic-trade-order/index.vue +0 -0
  186. /package/files/{demo → .wl-skills/templates}/sale/demo/heat-batch-return/data.ts +0 -0
  187. /package/files/{demo → .wl-skills/templates}/sale/demo/heat-batch-return/index.scss +0 -0
  188. /package/files/{demo → .wl-skills/templates}/sale/demo/heat-batch-return/index.vue +0 -0
  189. /package/files/{demo → .wl-skills/templates}/sale/demo/heat-batch-return/meltDialog.vue +0 -0
  190. /package/files/{demo → .wl-skills/templates}/sale/demo/metallurgical-spec/data.ts +0 -0
  191. /package/files/{demo → .wl-skills/templates}/sale/demo/metallurgical-spec/index.scss +0 -0
  192. /package/files/{demo → .wl-skills/templates}/sale/demo/metallurgical-spec/index.vue +0 -0
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.10.0
4
+ * wl-skills-kit CLI v2.11.0
5
5
  *
6
6
  * 命令:
7
7
  * init 全量安装(默认,向后兼容)
@@ -22,6 +22,13 @@ const fs = require("fs");
22
22
  const path = require("path");
23
23
  const crypto = require("crypto");
24
24
 
25
+ // ─── AST 规则引擎(v2.10.1+,语义级约束检测)──────────────────────────
26
+ const {
27
+ runAstRules,
28
+ getStagedFiles,
29
+ hasAstAvailable,
30
+ } = require("../lib/ast-rules");
31
+
25
32
  const FILES_DIR = path.resolve(__dirname, "..", "files");
26
33
  const TARGET_DIR = process.cwd();
27
34
  const MANIFEST_NAME = ".wl-skills-manifest.json";
@@ -50,12 +57,16 @@ const KNOWN_FLAGS = new Set([
50
57
  "-h",
51
58
  "--domain",
52
59
  "--all",
60
+ "--pre-commit",
61
+ "--strict",
53
62
  ]);
54
63
 
55
64
  const dryRun = args.includes("--dry-run");
56
65
  const showHelp = args.includes("--help") || args.includes("-h");
57
66
  const keepReports = args.includes("--keep-reports");
58
67
  const force = args.includes("--force");
68
+ const preCommit = args.includes("--pre-commit");
69
+ const strict = args.includes("--strict");
59
70
 
60
71
  // 校验所有 flag 是否已知(--help 优先,跳过校验直接显示帮助)
61
72
  if (!showHelp) {
@@ -100,6 +111,7 @@ if (showHelp) {
100
111
  check 环境预检(Node / 工具链 / MCP 配置 / manifest)
101
112
  diff 对比已安装文件与当前 kit 版本的差异
102
113
  validate 静态检查 src/views 页面文件、AGGrid、skills-ui runtime、mock
114
+ v2.10.1+ 集成 AST 语义级检测(R1~R7),覆盖正则无法检测的规则
103
115
  validate-page validate 的别名,适用于单页/目录检查
104
116
  doctor-ui 检查 @agile-team/wk-skills-ui 接入完整性
105
117
  export 导出 reports/SYS_* 数据为 xlsx
@@ -107,10 +119,12 @@ if (showHelp) {
107
119
 
108
120
  选项:
109
121
  --dry-run 预览模式,不实际写入/删除任何文件
110
- --keep-reports clean 命令保留 .github/reports/(默认一起删除)
122
+ --keep-reports clean 命令保留 .wl-skills/reports/(默认一起删除)
111
123
  --force 强制执行,跳过同版本检测(忽略已安装状态)
112
124
  --domain <name> mock-clean 指定要清理的业务域(如 sale、mdata)
113
125
  --all mock-clean 清理全部 mock(保留 _utils.ts)
126
+ --pre-commit validate 仅检测 git staged 文件,error 阻断提交,warn 仅提示
127
+ --strict validate 的 error 和 warn 都导致退出码 1(CI 用)
114
128
  --help 显示帮助
115
129
 
116
130
  示例:
@@ -131,7 +145,7 @@ if (showHelp) {
131
145
  npx @agile-team/wl-skills-kit mock-clean --all --dry-run 预览将要清理的 mock 文件
132
146
 
133
147
  保护路径(init / update 不覆盖已存在的):
134
- .github/reports/ AI 生成报告(团队累积数据,存在则跳过)
148
+ .wl-skills/reports/ AI 生成报告(团队累积数据,存在则跳过)
135
149
 
136
150
  清理保护路径(clean 不删除):
137
151
  src/components/ 通用组件(被业务页面 import,构建必需)
@@ -232,20 +246,20 @@ function writeManifest(data) {
232
246
  }
233
247
 
234
248
  // 受保护路径(clean 不删除)
235
- const PROTECTED_PREFIXES = ["src/components/", "src/types/"];
249
+ const PROTECTED_PREFIXES = ["src/components/", "src/types/", ".wl-skills/src/components/", ".wl-skills/src/types/"];
236
250
  function isProtected(relPath) {
237
251
  return PROTECTED_PREFIXES.some((p) => relPath.startsWith(p));
238
252
  }
239
253
 
240
254
  // reports/ 中的 AI 生成报告:init/update 遇到已存在不覆盖(团队累积数据)
241
255
  function isReportFile(relPath) {
242
- return relPath.startsWith(".github/reports/") && relPath.endsWith(".md");
256
+ return (relPath.startsWith(".wl-skills/reports/") || relPath.startsWith(".github/reports/")) && relPath.endsWith(".md");
243
257
  }
244
258
 
245
259
  // ─── 旧版遗留路径(v1.x/v2.0 → v2.1 迁移清理)───────────────────────────
246
260
  // update 时自动检测并移除,避免旧结构与新结构并存产生歧义。
247
261
  const LEGACY_PATHS = [
248
- // Skill 目录重组:flat → core/sync/ops 分级(v2.1)
262
+ // v2.1: Skill 目录重组 flat → core/sync/ops
249
263
  ".github/skills/prototype-scan/SKILL.md",
250
264
  ".github/skills/api-contract/SKILL.md",
251
265
  ".github/skills/page-codegen/SKILL.md",
@@ -261,34 +275,42 @@ const LEGACY_PATHS = [
261
275
  ".github/skills/menu-sync/SKILL.md",
262
276
  ".github/skills/menu-sync/env/env.local.json",
263
277
  ".github/skills/menu-sync/env/guide.md",
264
- ".github/skills/convention-extract/SKILL.md", // 已更名为 convention-audit
265
- // docs/ 废弃文件:内容已迁移至 guides/ 或 reports/(v2.0)
278
+ ".github/skills/convention-extract/SKILL.md",
266
279
  ".github/docs/menu-sync-design.md",
267
280
  ".github/docs/use-skill.md",
268
281
  ".github/docs/wl-skills-kit.md",
269
- ".github/docs/SYS_MENU_INFO.md", // 已迁移至 reports/
270
- // _compat/ 旧说明文件(v2.0 → v2.1 重构为可执行配置层)
282
+ ".github/docs/SYS_MENU_INFO.md",
271
283
  ".github/skills/_compat/ai-model-matrix.md",
272
284
  ".github/skills/_compat/editor-setup.md",
273
285
  ];
274
286
 
287
+ // ─── v2.11 迁移:.github/ → .wl-skills/ 目录重构 ────────────────────────────
288
+ // update 时自动检测旧目录结构并清理
289
+ const LEGACY_DIR_PREFIXES = [
290
+ ".github/skills/",
291
+ ".github/standards/",
292
+ ".github/guides/",
293
+ ".github/reports/",
294
+ ];
295
+
275
296
  // ─── 编辑器配置生成(从 _compat/editors.json 读取,特化 frontmatter 注入)─────
276
297
 
277
298
  const AUTO_HEADER_NOTE =
278
- "<!-- 由 @agile-team/wl-skills-kit 自动生成。源文件:.github/copilot-instructions.md -->\n" +
299
+ "<!-- 由 @agile-team/wl-skills-kit 自动生成。薄壳入口 → .wl-skills/copilot-instructions-full.md -->\n" +
279
300
  "<!-- 请勿手动编辑本文件,更新时重新执行:npx @agile-team/wl-skills-kit@latest update -->\n\n";
280
301
 
281
302
  function getEditorConfigs(raw) {
303
+ // v2.11+: _compat 目录已迁移到 .wl-skills/skills/_compat/
282
304
  const editorsJsonPath = path.join(
283
305
  FILES_DIR,
284
- ".github",
306
+ ".wl-skills",
285
307
  "skills",
286
308
  "_compat",
287
309
  "editors.json",
288
310
  );
289
311
  const headersDir = path.join(
290
312
  FILES_DIR,
291
- ".github",
313
+ ".wl-skills",
292
314
  "skills",
293
315
  "_compat",
294
316
  "headers",
@@ -341,6 +363,13 @@ function runInstall(incremental) {
341
363
 
342
364
  const oldManifest = readManifest();
343
365
 
366
+ // ── 约束基础设施:无论版本是否相同,都确保 pre-commit hook 和 eslint 配置就绪 ──
367
+ // 这样即使 early-return(同版本跳过文件复制),hook 也会被创建/更新
368
+ if (!dryRun) {
369
+ ensurePreCommitHook(TARGET_DIR);
370
+ ensureEslintConfig(TARGET_DIR);
371
+ }
372
+
344
373
  // ── 版本去重:同版本跳过,不同版本自动增量更新 ──────────────────────
345
374
  if (oldManifest && !force) {
346
375
  if (oldManifest.version === PKG.version) {
@@ -374,6 +403,9 @@ function runInstall(incremental) {
374
403
  if (dryRun) console.log(" [Step 1] files/ 静态文件:\n");
375
404
 
376
405
  for (const relPath of files) {
406
+ // eslint 模板由 ensureEslintConfig 单独处理,不通过 Step 1 复制
407
+ if (relPath === "eslint.config.wl-skills.cjs") continue;
408
+
377
409
  const src = path.join(FILES_DIR, relPath);
378
410
  const dest = path.join(TARGET_DIR, relPath);
379
411
  const srcHash = fileMd5(src);
@@ -453,6 +485,8 @@ function runInstall(incremental) {
453
485
  if (incremental) {
454
486
  let migrated = 0;
455
487
  if (dryRun) console.log("\n [Step 3] 旧版遗留文件检查(迁移清理):\n");
488
+
489
+ // v2.1 旧版单文件清理
456
490
  for (const legacyRel of LEGACY_PATHS) {
457
491
  const legacyFull = path.join(TARGET_DIR, legacyRel);
458
492
  if (fs.existsSync(legacyFull)) {
@@ -464,6 +498,28 @@ function runInstall(incremental) {
464
498
  migrated++;
465
499
  }
466
500
  }
501
+
502
+ // v2.11 目录重构迁移:.github/skills|standards|guides|reports/ → .wl-skills/
503
+ for (const prefix of LEGACY_DIR_PREFIXES) {
504
+ const legacyDir = path.join(TARGET_DIR, prefix);
505
+ if (fs.existsSync(legacyDir)) {
506
+ const legacyFiles = walkDir(legacyDir, legacyDir);
507
+ for (const f of legacyFiles) {
508
+ const legacyFile = path.join(legacyDir, f);
509
+ if (dryRun) {
510
+ console.log(" 迁移清理 " + prefix + f + " (v2.11 目录重构)");
511
+ } else {
512
+ removeFileAndEmptyParents(legacyFile);
513
+ }
514
+ migrated++;
515
+ }
516
+ // 删除空目录
517
+ if (!dryRun && fs.existsSync(legacyDir)) {
518
+ try { fs.rmSync(legacyDir, { recursive: true, force: true }); } catch {}
519
+ }
520
+ }
521
+ }
522
+
467
523
  if (!dryRun && migrated > 0) {
468
524
  console.log(
469
525
  " 迁移: " +
@@ -551,10 +607,126 @@ function runInstall(incremental) {
551
607
  " ℹ 规范插件:建议执行 npx @robot-admin/git-standards init 接入代码质量与提交规范。",
552
608
  );
553
609
  console.log("");
610
+
554
611
  }
555
612
 
556
613
  // ─── 命令: clean ────────────────────────────────────────────────────────
557
614
 
615
+ /**
616
+ * 确保 .husky/pre-commit 包含 wl-skills validate --pre-commit
617
+ * — 这是让 AI 生成的代码"绕不开"规范的核心拦截点
618
+ *
619
+ * 策略:
620
+ * 1. 如果 .husky/pre-commit 不存在 → 创建(包含 validate 调用)
621
+ * 2. 如果存在但不含 wl-skills → 追加(不破坏用户已有的 hook 内容)
622
+ * 3. 如果存在且已含但格式过旧 → 刷新为最新格式
623
+ * 4. 如果存在且格式最新 → 跳过
624
+ *
625
+ * hook 使用 npx 动态解析,避免硬编码 node_modules 路径在 pnpm 下失效。
626
+ * 包含存在性守卫:kit 未安装时优雅跳过,不阻断提交。
627
+ */
628
+ function ensurePreCommitHook(targetDir) {
629
+ const huskyDir = path.join(targetDir, ".husky");
630
+ const preCommitPath = path.join(huskyDir, ".husky/pre-commit");
631
+
632
+ // 只有 git 仓库才创建 husky hook
633
+ if (!fs.existsSync(path.join(targetDir, ".git"))) return;
634
+ if (!fs.existsSync(huskyDir)) return;
635
+
636
+ const VALIDATE_MARKER = "wl-skills validate --pre-commit";
637
+ // 最新 hook 版本标记,用于检测旧格式并刷新
638
+ const HOOK_VERSION_TAG = "# wl-skills-hook-v2";
639
+
640
+ const hookContent =
641
+ "#!/usr/bin/env sh\n" +
642
+ HOOK_VERSION_TAG + "\n" +
643
+ "# wl-skills-kit 自动管理:提交前规范检测(error 阻断提交)\n" +
644
+ "# 如果 node_modules 不存在或 kit 未安装,优雅跳过,不阻断提交\n" +
645
+ 'if [ -f "node_modules/@agile-team/wl-skills-kit/bin/wl-skills.js" ]; then\n' +
646
+ ' node node_modules/@agile-team/wl-skills-kit/bin/wl-skills.js validate --pre-commit\n' +
647
+ " if [ $? -ne 0 ]; then\n" +
648
+ ' echo ""\n' +
649
+ ' echo " ✖ 规范检测未通过,提交已阻断。修复后重新 git add + git commit"\n' +
650
+ " exit 1\n" +
651
+ " fi\n" +
652
+ "else\n" +
653
+ ' echo " ⚠ wl-skills-kit 未安装(node_modules 中未找到),跳过提交前检测"\n' +
654
+ "fi\n";
655
+
656
+ const preCommitFile = path.join(huskyDir, "pre-commit");
657
+
658
+ if (!fs.existsSync(preCommitFile)) {
659
+ fs.writeFileSync(preCommitFile, hookContent, "utf8");
660
+ try { fs.chmodSync(preCommitFile, 0o755); } catch {}
661
+ console.log(" ✔ 已创建 .husky/pre-commit(提交前自动运行 wl-skills validate)");
662
+ console.log(" → 每次 git commit 时自动检测页面规范,error 级别阻断提交");
663
+ console.log(" → kit 未安装时自动跳过,不阻断提交");
664
+ console.log("");
665
+ } else {
666
+ const existing = fs.readFileSync(preCommitFile, "utf8");
667
+
668
+ // 已有最新版本标记 → 跳过
669
+ if (existing.includes(HOOK_VERSION_TAG)) return;
670
+
671
+ // 有旧 marker 但格式过旧 → 替换整段 wl-skills 块为最新格式
672
+ if (existing.includes(VALIDATE_MARKER)) {
673
+ // 删除旧的 wl-skills 块(从 VALIDATE_MARKER 前的注释行到对应的 fi)
674
+ const lines = existing.split("\n");
675
+ const filtered = [];
676
+ let skipMode = false;
677
+ for (const line of lines) {
678
+ if (line.includes(VALIDATE_MARKER) || line.includes("wl-skills-kit 自动")) {
679
+ skipMode = true;
680
+ continue;
681
+ }
682
+ if (skipMode && (line.includes("exit 1") || line.trim() === "fi")) {
683
+ skipMode = false;
684
+ continue;
685
+ }
686
+ if (!skipMode) filtered.push(line);
687
+ }
688
+ // 去尾部空行
689
+ while (filtered.length > 0 && filtered[filtered.length - 1].trim() === "") {
690
+ filtered.pop();
691
+ }
692
+ // 追加最新格式
693
+ const updated = filtered.join("\n").trimEnd() + "\n\n" + hookContent.replace("#!/usr/bin/env sh\n", "");
694
+ fs.writeFileSync(preCommitFile, updated, "utf8");
695
+ try { fs.chmodSync(preCommitFile, 0o755); } catch {}
696
+ console.log(" ✔ 已刷新 .husky/pre-commit 为最新格式(v2,含存在性守卫)");
697
+ console.log("");
698
+ return;
699
+ }
700
+
701
+ // 无 marker → 追加
702
+ const addition = "\n" + hookContent.replace("#!/usr/bin/env sh\n", "");
703
+ fs.writeFileSync(preCommitFile, existing.trimEnd() + "\n" + addition, "utf8");
704
+ try { fs.chmodSync(preCommitFile, 0o755); } catch {}
705
+ console.log(" ✔ 已在 .husky/pre-commit 追加 wl-skills validate(提交前规范检测)");
706
+ console.log(" → kit 未安装时自动跳过,不阻断提交");
707
+ console.log("");
708
+ }
709
+ }
710
+
711
+ /**
712
+ * 确保业务项目有 ESLint 配置
713
+ * 策略:如果项目根目录没有 eslint.config.cjs,从 kit 复制模板
714
+ * 如果已有,不覆盖(尊重用户自定义配置)
715
+ */
716
+ function ensureEslintConfig(targetDir) {
717
+ const targetEslint = path.join(targetDir, "eslint.config.cjs");
718
+ if (fs.existsSync(targetEslint)) return; // 用户已有自定义配置
719
+
720
+ const templatePath = path.join(FILES_DIR, "eslint.config.wl-skills.cjs");
721
+ if (!fs.existsSync(templatePath)) return;
722
+
723
+ const content = fs.readFileSync(templatePath, "utf8");
724
+ fs.writeFileSync(targetEslint, content, "utf8");
725
+ console.log(" ✔ 已创建 eslint.config.cjs(wl-skills-kit 模板)");
726
+ console.log(" → 安装依赖后生效:pnpm add -D eslint eslint-plugin-vue vue-eslint-parser @typescript-eslint/parser @typescript-eslint/eslint-plugin");
727
+ console.log("");
728
+ }
729
+
558
730
  function runClean() {
559
731
  console.log("");
560
732
  console.log(" wl-skills-kit v" + PKG.version + " [clean]");
@@ -573,12 +745,12 @@ function runClean() {
573
745
  const allFiles = Object.keys(manifest.files);
574
746
  const toRemove = allFiles.filter((f) => {
575
747
  if (isProtected(f)) return false;
576
- if (keepReports && f.startsWith(".github/reports/")) return false;
748
+ if (keepReports && (f.startsWith(".wl-skills/reports/") || f.startsWith(".github/reports/"))) return false;
577
749
  return true;
578
750
  });
579
751
  const toKeep = allFiles.filter((f) => {
580
752
  if (isProtected(f)) return true;
581
- if (keepReports && f.startsWith(".github/reports/")) return true;
753
+ if (keepReports && (f.startsWith(".wl-skills/reports/") || f.startsWith(".github/reports/"))) return true;
582
754
  return false;
583
755
  });
584
756
 
@@ -614,7 +786,7 @@ function runClean() {
614
786
  console.log(
615
787
  " 保留: " +
616
788
  toKeep.length +
617
- " 个文件(src/components/ + src/types/ + .github/reports/)",
789
+ " 个文件(src/components/ + src/types/ + .wl-skills/reports/)",
618
790
  );
619
791
  } else {
620
792
  console.log(
@@ -633,6 +805,7 @@ function expectedManifestFiles() {
633
805
  for (const relPath of files) {
634
806
  expected[relPath] = fileMd5(path.join(FILES_DIR, relPath));
635
807
  }
808
+ // v2.11+: copilot-instructions.md 是薄壳入口,完整地图在 .wl-skills/
636
809
  const instructionsSrc = path.join(
637
810
  FILES_DIR,
638
811
  ".github",
@@ -665,14 +838,36 @@ function runCheck() {
665
838
  const nodeMajor = Number(process.versions.node.split(".")[0]);
666
839
  add("Node 版本", nodeMajor >= 16, process.versions.node + "(要求 >=16)");
667
840
 
668
- const toolFiles = [".prettierrc.js", "eslint.config.ts", ".husky"];
669
- for (const rel of toolFiles) {
670
- add(
671
- rel,
672
- fs.existsSync(path.join(TARGET_DIR, rel)),
673
- fs.existsSync(path.join(TARGET_DIR, rel)) ? "存在" : "缺失",
674
- );
841
+ // 工具链检测:支持多种可能的文件名
842
+ const prettierExists =
843
+ fs.existsSync(path.join(TARGET_DIR, ".prettierrc.js")) ||
844
+ fs.existsSync(path.join(TARGET_DIR, ".prettierrc")) ||
845
+ fs.existsSync(path.join(TARGET_DIR, ".prettierrc.cjs"));
846
+ add(".prettierrc", prettierExists, prettierExists ? "存在" : "缺失");
847
+
848
+ const eslintExists =
849
+ fs.existsSync(path.join(TARGET_DIR, "eslint.config.ts")) ||
850
+ fs.existsSync(path.join(TARGET_DIR, "eslint.config.mjs")) ||
851
+ fs.existsSync(path.join(TARGET_DIR, "eslint.config.cjs")) ||
852
+ fs.existsSync(path.join(TARGET_DIR, "eslint.config.js"));
853
+ add("eslint.config", eslintExists, eslintExists ? "存在" : "缺失");
854
+
855
+ // husky 目录检测
856
+ const huskyExists = fs.existsSync(path.join(TARGET_DIR, ".husky"));
857
+ add(".husky", huskyExists, huskyExists ? "存在" : "缺失");
858
+
859
+ // pre-commit hook 内容检测(不只检查目录存在)
860
+ const preCommitPath = path.join(TARGET_DIR, ".husky", "pre-commit");
861
+ let preCommitHasValidate = false;
862
+ if (fs.existsSync(preCommitPath)) {
863
+ const hookContent = fs.readFileSync(preCommitPath, "utf8");
864
+ preCommitHasValidate = hookContent.includes("wl-skills validate --pre-commit");
675
865
  }
866
+ add(
867
+ ".husky/pre-commit (wl-skills validate)",
868
+ preCommitHasValidate,
869
+ preCommitHasValidate ? "已配置规范检测" : huskyExists ? "存在但未配置 wl-skills validate" : "不存在",
870
+ );
676
871
 
677
872
  const manifest = readManifest();
678
873
  add(
@@ -859,10 +1054,37 @@ function findMockFiles() {
859
1054
  function runValidate() {
860
1055
  const scanPath =
861
1056
  args.find((a) => !a.startsWith("-") && a !== command) || "src/views";
862
- const pages = scanPageDirs(scanPath);
1057
+
1058
+ // --pre-commit 模式:获取 staged 文件列表,用于过滤
1059
+ let stagedSet = null;
1060
+ if (preCommit) {
1061
+ const staged = getStagedFiles(TARGET_DIR);
1062
+ if (staged.length === 0) {
1063
+ console.log("");
1064
+ console.log(" wl-skills-kit v" + PKG.version + " [validate --pre-commit]");
1065
+ console.log(" ⚠ 无 staged 的 .vue/.ts 文件,跳过检测");
1066
+ console.log("");
1067
+ return;
1068
+ }
1069
+ stagedSet = new Set(staged.map((f) => f.replace(/\\/g, "/")));
1070
+ }
1071
+
1072
+ const allPages = scanPageDirs(scanPath);
1073
+ // 在 pre-commit 模式下,只保留包含 staged 文件的页面目录
1074
+ const pages = preCommit
1075
+ ? allPages.filter((page) =>
1076
+ Array.from(stagedSet).some(
1077
+ (f) =>
1078
+ f.startsWith(page.dir + "/") ||
1079
+ f === page.dir + "/index.vue" ||
1080
+ f === page.dir + "/data.ts",
1081
+ ),
1082
+ )
1083
+ : allPages;
1084
+
863
1085
  console.log("");
864
- console.log(" wl-skills-kit v" + PKG.version + " [" + command + "]");
865
- console.log(" 扫描目录: " + scanPath);
1086
+ console.log(" wl-skills-kit v" + PKG.version + " [" + command + "]" + (preCommit ? " [pre-commit]" : ""));
1087
+ console.log(" 扫描目录: " + scanPath + (preCommit ? "(仅 staged 文件)" : ""));
866
1088
  console.log("");
867
1089
 
868
1090
  if (pages.length === 0) {
@@ -1021,18 +1243,185 @@ function runValidate() {
1021
1243
  }
1022
1244
  }
1023
1245
 
1024
- console.log(" 页面目录: " + pages.length);
1246
+ // ── AST 语义级规则检测(v2.10.1+)─────────────────────────────────
1247
+ // 补充正则无法覆盖的 7 条语义规则(R1~R7),与正则规则合并输出
1248
+ // 在 pre-commit 模式下复用上面已计算的 stagedSet
1249
+ const astStagedFiles = preCommit && stagedSet ? Array.from(stagedSet) : undefined;
1250
+ const astResult = runAstRules(TARGET_DIR, scanPath, {
1251
+ stagedFiles: astStagedFiles,
1252
+ });
1253
+ // 合并 AST 结果(降级和正常都 push)
1254
+ issues.push(...astResult.issues);
1255
+
1256
+ // ── 输出 ───────────────────────────────────────────────────────────
1257
+ console.log(" 页面目录: " + pages.length + (astResult.pages ? "(AST 扫描 " + astResult.pages + ")" : ""));
1025
1258
  console.log(" 提示项: " + issues.length);
1026
1259
  console.log("");
1027
1260
  const errors = issues.filter((issue) => issue.level === "error").length;
1261
+ const warns = issues.filter(
1262
+ (issue) => issue.level === "warn" || issue.level === undefined,
1263
+ ).length;
1028
1264
  for (const issue of issues) {
1029
1265
  const icon =
1030
1266
  issue.level === "error" ? "✖" : issue.level === "info" ? "ℹ" : "⚠";
1031
1267
  console.log(" " + icon + " " + issue.dir + " — " + issue.text);
1032
1268
  }
1033
- if (issues.length === 0) console.log(" 页面文件完整性检查通过");
1269
+ if (issues.length === 0) console.log(" \u2714 \u9875\u9762\u6587\u4ef6\u5b8c\u6574\u6027\u68c0\u67e5\u901a\u8fc7");
1270
+ console.log("");
1271
+
1272
+ // ── \u4fee\u590d\u5efa\u8bae\u8f93\u51fa\uff08P0 \u6539\u8fdb\uff1a\u963b\u65ad\u65f6\u544a\u8bc9\u5f00\u53d1\u8005\u600e\u4e48\u4fee\uff09─────────────────────
1273
+ const blockingIssues = issues.filter((i) => i.level === "error" || (strict && i.level === "warn"));
1274
+ if (blockingIssues.length > 0) {
1275
+ printFixSuggestions(blockingIssues);
1276
+ }
1277
+
1278
+ if (preCommit) {
1279
+ // pre-commit \u6a21\u5f0f\uff1aerror \u963b\u65ad\u63d0\u4ea4
1280
+ // --pre-commit --strict \u7ec4\u5408\uff1aerror + warn \u90fd\u963b\u65ad
1281
+ const failCount = strict ? errors + warns : errors;
1282
+ if (failCount > 0) {
1283
+ console.log(
1284
+ " \u2716 pre-commit \u68c0\u67e5\u53d1\u73b0 " +
1285
+ errors + " \u4e2a error" +
1286
+ (strict && warns > 0 ? " + " + warns + " \u4e2a warn\uff08strict \u6a21\u5f0f\uff09" : "") +
1287
+ "\uff0c\u63d0\u4ea4\u5df2\u963b\u65ad",
1288
+ );
1289
+ console.log(" \u2192 \u8bf7\u4fee\u590d\u540e\u91cd\u65b0 git add + git commit");
1290
+ console.log(" \u2192 \u5982\u9700 AI \u8f85\u52a9\u4fee\u590d\uff0c\u8bf7\u89e6\u53d1\uff1a\u89c4\u8303\u5ba1\u8ba1 \u2192 \u81ea\u52a8\u4fee\u590d \u2192 \u590d\u626b\u9a8c\u8bc1");
1291
+ console.log("");
1292
+ process.exitCode = 1;
1293
+ } else {
1294
+ console.log(" \u2714 pre-commit \u68c0\u67e5\u901a\u8fc7\uff08" + issues.length + " \u4e2a\u63d0\u793a\u9879\u4e0d\u963b\u65ad\u63d0\u4ea4\uff09");
1295
+ console.log("");
1296
+ }
1297
+ } else if (strict) {
1298
+ // --strict \u6a21\u5f0f\uff08CI \u7528\uff09\uff1aerror \u548c warn \u5bfc\u81f4\u5931\u8d25\uff0cinfo \u4e0d\u8ba1\u5165
1299
+ if (errors > 0 || warns > 0) {
1300
+ console.log(
1301
+ " \u2716 strict \u6a21\u5f0f\u68c0\u67e5\u53d1\u73b0 " + errors + " error / " + warns + " warn\uff0cCI \u5df2\u963b\u65ad",
1302
+ );
1303
+ console.log(" \u2192 --strict \u6a21\u5f0f\u4e0b warn \u4e5f\u4f1a\u5931\u8d25\uff0c\u8bf7\u4fee\u590d");
1304
+ process.exitCode = 1;
1305
+ } else {
1306
+ console.log(" \u2714 strict \u6a21\u5f0f\u68c0\u67e5\u5168\u90e8\u901a\u8fc7");
1307
+ }
1308
+ console.log("");
1309
+ } else {
1310
+ // \u666e\u901a\u6a21\u5f0f\uff1a\u53ea\u6709 error \u6216 warn \u624d exit 1\uff0cinfo \u4ec5\u63d0\u793a
1311
+ if (errors > 0 || warns > 0) process.exitCode = 1;
1312
+ }
1313
+ }
1314
+
1315
+ // ── \u4fee\u590d\u5efa\u8bae\u6620\u5c04\u8868\uff08P0\uff1a\u8ba9\u5f00\u53d1\u8005\u77e5\u9053\u600e\u4e48\u4fee\uff09──────────────────────────────
1316
+ const FIX_SUGGESTIONS = {
1317
+ // \u6b63\u5219\u7ea7\u68c0\u67e5
1318
+ 'render-type="agGrid"': {
1319
+ fix: '<BaseTable render-type="agGrid" ...>',
1320
+ ref: 'standards/12-base-table.md',
1321
+ auto: true,
1322
+ },
1323
+ 'cid / :cid': {
1324
+ fix: '\u7ed9 BaseTable \u52a0 cid="{\u6a21\u5757\u7f29\u5199}-{\u529f\u80fd}"\uff0c\u5168\u5c40\u552f\u4e00',
1325
+ ref: 'standards/12-base-table.md',
1326
+ auto: true,
1327
+ },
1328
+ 'defineColumns()': {
1329
+ fix: 'import { defineColumns } from "@agile-team/wk-skills-ui/runtime" \u5e76\u7528\u4e8e\u5217\u5b9a\u4e49',
1330
+ ref: 'standards/12-base-table.md',
1331
+ auto: true,
1332
+ },
1333
+ 'renderOps()': {
1334
+ fix: '\u64cd\u4f5c\u5217\u4f7f\u7528 defaultSlot + renderOps()\uff0c\u7981\u6b62 operations \u6570\u7ec4',
1335
+ ref: 'standards/12-base-table.md',
1336
+ auto: true,
1337
+ },
1338
+ 'C_Splitter': {
1339
+ fix: '\u66ff\u6362\u4e3a jh-drag-col\uff08\u5de6\u53f3\uff09/ jh-drag-row\uff08\u4e0a\u4e0b\uff09',
1340
+ ref: 'standards/14-layout-containers.md',
1341
+ auto: true,
1342
+ },
1343
+ 'onClick: () => {}': {
1344
+ fix: '\u586b\u5145\u5b9e\u9645\u4e8b\u4ef6\u5904\u7406\u903b\u8f91\uff0c\u6216\u8054\u52a8 code-fix \u81ea\u52a8\u4fee\u590d',
1345
+ ref: 'standards/04-coding-basics.md',
1346
+ auto: true,
1347
+ },
1348
+ };
1349
+
1350
+ const AST_FIX_SUGGESTIONS = {
1351
+ R1: { fix: '\u5c06\u4e1a\u52a1\u903b\u8f91\u8fc1\u79fb\u5230 data.ts\uff0cindex.vue \u53ea\u4fdd\u7559\u6a21\u677f+\u89e3\u6784', ref: 'standards/02-code-structure.md', auto: false },
1352
+ R2: { fix: '\u5c06 getAction/postAction/sessionStorage \u79fb\u5230 data.ts \u4e2d\u8c03\u7528', ref: 'standards/02-code-structure.md', auto: true },
1353
+ R3: { fix: '\u66ff\u6362 <el-table> \u4e3a <BaseTable render-type="agGrid" :cid="xxx">', ref: 'standards/12-base-table.md', auto: true },
1354
+ R4: { fix: '\u4fee\u6539\u91cd\u590d cid \u4e3a\u5168\u5c40\u552f\u4e00\u503c\uff08\u683c\u5f0f: {\u6a21\u5757\u7f29\u5199}-{\u529f\u80fd}\uff09', ref: 'standards/12-base-table.md', auto: true },
1355
+ R5: { fix: 'data.ts \u4e2d class extends AbstractPageQueryHook\uff0c\u5b9e\u73b0 queryDef/columnsDef', ref: 'standards/02-code-structure.md', auto: false },
1356
+ R6: { fix: '\u5220\u9664 import axios\uff0c\u6539\u7528 getAction/postAction', ref: 'standards/06-security.md', auto: true },
1357
+ R7: { fix: '\u5220\u9664 eval/new Function\uff0c\u7528\u5b89\u5168\u7684\u66ff\u4ee3\u65b9\u6848', ref: 'standards/06-security.md', auto: false },
1358
+ R8: { fix: '\u521b\u5efa data.ts\uff0c\u5c06\u63a5\u53e3\u8c03\u7528\u548c\u4e1a\u52a1\u903b\u8f91\u79fb\u5165\uff1b\u786e\u4fdd index.vue \u65e0 API \u8c03\u7528', ref: 'standards/02-code-structure.md', auto: true },
1359
+ R9: { fix: '\u66f4\u65b0 api.md\uff0c\u786e\u4fdd URL \u4e0e data.ts API_CONFIG \u4e00\u81f4', ref: 'standards/02-code-structure.md', auto: true },
1360
+ R10: { fix: '\u66ff\u6362\u539f\u751f el-* \u7ec4\u4ef6\u4e3a\u5e73\u53f0\u5c01\u88c5\uff08jh-select/jh-date/jh-pagination \u7b49\uff09', ref: 'standards/13-platform-components.md', auto: true },
1361
+ R11: { fix: '\u4ece data.ts \u4e2d\u79fb\u9664 Pinia Store import\uff0cStore \u5e94\u5728\u7ec4\u4ef6\u5c42\u4f7f\u7528', ref: 'standards/10-pinia.md', auto: true },
1362
+ R12: { fix: '\u5c06\u786c\u7f16\u7801 IP/URL \u79fb\u81f3 .env.* \u73af\u5883\u53d8\u91cf', ref: 'standards/07-config.md', auto: true },
1363
+ };
1364
+
1365
+ function printFixSuggestions(blockingIssues) {
1366
+ // \u6309\u89c4\u5219\u5206\u7ec4\u53bb\u91cd
1367
+ const ruleGroups = new Map();
1368
+ for (const issue of blockingIssues) {
1369
+ const key = issue.rule || guessRuleFromText(issue.text);
1370
+ if (!key) continue;
1371
+ if (!ruleGroups.has(key)) ruleGroups.set(key, []);
1372
+ ruleGroups.get(key).push(issue);
1373
+ }
1374
+
1375
+ if (ruleGroups.size === 0) return;
1376
+
1377
+ console.log(" \u250c\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\u2510");
1378
+ console.log(" \u2502 \ud83d\udd27 \u4fee\u590d\u5efa\u8bae \u2502");
1379
+ 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");
1380
+
1381
+ let hasAutoFix = false;
1382
+ let hasUnknownIssue = false;
1383
+ for (const [rule, ruleIssues] of ruleGroups.entries()) {
1384
+ const suggestion = AST_FIX_SUGGESTIONS[rule] || findRegexSuggestion(ruleIssues[0].text);
1385
+ const count = ruleIssues.length;
1386
+ if (!suggestion) {
1387
+ // 免底:未知规则的阻断项也要展示,避免用户看不到任何提示
1388
+ hasUnknownIssue = true;
1389
+ console.log(" \u2502 " + rule + "\uff08" + count + " \u5904\uff09 [\u2753\u672a\u77e5\u89c4\u5219]");
1390
+ console.log(" \u2502 \u2192 \u8bf7\u67e5\u770b .github/standards/ \u76f8\u5173\u89c4\u8303\u6216\u89e6\u53d1\u89c4\u8303\u5ba1\u8ba1");
1391
+ console.log(" \u2502");
1392
+ continue;
1393
+ }
1394
+ const autoTag = suggestion.auto ? " [\u2705\u53ef\u81ea\u52a8\u4fee]" : " [\u270b\u9700\u4eba\u5de5]";
1395
+ if (suggestion.auto) hasAutoFix = true;
1396
+ console.log(" \u2502 " + rule + "\uff08" + count + " \u5904\uff09" + autoTag);
1397
+ console.log(" \u2502 \u2192 " + suggestion.fix);
1398
+ console.log(" \u2502 \u53c2\u8003: .github/" + suggestion.ref);
1399
+ console.log(" \u2502");
1400
+ }
1401
+
1402
+ 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");
1403
+ if (hasAutoFix) {
1404
+ console.log(" \u2502 \ud83d\ude80 \u5feb\u901f\u4fee\u590d\uff1a\u5728 AI \u7f16\u8f91\u5668\u4e2d\u8f93\u5165\uff1a \u2502");
1405
+ console.log(" \u2502 \"\u89c4\u8303\u5ba1\u8ba1\" \u2192 \"\u81ea\u52a8\u4fee\u590d\" \u2192 \"\u590d\u626b\u9a8c\u8bc1\" \u2502");
1406
+ } else {
1407
+ console.log(" \u2502 \ud83d\udcdd \u8bf7\u53c2\u7167\u4e0a\u8ff0\u89c4\u8303\u6587\u6863\u624b\u52a8\u4fee\u590d \u2502");
1408
+ }
1409
+ console.log(" \u2514\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\u2518");
1034
1410
  console.log("");
1035
- if (errors > 0 || issues.length > 0) process.exitCode = 1;
1411
+ }
1412
+
1413
+ function guessRuleFromText(text) {
1414
+ for (const key of Object.keys(FIX_SUGGESTIONS)) {
1415
+ if (text.includes(key)) return key;
1416
+ }
1417
+ return null;
1418
+ }
1419
+
1420
+ function findRegexSuggestion(text) {
1421
+ for (const [key, suggestion] of Object.entries(FIX_SUGGESTIONS)) {
1422
+ if (text.includes(key)) return suggestion;
1423
+ }
1424
+ return null;
1036
1425
  }
1037
1426
 
1038
1427
  function readJsonSafe(filePath) {