@agile-team/wl-skills-kit 2.6.0 → 2.7.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.
package/mcp/server.js CHANGED
@@ -2,337 +2,23 @@
2
2
  "use strict";
3
3
 
4
4
  /**
5
- * wl-skills MCP Server
5
+ * wl-skills MCP Server (v2.7.0+)
6
6
  *
7
7
  * 实现 MCP 协议(stdio transport,JSON-RPC 2.0)
8
- * 暴露工具:
9
- * wls_menu_query 查询菜单树
10
- * wls_menu_upsert 批量新增/更新菜单
11
- * wls_menu_sync_from_report 从 SYS_MENU_INFO*.md 确定性同步菜单
12
- * wls_dict_query 查询字典模块
13
- * wls_dict_upsert 新增/更新字典模块及字典项
14
- * wls_role_query 查询角色列表
15
- * wls_role_upsert 批量新增角色(按 code 去重)
16
- * wls_assignable_menus_query 查询全量可授权菜单(用于角色授权)
17
- * wls_role_assign_menus 给角色批量分配菜单权限
18
- * wls_action_query 查询页面菜单下的动作(type=A)
19
- * wls_action_upsert 批量新增动作(按 permission 去重)
20
- * wls_code_scan 扫描页面目录与文件完整性
21
- * wls_route_check 检查页面目录在路由文件中的可发现性
22
- * wls_git_log_extract 提取最近提交摘要
23
- * wls_audit_report_push 推送最新审计报告(可选飞书 webhook)
24
8
  *
25
- * 启动方式(由 .cursor/mcp.json 自动注入):
9
+ * 工具描述符集中维护在 mcp/registry.js(auto-discovery)。
10
+ * server.js 自身只做协议层 + 自动调度,新增/修改 Tool 仅改 registry.js。
11
+ *
12
+ * 启动方式(由 .cursor/mcp.json 等编辑器配置自动注入):
26
13
  * node node_modules/@agile-team/wl-skills-kit/mcp/server.js
27
14
  */
28
15
 
29
16
  const readline = require("readline");
30
17
  const { loadConfig } = require("./config");
31
- const {
32
- handleMenuQuery,
33
- handleMenuUpsert,
34
- handleMenuSyncFromReport,
35
- } = require("./tools/menuSync");
36
- const { handleDictQuery, handleDictUpsert } = require("./tools/dictSync");
37
- const {
38
- handleRoleQuery,
39
- handleRoleUpsert,
40
- handleRoleAssignMenus,
41
- handleAssignableMenusQuery,
42
- handleActionQuery,
43
- handleActionUpsert,
44
- } = require("./tools/permissionSync");
45
- const {
46
- handleCodeScan,
47
- handleValidatePage,
48
- handleDoctorUi,
49
- handleRouteCheck,
50
- handleGitLogExtract,
51
- handleAuditReportPush,
52
- } = require("./tools/projectTools");
18
+ const { TOOLS, HANDLERS } = require("./registry");
53
19
 
54
20
  const PKG = require("../package.json");
55
21
 
