@agile-team/wl-skills-kit 2.11.1 → 2.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +1 -1
- package/bin/wl-skills.js +27 -3
- package/files/.wl-skills/docs/jh-pagination.md +505 -505
- package/files/.wl-skills/docs/request.md +940 -940
- package/files/.wl-skills/guides/architecture.md +1 -1
- package/files/.wl-skills/skills/core/convention-audit/SKILL.md +3 -3
- package/files/.wl-skills/skills/core/spec-doc-parse/SKILL.md +332 -332
- package/files/.wl-skills/skills/core/spec-doc-parse/USAGE.md +97 -97
- package/files/.wl-skills/skills/sync/permission-sync/USAGE.md +107 -107
- package/files/.wl-skills/src/components/global/C_ParentView/index.vue +3 -3
- package/files/.wl-skills/src/components/global/C_RightToolbar/index.vue +157 -157
- package/files/.wl-skills/src/components/global/C_SvgIcon/index.vue +31 -31
- package/files/.wl-skills/src/components/global/C_SvgIcon/svgicon.js +10 -10
- package/files/.wl-skills/src/components/global/C_TagStatus/README.md +264 -264
- package/files/.wl-skills/src/components/global/C_TagStatus/config.ts +192 -192
- package/files/.wl-skills/src/components/global/C_TagStatus/index.vue +106 -106
- package/files/.wl-skills/src/components/global/C_TagStatus/types.ts +64 -64
- package/files/.wl-skills/src/components/global/C_Tree/README.md +153 -153
- package/files/.wl-skills/src/components/global/C_Tree/index.scss +42 -42
- package/files/.wl-skills/src/components/global/C_Tree/index.vue +78 -78
- package/files/.wl-skills/src/components/global/C_Tree/types.ts +59 -59
- package/files/.wl-skills/src/components/local/c_formModal/README.md +235 -235
- package/files/.wl-skills/src/components/local/c_formModal/data.ts +95 -95
- package/files/.wl-skills/src/components/local/c_formModal/index.scss +8 -8
- package/files/.wl-skills/src/components/local/c_formModal/index.vue +107 -107
- package/files/.wl-skills/src/components/local/c_formSections/data.ts +175 -175
- package/files/.wl-skills/src/components/local/c_formSections/index.scss +280 -280
- package/files/.wl-skills/src/components/local/c_formSections/index.vue +429 -429
- package/files/.wl-skills/src/components/local/c_listModal/data.ts +41 -41
- package/files/.wl-skills/src/components/local/c_listModal/index.vue +136 -136
- package/files/.wl-skills/src/components/local/c_spliterTitle/index.scss +25 -25
- package/files/.wl-skills/src/components/local/c_spliterTitle/index.vue +21 -21
- package/files/.wl-skills/src/components/remote/AGGrid/README.md +530 -530
- package/files/.wl-skills/src/components/remote/BaseForm/README.md +508 -508
- package/files/.wl-skills/src/components/remote/BaseQuery/README.md +865 -865
- package/files/.wl-skills/src/components/remote/BaseTable/README.md +941 -941
- package/files/.wl-skills/src/components/remote/BaseToolbar/README.md +496 -496
- package/files/.wl-skills/src/types/page.ts +24 -24
- package/files/.wl-skills/standards/04-coding-basics.md +39 -1
- package/files/.wl-skills/standards/09-typescript.md +26 -3
- package/files/.wl-skills/standards/index.md +2 -2
- package/files/.wl-skills/templates/README.md +44 -44
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/api.md +54 -54
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/data.ts +346 -346
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/index.scss +1 -1
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add/index.vue +28 -28
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/data.ts +115 -115
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/index.scss +44 -44
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-add-form/index.vue +43 -43
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/data.ts +338 -338
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/index.scss +1 -1
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change/index.vue +28 -28
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/data.ts +115 -115
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/index.scss +44 -44
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-apply-change-form/index.vue +43 -43
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/api.md +88 -88
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/data.ts +601 -601
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/index.scss +1 -1
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-archive/index.vue +64 -64
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/api.md +67 -67
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/data.ts +286 -286
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/index.scss +139 -139
- package/files/.wl-skills/templates/produce/aiflow/mmwr-customer-detail/index.vue +318 -318
- package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/api.md +98 -98
- package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/data.ts +543 -543
- package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/index.scss +1 -1
- package/files/.wl-skills/templates/produce/aiflow/mmwr-temp-customer-archive/index.vue +52 -52
- package/files/.wl-skills/templates/sale/demo/add-demo/data.ts +518 -518
- package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/data.ts +524 -524
- package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/index.scss +154 -154
- package/files/.wl-skills/templates/sale/demo/billet-flame-cut-plan/index.vue +117 -117
- package/files/.wl-skills/templates/sale/demo/domestic-trade-order/data.ts +308 -308
- package/files/.wl-skills/templates/sale/demo/domestic-trade-order/index.scss +99 -99
- package/files/.wl-skills/templates/sale/demo/domestic-trade-order/index.vue +77 -77
- package/files/.wl-skills/templates/sale/demo/heat-batch-return/data.ts +367 -367
- package/files/.wl-skills/templates/sale/demo/heat-batch-return/index.scss +100 -100
- package/files/.wl-skills/templates/sale/demo/heat-batch-return/index.vue +170 -170
- package/files/.wl-skills/templates/sale/demo/heat-batch-return/meltDialog.vue +320 -320
- package/files/.wl-skills/templates/sale/demo/metallurgical-spec/data.ts +824 -824
- package/lib/ast-rules.js +304 -9
- package/mcp/config.js +46 -46
- package/mcp/registry.js +6 -1
- package/mcp/tools/projectTools.js +9 -1
- package/package.json +2 -2
package/lib/ast-rules.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* 依赖:@vue/compiler-sfc(解析 .vue)、@babel/parser(解析 <script> AST)
|
|
8
8
|
* 这两个包在 Vue 3 + Vite 项目中天然存在,用 try-require 做优雅降级。
|
|
9
9
|
*
|
|
10
|
-
* 规则编号 R1~
|
|
10
|
+
* 规则编号 R1~R14 对应 standards 02/04/06/07/09/10/12/13 中的语义约束:
|
|
11
11
|
* R1: index.vue <script setup> 业务逻辑行数超阈值 → warn(02)
|
|
12
12
|
* R2: index.vue 含禁止 import(getAction/postAction/sessionStorage 等)→ error(02/06)
|
|
13
13
|
* R3: 页面用 <el-table> 但未用 <BaseTable> → error(12/13)
|
|
@@ -20,17 +20,23 @@
|
|
|
20
20
|
* R10: 平台组件替换检测 — el-form/el-select/el-date-picker 等应替换为平台封装 → error(13)
|
|
21
21
|
* R11: data.ts 禁止 import Pinia Store → error(10)
|
|
22
22
|
* R12: 硬编码 IP/URL 检测 → error/warn(07)
|
|
23
|
+
* R13: 单函数圈复杂度 > 10(Mcabe,与 ESLint complexity 定义一致)→ error(04)
|
|
24
|
+
* R14: 文件类型错误零容忍 — vue-tsc/tsc --noEmit 产物解析 → error(09)
|
|
25
|
+
* 注:R14 为项目级检查,体积较大,validate 默认不跑,
|
|
26
|
+
* 需显式 --typecheck(CLI)/ typecheck:true(MCP)触发,优雅降级为 warn
|
|
23
27
|
*
|
|
24
28
|
* 导出函数:
|
|
25
29
|
* runAstRules(targetDir, scanRel, { stagedFiles }) → { issues, pages }
|
|
26
30
|
* parseVueScript(absPath) → { content, template, source } | null
|
|
27
31
|
* countEffectiveLines(scriptContent) → number
|
|
32
|
+
* computeFunctionComplexity(fnNode) → number
|
|
33
|
+
* runTypeCheck(root) → { issues, ran, errorCount }
|
|
28
34
|
* hasAstAvailable() → boolean
|
|
29
35
|
*/
|
|
30
36
|
|
|
31
37
|
const fs = require("fs");
|
|
32
38
|
const path = require("path");
|
|
33
|
-
const { execFileSync } = require("child_process");
|
|
39
|
+
const { execFileSync, spawnSync } = require("child_process");
|
|
34
40
|
|
|
35
41
|
// ─── AST 依赖探测(优雅降级)──────────────────────────────────────────
|
|
36
42
|
//
|
|
@@ -159,6 +165,10 @@ const CONFIG = {
|
|
|
159
165
|
],
|
|
160
166
|
FORBIDDEN_GLOBALS: ["sessionStorage", "localStorage"],
|
|
161
167
|
WARN_IMPORTS: ["useRoute"],
|
|
168
|
+
// R13: 单函数圈复杂度上限(Mcabe),与 ESLint complexity 规则阈值一致
|
|
169
|
+
MAX_CYCLOMATIC_COMPLEXITY: 10,
|
|
170
|
+
// R14: 单页类型错误采集上限(避免输出爆炸)
|
|
171
|
+
TYPECHECK_ERROR_CAP: 50,
|
|
162
172
|
SKIP_DIRS: ["node_modules", "dist", ".git", "demo", "template"],
|
|
163
173
|
};
|
|
164
174
|
|
|
@@ -238,22 +248,30 @@ function countEffectiveLines(scriptContent) {
|
|
|
238
248
|
}
|
|
239
249
|
|
|
240
250
|
/**
|
|
241
|
-
* 用 babel/parser 解析 script
|
|
251
|
+
* 用 babel/parser 解析 script 为 AST(复用给 extractScriptInfo / 圈复杂度计算)
|
|
242
252
|
*/
|
|
243
|
-
function
|
|
244
|
-
if (!ensureAst() || !scriptContent)
|
|
245
|
-
return { specifiers: [], sources: [] };
|
|
246
|
-
}
|
|
247
|
-
let ast;
|
|
253
|
+
function parseScriptAst(scriptContent) {
|
|
254
|
+
if (!ensureAst() || !scriptContent) return null;
|
|
248
255
|
try {
|
|
249
|
-
|
|
256
|
+
return _babelParser.parse(scriptContent, {
|
|
250
257
|
sourceType: "module",
|
|
251
258
|
plugins: ["typescript", "jsx"],
|
|
252
259
|
errorRecovery: true,
|
|
253
260
|
});
|
|
254
261
|
} catch {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 用 babel/parser 解析 script,提取 import 标识符和来源
|
|
268
|
+
*/
|
|
269
|
+
function extractScriptInfo(scriptContent) {
|
|
270
|
+
if (!ensureAst() || !scriptContent) {
|
|
255
271
|
return { specifiers: [], sources: [] };
|
|
256
272
|
}
|
|
273
|
+
const ast = parseScriptAst(scriptContent);
|
|
274
|
+
if (!ast) return { specifiers: [], sources: [] };
|
|
257
275
|
const specifiers = [];
|
|
258
276
|
const sources = [];
|
|
259
277
|
for (const node of ast.program.body) {
|
|
@@ -272,6 +290,114 @@ function extractScriptInfo(scriptContent) {
|
|
|
272
290
|
return { specifiers, sources };
|
|
273
291
|
}
|
|
274
292
|
|
|
293
|
+
// ─── R13 圈复杂度(Mcabe)──────────────────────────────────────────────
|
|
294
|
+
//
|
|
295
|
+
// 定义与 ESLint `complexity` 规则一致:复杂度 = 1 + 决策点数。
|
|
296
|
+
// 不依赖 @babel/traverse,自写轻量遍历,避免新增运行时依赖。
|
|
297
|
+
|
|
298
|
+
const COMPLEXITY_NODE_TYPES = new Set([
|
|
299
|
+
"IfStatement", // if / else if(每个 IfStatement 计 1)
|
|
300
|
+
"SwitchCase", // 每个 case / default
|
|
301
|
+
"ForStatement",
|
|
302
|
+
"ForInStatement",
|
|
303
|
+
"ForOfStatement",
|
|
304
|
+
"WhileStatement",
|
|
305
|
+
"DoWhileStatement",
|
|
306
|
+
"CatchClause",
|
|
307
|
+
"ConditionalExpression", // 三元 ?:
|
|
308
|
+
"LogicalExpression", // && / || / ??
|
|
309
|
+
]);
|
|
310
|
+
|
|
311
|
+
const FUNCTION_NODE_TYPES = new Set([
|
|
312
|
+
"FunctionDeclaration",
|
|
313
|
+
"FunctionExpression",
|
|
314
|
+
"ArrowFunctionExpression",
|
|
315
|
+
]);
|
|
316
|
+
|
|
317
|
+
function isFunctionNode(node) {
|
|
318
|
+
return Boolean(node) && FUNCTION_NODE_TYPES.has(node.type);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// 取节点所有子 AST 节点(跳过位置/范围元信息)
|
|
322
|
+
function getChildNodes(node) {
|
|
323
|
+
const out = [];
|
|
324
|
+
for (const key of Object.keys(node)) {
|
|
325
|
+
if (key === "loc" || key === "range" || key === "start" || key === "end") {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const child = node[key];
|
|
329
|
+
if (Array.isArray(child)) {
|
|
330
|
+
for (const c of child) {
|
|
331
|
+
if (c && typeof c.type === "string") out.push(c);
|
|
332
|
+
}
|
|
333
|
+
} else if (child && typeof child.type === "string") {
|
|
334
|
+
out.push(child);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return out;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 尽力推断函数名(变量赋值 / 类方法 / 对象方法 / 命名函数表达式)
|
|
341
|
+
function resolveFnName(node, parent) {
|
|
342
|
+
if (node.id && node.id.name) return node.id.name;
|
|
343
|
+
if (!parent) return "(anonymous)";
|
|
344
|
+
if (parent.type === "VariableDeclarator" && parent.id && parent.id.name) {
|
|
345
|
+
return parent.id.name;
|
|
346
|
+
}
|
|
347
|
+
if (
|
|
348
|
+
(parent.type === "MethodDefinition" || parent.type === "Property") &&
|
|
349
|
+
parent.key
|
|
350
|
+
) {
|
|
351
|
+
return parent.key.name || parent.key.value || "(anonymous)";
|
|
352
|
+
}
|
|
353
|
+
if (parent.type === "AssignmentExpression" && parent.left) {
|
|
354
|
+
const left = parent.left;
|
|
355
|
+
if (left.property) return left.property.name || left.property.value;
|
|
356
|
+
if (left.name) return left.name;
|
|
357
|
+
}
|
|
358
|
+
return "(anonymous)";
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// 收集整棵 AST 中的所有函数节点 + 名字(含嵌套函数,各独立计 R13)
|
|
362
|
+
function collectFunctions(ast) {
|
|
363
|
+
const fns = [];
|
|
364
|
+
const stack = [];
|
|
365
|
+
function walk(node) {
|
|
366
|
+
if (!node || typeof node.type !== "string") return;
|
|
367
|
+
let pushed = false;
|
|
368
|
+
if (isFunctionNode(node)) {
|
|
369
|
+
const parent = stack.length ? stack[stack.length - 1] : null;
|
|
370
|
+
fns.push({ node, name: resolveFnName(node, parent) });
|
|
371
|
+
// 不再下钻到该函数体内部去发现"孙函数"——
|
|
372
|
+
// 嵌套函数会在遍历其父函数体时自然被收集
|
|
373
|
+
}
|
|
374
|
+
stack.push(node);
|
|
375
|
+
pushed = true;
|
|
376
|
+
for (const child of getChildNodes(node)) walk(child);
|
|
377
|
+
if (pushed) stack.pop();
|
|
378
|
+
}
|
|
379
|
+
walk(ast);
|
|
380
|
+
return fns;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* 计算单个函数的圈复杂度(不下钻进嵌套函数体,嵌套函数独立计)
|
|
385
|
+
* @param {object} fnNode FunctionDeclaration / FunctionExpression / ArrowFunctionExpression
|
|
386
|
+
* @returns {number}
|
|
387
|
+
*/
|
|
388
|
+
function computeFunctionComplexity(fnNode) {
|
|
389
|
+
if (!fnNode) return 0;
|
|
390
|
+
let complexity = 1;
|
|
391
|
+
function walk(node) {
|
|
392
|
+
if (!node || typeof node.type !== "string") return;
|
|
393
|
+
if (node !== fnNode && isFunctionNode(node)) return; // 嵌套函数边界
|
|
394
|
+
if (COMPLEXITY_NODE_TYPES.has(node.type)) complexity++;
|
|
395
|
+
for (const child of getChildNodes(node)) walk(child);
|
|
396
|
+
}
|
|
397
|
+
walk(fnNode);
|
|
398
|
+
return complexity;
|
|
399
|
+
}
|
|
400
|
+
|
|
275
401
|
/**
|
|
276
402
|
* 检测模板中是否有 el-table(非 BaseTable)
|
|
277
403
|
*/
|
|
@@ -419,6 +545,34 @@ function runAstRules(targetDir, scanRel, options) {
|
|
|
419
545
|
});
|
|
420
546
|
}
|
|
421
547
|
|
|
548
|
+
// R13: 单函数圈复杂度 ≤ MAX_CYCLOMATIC_COMPLEXITY(standard 04,Mcabe)
|
|
549
|
+
// 覆盖 index.vue <script> 与 data.ts 的所有函数/方法/箭头函数
|
|
550
|
+
if (!hasIgnoreMarker(fullSource, "R13")) {
|
|
551
|
+
const maxC = CONFIG.MAX_CYCLOMATIC_COMPLEXITY;
|
|
552
|
+
const scanComplexity = (code, label) => {
|
|
553
|
+
const ast = parseScriptAst(code);
|
|
554
|
+
if (!ast) return;
|
|
555
|
+
for (const fn of collectFunctions(ast)) {
|
|
556
|
+
const cc = computeFunctionComplexity(fn.node);
|
|
557
|
+
if (cc > maxC) {
|
|
558
|
+
issues.push({
|
|
559
|
+
level: "error",
|
|
560
|
+
dir: page.dir,
|
|
561
|
+
text:
|
|
562
|
+
label + " 函数 " + fn.name +
|
|
563
|
+
"() 圈复杂度 " + cc + "(阈值 " + maxC +
|
|
564
|
+
"),需拆分为更小函数(standard 04)",
|
|
565
|
+
rule: "R13",
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
scanComplexity(scriptContent, "index.vue");
|
|
571
|
+
if (fs.existsSync(dataPath)) {
|
|
572
|
+
scanComplexity(fs.readFileSync(dataPath, "utf8"), "data.ts");
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
422
576
|
// R2: 禁止的 import / 全局 API
|
|
423
577
|
if (scriptContent) {
|
|
424
578
|
const { specifiers, sources } = extractScriptInfo(scriptContent);
|
|
@@ -754,11 +908,152 @@ function getStagedFiles(targetDir) {
|
|
|
754
908
|
}
|
|
755
909
|
}
|
|
756
910
|
|
|
911
|
+
// ─── R14 类型错误零容忍(项目级,vue-tsc / tsc 委托)──────────────────
|
|
912
|
+
//
|
|
913
|
+
// 体积较大,validate 默认不触发,由 CLI --typecheck / MCP typecheck:true 显式开启。
|
|
914
|
+
// 无 tsconfig / 无 checker → 优雅降级为 warn(与 AST 依赖降级策略一致)。
|
|
915
|
+
// 该函数不进入 page 粒度,整项目执行一次,结果按文件归并到 issues。
|
|
916
|
+
|
|
917
|
+
function runTypeCheck(root) {
|
|
918
|
+
const safeRoot = root || process.cwd();
|
|
919
|
+
const label = path.basename(safeRoot) || ".";
|
|
920
|
+
const tsconfigPath = path.join(safeRoot, "tsconfig.json");
|
|
921
|
+
|
|
922
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
923
|
+
return {
|
|
924
|
+
issues: [
|
|
925
|
+
{
|
|
926
|
+
level: "warn",
|
|
927
|
+
dir: label,
|
|
928
|
+
text: "未发现 tsconfig.json,跳过类型检查 R14",
|
|
929
|
+
rule: "R14",
|
|
930
|
+
},
|
|
931
|
+
],
|
|
932
|
+
ran: false,
|
|
933
|
+
errorCount: 0,
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
const nmBin = path.join(safeRoot, "node_modules", ".bin");
|
|
938
|
+
const envPath =
|
|
939
|
+
nmBin + path.delimiter + (process.env.PATH || process.env.Path || "");
|
|
940
|
+
const env = Object.assign({}, process.env, { PATH: envPath });
|
|
941
|
+
|
|
942
|
+
// 优先 vue-tsc(.vue 项目),回退 tsc;shell:true 让 Windows 找到 .cmd
|
|
943
|
+
let checker = null;
|
|
944
|
+
for (const bin of ["vue-tsc", "tsc"]) {
|
|
945
|
+
try {
|
|
946
|
+
const probe = spawnSync(bin, ["--version"], {
|
|
947
|
+
shell: true,
|
|
948
|
+
env,
|
|
949
|
+
encoding: "utf8",
|
|
950
|
+
timeout: 20000,
|
|
951
|
+
});
|
|
952
|
+
if (probe.status === 0) {
|
|
953
|
+
checker = bin;
|
|
954
|
+
break;
|
|
955
|
+
}
|
|
956
|
+
} catch {
|
|
957
|
+
// ignore, try next
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
if (!checker) {
|
|
961
|
+
return {
|
|
962
|
+
issues: [
|
|
963
|
+
{
|
|
964
|
+
level: "warn",
|
|
965
|
+
dir: label,
|
|
966
|
+
text: "未发现 vue-tsc / tsc,跳过类型检查 R14(建议安装后纳入 CI)",
|
|
967
|
+
rule: "R14",
|
|
968
|
+
},
|
|
969
|
+
],
|
|
970
|
+
ran: false,
|
|
971
|
+
errorCount: 0,
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
let result;
|
|
976
|
+
try {
|
|
977
|
+
result = spawnSync(checker, ["--noEmit"], {
|
|
978
|
+
cwd: safeRoot,
|
|
979
|
+
shell: true,
|
|
980
|
+
env,
|
|
981
|
+
encoding: "utf8",
|
|
982
|
+
timeout: 180000,
|
|
983
|
+
});
|
|
984
|
+
} catch (e) {
|
|
985
|
+
return {
|
|
986
|
+
issues: [
|
|
987
|
+
{
|
|
988
|
+
level: "warn",
|
|
989
|
+
dir: label,
|
|
990
|
+
text:
|
|
991
|
+
"类型检查执行异常:" + ((e && e.message) || String(e)),
|
|
992
|
+
rule: "R14",
|
|
993
|
+
},
|
|
994
|
+
],
|
|
995
|
+
ran: false,
|
|
996
|
+
errorCount: 0,
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
const out =
|
|
1001
|
+
String(result.stdout || "") + String(result.stderr || "");
|
|
1002
|
+
// 标准格式:path(line,col): error TS1234: message
|
|
1003
|
+
// 捕获组:1=file 2=line 3=col 4=code 5=msg
|
|
1004
|
+
const errRe = /^(.+?)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s*(.+)$/gm;
|
|
1005
|
+
const errors = [];
|
|
1006
|
+
let m;
|
|
1007
|
+
while ((m = errRe.exec(out)) !== null) {
|
|
1008
|
+
errors.push({ file: m[1], line: m[2], code: m[4], msg: m[5] });
|
|
1009
|
+
if (errors.length >= CONFIG.TYPECHECK_ERROR_CAP) break;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (errors.length === 0) {
|
|
1013
|
+
if (result.status === 0) {
|
|
1014
|
+
return { issues: [], ran: true, errorCount: 0 };
|
|
1015
|
+
}
|
|
1016
|
+
// 退出码非 0 但未解析出标准 error 行:疑似 tsconfig / 配置级错误
|
|
1017
|
+
return {
|
|
1018
|
+
issues: [
|
|
1019
|
+
{
|
|
1020
|
+
level: "error",
|
|
1021
|
+
dir: label,
|
|
1022
|
+
text:
|
|
1023
|
+
checker +
|
|
1024
|
+
" --noEmit 退出码 " +
|
|
1025
|
+
result.status +
|
|
1026
|
+
"(请检查 tsconfig / 类型配置,无标准 TS 错误输出)",
|
|
1027
|
+
rule: "R14",
|
|
1028
|
+
},
|
|
1029
|
+
],
|
|
1030
|
+
ran: true,
|
|
1031
|
+
errorCount: 1,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
const issues = errors.map((e) => {
|
|
1036
|
+
const rel = path.relative(safeRoot, e.file).replace(/\\/g, "/") || e.file;
|
|
1037
|
+
return {
|
|
1038
|
+
level: "error",
|
|
1039
|
+
dir: path.dirname(rel) || ".",
|
|
1040
|
+
text:
|
|
1041
|
+
e.code + " " + e.msg + " (" + path.basename(rel) + ":" + e.line + ")",
|
|
1042
|
+
rule: "R14",
|
|
1043
|
+
};
|
|
1044
|
+
});
|
|
1045
|
+
return { issues, ran: true, errorCount: errors.length };
|
|
1046
|
+
}
|
|
1047
|
+
|
|
757
1048
|
module.exports = {
|
|
758
1049
|
runAstRules,
|
|
759
1050
|
parseVueScript,
|
|
760
1051
|
countEffectiveLines,
|
|
761
1052
|
extractScriptInfo,
|
|
1053
|
+
parseScriptAst,
|
|
1054
|
+
computeFunctionComplexity,
|
|
1055
|
+
collectFunctions,
|
|
1056
|
+
runTypeCheck,
|
|
762
1057
|
hasAstAvailable,
|
|
763
1058
|
isAstFunctionallyUsable,
|
|
764
1059
|
getStagedFiles,
|
package/mcp/config.js
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const path = require('path')
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 从项目的 .wl-skills/skills/sync/env.local.json 加载 MCP 运行配置
|
|
8
|
-
* 项目根目录通过环境变量 WL_PROJECT_ROOT 传入(由 .cursor/mcp.json 注入)
|
|
9
|
-
*/
|
|
10
|
-
function loadConfig() {
|
|
11
|
-
const projectRoot = process.env.WL_PROJECT_ROOT
|
|
12
|
-
? path.resolve(process.env.WL_PROJECT_ROOT)
|
|
13
|
-
: process.cwd()
|
|
14
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 从项目的 .wl-skills/skills/sync/env.local.json 加载 MCP 运行配置
|
|
8
|
+
* 项目根目录通过环境变量 WL_PROJECT_ROOT 传入(由 .cursor/mcp.json 注入)
|
|
9
|
+
*/
|
|
10
|
+
function loadConfig() {
|
|
11
|
+
const projectRoot = process.env.WL_PROJECT_ROOT
|
|
12
|
+
? path.resolve(process.env.WL_PROJECT_ROOT)
|
|
13
|
+
: process.cwd()
|
|
14
|
+
|
|
15
15
|
const configPath = path.join(projectRoot, '.wl-skills', 'skills', 'sync', 'env.local.json')
|
|
16
|
-
|
|
17
|
-
if (!fs.existsSync(configPath)) {
|
|
18
|
-
throw new Error(
|
|
19
|
-
`配置文件不存在: ${configPath}\n` +
|
|
20
|
-
`请先执行 pnpm dlx @agile-team/wl-skills-kit init,然后填写 .wl-skills/skills/sync/env.local.json`
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let raw
|
|
25
|
-
try {
|
|
26
|
-
raw = JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
27
|
-
} catch (e) {
|
|
28
|
-
throw new Error(`配置文件解析失败: ${e.message}`)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!raw.gatewayPath || raw.gatewayPath.includes('你的网关')) {
|
|
32
|
-
throw new Error('请在 env.local.json 中填写真实的 gatewayPath(当前为占位值)')
|
|
33
|
-
}
|
|
34
|
-
if (!raw.token || raw.token.includes('Bearer Token')) {
|
|
35
|
-
throw new Error('请在 env.local.json 中填写真实的 token(当前为占位值)')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
gatewayPath: raw.gatewayPath.replace(/\/$/, ''), // 去掉尾部斜杠
|
|
40
|
-
token: raw.token,
|
|
41
|
-
sysAppNo: raw.sysAppNo || '',
|
|
42
|
-
menu: raw.menu || {},
|
|
43
|
-
dict: raw.dict || {},
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
module.exports = { loadConfig }
|
|
16
|
+
|
|
17
|
+
if (!fs.existsSync(configPath)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`配置文件不存在: ${configPath}\n` +
|
|
20
|
+
`请先执行 pnpm dlx @agile-team/wl-skills-kit init,然后填写 .wl-skills/skills/sync/env.local.json`
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let raw
|
|
25
|
+
try {
|
|
26
|
+
raw = JSON.parse(fs.readFileSync(configPath, 'utf8'))
|
|
27
|
+
} catch (e) {
|
|
28
|
+
throw new Error(`配置文件解析失败: ${e.message}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!raw.gatewayPath || raw.gatewayPath.includes('你的网关')) {
|
|
32
|
+
throw new Error('请在 env.local.json 中填写真实的 gatewayPath(当前为占位值)')
|
|
33
|
+
}
|
|
34
|
+
if (!raw.token || raw.token.includes('Bearer Token')) {
|
|
35
|
+
throw new Error('请在 env.local.json 中填写真实的 token(当前为占位值)')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
gatewayPath: raw.gatewayPath.replace(/\/$/, ''), // 去掉尾部斜杠
|
|
40
|
+
token: raw.token,
|
|
41
|
+
sysAppNo: raw.sysAppNo || '',
|
|
42
|
+
menu: raw.menu || {},
|
|
43
|
+
dict: raw.dict || {},
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { loadConfig }
|
package/mcp/registry.js
CHANGED
|
@@ -296,11 +296,16 @@ const DESCRIPTORS = [
|
|
|
296
296
|
{
|
|
297
297
|
name: "wls_validate_page",
|
|
298
298
|
description:
|
|
299
|
-
"校验页面是否符合 wl-skills-kit 最新页面规范:BaseTable+AGGrid+cid、defineColumns、renderOps、mock-first、api.md
|
|
299
|
+
"校验页面是否符合 wl-skills-kit 最新页面规范:BaseTable+AGGrid+cid、defineColumns、renderOps、mock-first、api.md 等。开启 typecheck 额外执行 vue-tsc/tsc 类型检查(R14)。",
|
|
300
300
|
inputSchema: {
|
|
301
301
|
type: "object",
|
|
302
302
|
properties: {
|
|
303
303
|
path: { type: "string", description: "页面或目录路径,默认 src/views" },
|
|
304
|
+
typecheck: {
|
|
305
|
+
type: "boolean",
|
|
306
|
+
description:
|
|
307
|
+
"是否额外执行 vue-tsc/tsc --noEmit 类型检查(R14,体积较大,CI 场景开启)",
|
|
308
|
+
},
|
|
304
309
|
},
|
|
305
310
|
required: [],
|
|
306
311
|
},
|
|
@@ -4,7 +4,7 @@ const fs = require("fs");
|
|
|
4
4
|
const path = require("path");
|
|
5
5
|
const { execFileSync } = require("child_process");
|
|
6
6
|
const https = require("https");
|
|
7
|
-
const { runAstRules } = require("../../lib/ast-rules");
|
|
7
|
+
const { runAstRules, runTypeCheck } = require("../../lib/ast-rules");
|
|
8
8
|
const { alignPage } = require("../../lib/page-spec");
|
|
9
9
|
|
|
10
10
|
function getProjectRoot() {
|
|
@@ -213,6 +213,14 @@ async function handleValidatePage(args) {
|
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// ── 类型检查 R14(v2.11.2+,仅当 typecheck:true 触发)─────────────────
|
|
217
|
+
if (args && args.typecheck) {
|
|
218
|
+
const tc = runTypeCheck(root);
|
|
219
|
+
for (const iss of tc.issues) {
|
|
220
|
+
issues.push([iss.dir, iss.level, `[${iss.rule}] ${iss.text}`]);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
216
224
|
const errors = issues.filter((item) => item[1] === "error").length;
|
|
217
225
|
const lines = [
|
|
218
226
|
`✅ 页面校验完成:${scanPath}`,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agile-team/wl-skills-kit",
|
|
3
|
-
"version": "2.11.
|
|
4
|
-
"description": "AI Skill 模板包 v2.11.
|
|
3
|
+
"version": "2.11.2",
|
|
4
|
+
"description": "AI Skill 模板包 v2.11.2 — 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": {
|