56
- // ─── Tool 注册表 ────────────────────────────────────────────────────────
57
-
58
- const TOOLS = [
59
- {
60
- name: "wls_menu_query",
61
- description:
62
- "查询当前应用的完整菜单树。自动从 .github/skills/sync/env.local.json 读取 domainId," +
63
- "无需传参。在 wls_menu_upsert 前调用,用于判断哪些菜单需要新增、哪些需要更新。",
64
- inputSchema: {
65
- type: "object",
66
- properties: {},
67
- required: [],
68
- },
69
- },
70
- {
71
- name: "wls_menu_upsert",
72
- description:
73
- "批量新增或更新菜单项。有 id 字段 → 更新;无 id 字段 → 新增。" +
74
- "新增时响应自动包含服务端生成的 id,可链式用于创建子菜单。",
75
- inputSchema: {
76
- type: "object",
77
- properties: {
78
- items: {
79
- type: "array",
80
- description:
81
- "MenuSaveBody 数组。每项字段:" +
82
- "id(更新时传), sysAppNo, menuName, menuNameCode, parentId, " +
83
- 'type("M"=目录/"C"=菜单), path, icon, orderNum, ' +
84
- "useCache(1), common(2), hidden(false), editMode(false), " +
85
- "component(type=C时传), permission(type=C时传)",
86
- items: { type: "object" },
87
- },
88
- },
89
- required: ["items"],
90
- },
91
- },
92
- {
93
- name: "wls_menu_sync_from_report",
94
- description:
95
- "读取 .github/reports/SYS_MENU_INFO*.md,按一级目录(type=M)优先、二级菜单(type=C)随后同步到后端菜单。" +
96
- "自动查询 domain 菜单树去重,复用或更新已存在菜单,避免把二级页面全部挂到根 parentMenuId。",
97
- inputSchema: {
98
- type: "object",
99
- properties: {
100
- reportPath: {
101
- type: "string",
102
- description:
103
- "可选。SYS_MENU_INFO*.md 路径;不传则使用 .github/reports 下最新报告。",
104
- },
105
- dryRun: {
106
- type: "boolean",
107
- description: "可选。true 时只解析和预览,不调用保存接口。",
108
- },
109
- },
110
- required: [],
111
- },
112
- },
113
- {
114
- name: "wls_dict_query",
115
- description:
116
- "查询当前应用的所有字典模块及字典项。在 wls_dict_upsert 前调用," +
117
- "用于判断哪些模块/字典项已存在。",
118
- inputSchema: {
119
- type: "object",
120
- properties: {},
121
- required: [],
122
- },
123
- },
124
- {
125
- name: "wls_dict_upsert",
126
- description:
127
- "新增或更新字典模块及其字典项。内部自动处理:" +
128
- "若模块不存在则创建(data=null 后自动 re-query 获取 id)," +
129
- "若已存在则直接取 id;字典项自动跳过已存在的 strSn。",
130
- inputSchema: {
131
- type: "object",
132
- properties: {
133
- module: {
134
- type: "object",
135
- description:
136
- 'DictModuleSaveBody: strSn(必填), strName(必填), sortPriority("1"), strLevel(2)',
137
- properties: {
138
- strSn: { type: "string", description: '模块标识符,如 "gender"' },
139
- strName: { type: "string", description: '模块显示名,如 "性别"' },
140
- sortPriority: {
141
- type: "string",
142
- description: '排序,字符串类型,如 "1"',
143
- },
144
- strLevel: { type: "number", description: "固定传 2" },
145
- },
146
- required: ["strSn", "strName"],
147
- },
148
- items: {
149
- type: "array",
150
- description:
151
- "DictItemSaveBody 数组(可选)。每项字段:" +
152
- "strSn(必填), strName(必填), strLevel(2), " +
153
- 'dtlValue(""), dtlValueRequired(false), dtlValue2Required(false), ' +
154
- "dtlValue3Required(false), dtlValue4Required(false)",
155
- items: { type: "object" },
156
- },
157
- },
158
- required: ["module"],
159
- },
160
- },
161
- {
162
- name: "wls_role_query",
163
- description:
164
- "查询角色列表。可选参数 current/size 翻页,默认 size=100。返回精简字段:id, roleName, code, sysAppNo, roleDesc。",
165
- inputSchema: {
166
- type: "object",
167
- properties: {
168
- current: { type: "number", description: "页码,默认 1" },
169
- size: { type: "number", description: "每页数量,默认 100" },
170
- },
171
- required: [],
172
- },
173
- },
174
- {
175
- name: "wls_role_upsert",
176
- description:
177
- "批量新增角色(按 code 字段自动去重;已存在则跳过)。每项必填 roleName 和 code,可选 configDesc。" +
178
- "注意:角色仅新增不更新,因角色变更通常需要业务确认。",
179
- inputSchema: {
180
- type: "object",
181
- properties: {
182
- items: {
183
- type: "array",
184
- description:
185
- "角色数组。字段:roleName(必填,显示名), code(必填,唯一标识), configDesc(可选,描述)",
186
- items: { type: "object" },
187
- },
188
- },
189
- required: ["items"],
190
- },
191
- },
192
- {
193
- name: "wls_assignable_menus_query",
194
- description:
195
- "查询全量可授权菜单列表(扁平结构,含菜单 id/menuName/permission)。" +
196
- "在 wls_role_assign_menus 前调用,AI 据此选出要分配给角色的 menuIds。",
197
- inputSchema: { type: "object", properties: {}, required: [] },
198
- },
199
- {
200
- name: "wls_role_assign_menus",
201
- description:
202
- "给指定角色批量分配菜单权限。menuIds 传字符串数组,内部自动拼成逗号分隔字符串提交后端。" +
203
- "该接口为全量覆盖式,应包含该角色所有菜单(含已有的,否则会被移除)。",
204
- inputSchema: {
205
- type: "object",
206
- properties: {
207
- roleId: {
208
- type: "string",
209
- description: "角色 id(来自 wls_role_query)",
210
- },
211
- menuIds: {
212
- type: "array",
213
- description: "该角色应拥有的全部菜单 id 数组",
214
- items: { type: "string" },
215
- },
216
- },
217
- required: ["roleId", "menuIds"],
218
- },
219
- },
220
- {
221
- name: "wls_action_query",
222
- description:
223
- "查询指定页面菜单(type=C)下的动作按钮列表(type=A)。返回 id/menuName/permission/orderNum/icon。",
224
- inputSchema: {
225
- type: "object",
226
- properties: {
227
- menuId: { type: "string", description: "父菜单 id(页面菜单)" },
228
- },
229
- required: ["menuId"],
230
- },
231
- },
232
- {
233
- name: "wls_action_upsert",
234
- description:
235
- "在指定页面菜单下批量新增动作按钮(type=A),按 permission 字段自动去重。" +
236
- "权限码命名规范:{资源camelCase}_{动作} 或 {模块}:{资源}:{动作}(与项目既有约定保持一致)。" +
237
- "常见动作:add/edit/remove/export/import/approve。",
238
- inputSchema: {
239
- type: "object",
240
- properties: {
241
- parentId: {
242
- type: "string",
243
- description: "页面菜单 id(动作挂在它下面)",
244
- },
245
- items: {
246
- type: "array",
247
- description:
248
- "动作数组。字段:menuName(必填,显示名), permission(必填,权限码), icon(可选,默认list), orderNum(可选,默认1), useCache(可选,默认1)",
249
- items: { type: "object" },
250
- },
251
- },
252
- required: ["parentId", "items"],
253
- },
254
- },
255
- {
256
- name: "wls_code_scan",
257
- description:
258
- "扫描项目页面目录,返回 index.vue/data.ts/index.scss/api.md 完整性与 API_CONFIG 概览。" +
259
- "默认扫描 src/views,可传 path 指定目录。适用于 convention-audit / Agent Pipeline 前置感知项目结构。",
260
- inputSchema: {
261
- type: "object",
262
- properties: {
263
- path: {
264
- type: "string",
265
- description: "相对项目根目录的扫描路径,默认 src/views",
266
- },
267
- },
268
- required: [],
269
- },
270
- },
271
- {
272
- name: "wls_route_check",
273
- description:
274
- "检查 src/views 页面目录是否能在路由文件中被发现。默认查找 vite/plugins/shared/pages.ts 等常见路由文件," +
275
- "可传 path 和 routeFile 定制。用于 page-codegen/menu-sync 后闭环验证。",
276
- inputSchema: {
277
- type: "object",
278
- properties: {
279
- path: { type: "string", description: "页面扫描路径,默认 src/views" },
280
- routeFile: {
281
- type: "string",
282
- description: "路由文件路径,默认自动探测",
283
- },
284
- },
285
- required: [],
286
- },
287
- },
288
- {
289
- name: "wls_validate_page",
290
- description:
291
- "校验页面是否符合 wl-skills-kit 最新页面规范:BaseTable+AGGrid+cid、defineColumns、renderOps、mock-first、api.md 等。",
292
- inputSchema: {
293
- type: "object",
294
- properties: {
295
- path: { type: "string", description: "页面或目录路径,默认 src/views" },
296
- },
297
- required: [],
298
- },
299
- },
300
- {
301
- name: "wls_doctor_ui",
302
- description:
303
- "检查 @agile-team/wk-skills-ui 是否真正接入:依赖、tokens、styles preset、installCommonPreset、defineColumns、renderOps。",
304
- inputSchema: { type: "object", properties: {}, required: [] },
305
- },
306
- {
307
- name: "wls_git_log_extract",
308
- description:
309
- "提取最近 N 次 git commit 摘要,用于 convention-audit 的 Git 规范检查或 changelog-gen 的数据源。",
310
- inputSchema: {
311
- type: "object",
312
- properties: {
313
- n: { type: "number", description: "提取数量,默认 20,最大 100" },
314
- },
315
- required: [],
316
- },
317
- },
318
- {
319
- name: "wls_audit_report_push",
320
- description:
321
- "将最新审计报告推送到飞书机器人 webhook。未配置 env.local.json 的 feishu_webhook 时静默跳过,不影响其他流程。",
322
- inputSchema: {
323
- type: "object",
324
- properties: {
325
- reportPath: {
326
- type: "string",
327
- description:
328
- "审计报告路径,不传则自动选择 .github/reports 下最新 AUDIT_*.md 或规范审查报告.md",
329
- },
330
- },
331
- required: [],
332
- },
333
- },
334
- ];
335
-
336
22
  // ─── JSON-RPC 协议层 ────────────────────────────────────────────────────
337
23
 
338
24
  function send(obj) {
@@ -347,23 +33,20 @@ function sendError(id, code, message) {
347
33
  send({ jsonrpc: "2.0", id, error: { code, message } });
348
34
  }
349
35
 
350
- // ─── Tool 调度 ───────────────────────────────────────────────────────────
36
+ // ─── Tool 调度(auto-dispatch via HANDLERS)─────────────────────────────
351
37
 
352
38
  async function dispatchTool(id, toolName, toolArgs) {
39
+ const desc = HANDLERS[toolName];
40
+ if (!desc) {
41
+ sendError(id, -32601, `未知工具: ${toolName}`);
42
+ return;
43
+ }
44
+
353
45
  let config;
354
- const needsBackendConfig = ![
355
- "wls_code_scan",
356
- "wls_route_check",
357
- "wls_validate_page",
358
- "wls_doctor_ui",
359
- "wls_git_log_extract",
360
- "wls_audit_report_push",
361
- ].includes(toolName);
362
- if (needsBackendConfig) {
46
+ if (desc.needsBackendConfig) {
363
47
  try {
364
48
  config = loadConfig();
365
49
  } catch (e) {
366
- // 配置加载失败:以文本形式返回给 AI(不是 JSON-RPC error,AI 能读)
367
50
  sendResult(id, {
368
51
  content: [{ type: "text", text: `❌ 配置加载失败: ${e.message}` }],
369
52
  isError: true,
@@ -373,63 +56,7 @@ async function dispatchTool(id, toolName, toolArgs) {
373
56
  }
374
57
 
375
58
  try {
376
- let text;
377
- switch (toolName) {
378
- case "wls_menu_query":
379
- text = await handleMenuQuery(config);
380
- break;
381
- case "wls_menu_upsert":
382
- text = await handleMenuUpsert(toolArgs, config);
383
- break;
384
- case "wls_menu_sync_from_report":
385
- text = await handleMenuSyncFromReport(toolArgs, config);
386
- break;
387
- case "wls_dict_query":
388
- text = await handleDictQuery(config);
389
- break;
390
- case "wls_dict_upsert":
391
- text = await handleDictUpsert(toolArgs, config);
392
- break;
393
- case "wls_role_query":
394
- text = await handleRoleQuery(toolArgs, config);
395
- break;
396
- case "wls_role_upsert":
397
- text = await handleRoleUpsert(toolArgs, config);
398
- break;
399
- case "wls_assignable_menus_query":
400
- text = await handleAssignableMenusQuery(toolArgs, config);
401
- break;
402
- case "wls_role_assign_menus":
403
- text = await handleRoleAssignMenus(toolArgs, config);
404
- break;
405
- case "wls_action_query":
406
- text = await handleActionQuery(toolArgs, config);
407
- break;
408
- case "wls_action_upsert":
409
- text = await handleActionUpsert(toolArgs, config);
410
- break;
411
- case "wls_code_scan":
412
- text = await handleCodeScan(toolArgs);
413
- break;
414
- case "wls_route_check":
415
- text = await handleRouteCheck(toolArgs);
416
- break;
417
- case "wls_validate_page":
418
- text = await handleValidatePage(toolArgs);
419
- break;
420
- case "wls_doctor_ui":
421
- text = await handleDoctorUi(toolArgs);
422
- break;
423
- case "wls_git_log_extract":
424
- text = await handleGitLogExtract(toolArgs);
425
- break;
426
- case "wls_audit_report_push":
427
- text = await handleAuditReportPush(toolArgs);
428
- break;
429
- default:
430
- sendError(id, -32601, `未知工具: ${toolName}`);
431
- return;
432
- }
59
+ const text = await desc.handle(toolArgs, config);
433
60
  sendResult(id, { content: [{ type: "text", text }] });
434
61
  } catch (e) {
435
62
  sendResult(id, {
@@ -441,55 +68,61 @@ async function dispatchTool(id, toolName, toolArgs) {
441
68
 
442
69
  // ─── 消息循环 ────────────────────────────────────────────────────────────
443
70
 
444
- const rl = readline.createInterface({ input: process.stdin, terminal: false });
445
-
446
- rl.on("line", async (line) => {
447
- const raw = line.trim();
448
- if (!raw) return;
449
-
450
- let msg;
451
- try {
452
- msg = JSON.parse(raw);
453
- } catch (e) {
454
- send({
455
- jsonrpc: "2.0",
456
- id: null,
457
- error: { code: -32700, message: "Parse error" },
458
- });
459
- return;
460
- }
461
-
462
- const { id, method, params = {} } = msg;
71
+ function startServer() {
72
+ const rl = readline.createInterface({
73
+ input: process.stdin,
74
+ terminal: false,
75
+ });
463
76
 
464
- // Notifications(无 id)不需要响应
465
- if (id === undefined || id === null) return;
77
+ rl.on("line", async (line) => {
78
+ const raw = line.trim();
79
+ if (!raw) return;
466
80
 
467
- switch (method) {
468
- case "initialize":
469
- sendResult(id, {
470
- protocolVersion: "2024-11-05",
471
- capabilities: { tools: {} },
472
- serverInfo: { name: "wl-skills", version: PKG.version },
81
+ let msg;
82
+ try {
83
+ msg = JSON.parse(raw);
84
+ } catch (e) {
85
+ send({
86
+ jsonrpc: "2.0",
87
+ id: null,
88
+ error: { code: -32700, message: "Parse error" },
473
89
  });
474
- break;
90
+ return;
91
+ }
475
92
 
476
- case "tools/list":
477
- sendResult(id, { tools: TOOLS });
478
- break;
93
+ const { id, method, params = {} } = msg;
94
+ if (id === undefined || id === null) return; // notifications
479
95
 
480
- case "tools/call":
481
- await dispatchTool(id, params.name, params.arguments || {});
482
- break;
96
+ switch (method) {
97
+ case "initialize":
98
+ sendResult(id, {
99
+ protocolVersion: "2024-11-05",
100
+ capabilities: { tools: {} },
101
+ serverInfo: { name: "wl-skills", version: PKG.version },
102
+ });
103
+ break;
104
+ case "tools/list":
105
+ sendResult(id, { tools: TOOLS });
106
+ break;
107
+ case "tools/call":
108
+ await dispatchTool(id, params.name, params.arguments || {});
109
+ break;
110
+ case "ping":
111
+ sendResult(id, {});
112
+ break;
113
+ default:
114
+ sendError(id, -32601, `Method not found: ${method}`);
115
+ }
116
+ });
483
117
 
484
- case "ping":
485
- sendResult(id, {});
486
- break;
118
+ rl.on("close", () => {
119
+ process.exit(0);
120
+ });
121
+ }
487
122
 
488
- default:
489
- sendError(id, -32601, `Method not found: ${method}`);
490
- }
491
- });
123
+ // 仅在直接执行时启动监听;被 require(如测试)时不启动
124
+ if (require.main === module) {
125
+ startServer();
126
+ }
492
127
 
493
- rl.on("close", () => {
494
- process.exit(0);
495
- });
128
+ module.exports = { TOOLS, HANDLERS, dispatchTool, startServer };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@agile-team/wl-skills-kit",
3
- "version": "2.6.0",
4
- "description": "AI Skill 模板包 v2.6.0 — 13 条编码规范 + 10 个 AI Skill + 17 个 MCP Tool,一条命令导入 Vue 3 项目",
3
+ "version": "2.7.0",
4
+ "description": "AI Skill 模板包 v2.7.0 — 13 条编码规范 + 10 个 AI Skill + 17 个 MCP Tool,一条命令导入 Vue 3 项目",
5
5
  "main": "./bin/wl-skills.js",
6
6
  "bin": {
7
7
  "wl-skills": "bin/wl-skills.js"
@@ -42,7 +42,10 @@
42
42
  "standards:init": "npx @robot-admin/git-standards init",
43
43
  "prepare": "husky",
44
44
  "cz": "git-cz",
45
- "lint": "eslint ."
45
+ "lint": "eslint .",
46
+ "test": "vitest run",
47
+ "version:verify": "node scripts/verify-version.js",
48
+ "prepublishOnly": "node scripts/verify-version.js && vitest run"
46
49
  },
47
50
  "dependencies": {
48
51
  "xlsx": "^0.18.5"
@@ -58,7 +61,8 @@
58
61
  "eslint": "^10.3.0",
59
62
  "eslint-plugin-vue": "^10.9.0",
60
63
  "husky": "^9.1.7",
61
- "lint-staged": "^16.4.0"
64
+ "lint-staged": "^16.4.0",
65
+ "vitest": "^2.1.9"
62
66
  },
63
67
  "config": {
64
68
  "commitizen": {