@agile-team/wl-skills-kit 2.3.7 → 2.4.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 (30) hide show
  1. package/CHANGELOG.md +495 -404
  2. package/README.md +286 -261
  3. package/bin/wl-skills.js +796 -503
  4. package/docs/ai/345/205/250/346/231/257/345/210/206/346/236/220.md +144 -0
  5. package/docs/input-spec-api.md +263 -0
  6. package/docs/input-spec-detailed-design.md +238 -0
  7. package/docs/input-spec-page-spec.md +371 -0
  8. package/docs/input-spec-prototype.md +176 -0
  9. package/docs//345/205/250/347/233/230/345/210/206/346/236/220/344/270/216/346/231/272/350/203/275/344/275/223/346/220/255/345/273/272/346/214/207/345/215/227.md +267 -0
  10. package/files/.github/copilot-instructions.md +3 -3
  11. package/files/.github/guides/architecture.md +11 -11
  12. package/files/.github/guides/usage.md +5 -4
  13. package/files/.github/skills/_compat/headers/cursor-mdc.txt +1 -1
  14. package/files/.github/skills/_compat/headers/kiro.txt +1 -1
  15. package/files/.github/skills/_compat/headers/trae.txt +1 -1
  16. package/files/.github/skills/_pipeline.md +91 -0
  17. package/files/.github/skills/_registry.md +4 -2
  18. package/files/.github/skills/core/convention-audit/SKILL.md +241 -65
  19. package/files/.github/skills/core/page-codegen/SKILL.md +3 -3
  20. package/files/.github/skills/core/page-codegen/USAGE.md +1 -1
  21. package/files/.github/skills/core/page-codegen/templates/domains/_CONTRIBUTING.md +1 -1
  22. package/files/.github/skills/core/template-extract/SKILL.md +1 -1
  23. package/files/.github/skills/sync/env.local.json +20 -18
  24. package/files/.github/standards/02-code-structure.md +34 -4
  25. package/files/.github/standards/08-git.md +24 -0
  26. package/files/.github/standards/12-base-table.md +44 -0
  27. package/files/.github/standards/index.md +2 -2
  28. package/mcp/server.js +411 -330
  29. package/mcp/tools/projectTools.js +228 -0
  30. package/package.json +40 -39
package/mcp/server.js CHANGED
@@ -1,330 +1,411 @@
1
- #!/usr/bin/env node
2
- 'use strict'
3
-
4
- /**
5
- * wl-skills MCP Server
6
- *
7
- * 实现 MCP 协议(stdio transport,JSON-RPC 2.0)
8
- * 暴露工具:
9
- * wls_menu_query 查询菜单树
10
- * wls_menu_upsert 批量新增/更新菜单
11
- * wls_dict_query 查询字典模块
12
- * wls_dict_upsert 新增/更新字典模块及字典项
13
- * wls_role_query 查询角色列表
14
- * wls_role_upsert 批量新增角色(按 code 去重)
15
- * wls_assignable_menus_query 查询全量可授权菜单(用于角色授权)
16
- * wls_role_assign_menus 给角色批量分配菜单权限
17
- * wls_action_query 查询页面菜单下的动作(type=A)
18
- * wls_action_upsert 批量新增动作(按 permission 去重)
19
- *
20
- * 启动方式(由 .cursor/mcp.json 自动注入):
21
- * node node_modules/@agile-team/wl-skills-kit/mcp/server.js
22
- */
23
-
24
- const readline = require('readline')
25
- const { loadConfig } = require('./config')
26
- const { handleMenuQuery, handleMenuUpsert } = require('./tools/menuSync')
27
- const { handleDictQuery, handleDictUpsert } = require('./tools/dictSync')
28
- const {
29
- handleRoleQuery,
30
- handleRoleUpsert,
31
- handleRoleAssignMenus,
32
- handleAssignableMenusQuery,
33
- handleActionQuery,
34
- handleActionUpsert,
35
- } = require('./tools/permissionSync')
36
-
37
- const PKG = require('../package.json')
38
-
39
- // ─── Tool 注册表 ────────────────────────────────────────────────────────
40
-
41
- const TOOLS = [
42
- {
43
- name: 'wls_menu_query',
44
- description:
45
- '查询当前应用的完整菜单树。自动从 .github/skills/sync/env.local.json 读取 domainId,' +
46
- '无需传参。在 wls_menu_upsert 前调用,用于判断哪些菜单需要新增、哪些需要更新。',
47
- inputSchema: {
48
- type: 'object',
49
- properties: {},
50
- required: [],
51
- },
52
- },
53
- {
54
- name: 'wls_menu_upsert',
55
- description:
56
- '批量新增或更新菜单项。有 id 字段 → 更新;无 id 字段 → 新增。' +
57
- '新增时响应自动包含服务端生成的 id,可链式用于创建子菜单。',
58
- inputSchema: {
59
- type: 'object',
60
- properties: {
61
- items: {
62
- type: 'array',
63
- description:
64
- 'MenuSaveBody 数组。每项字段:' +
65
- 'id(更新时传), sysAppNo, menuName, menuNameCode, parentId, ' +
66
- 'type("M"=目录/"C"=菜单), path, icon, orderNum, ' +
67
- 'useCache(1), common(2), hidden(false), editMode(false), ' +
68
- 'component(type=C时传), permission(type=C时传)',
69
- items: { type: 'object' },
70
- },
71
- },
72
- required: ['items'],
73
- },
74
- },
75
- {
76
- name: 'wls_dict_query',
77
- description:
78
- '查询当前应用的所有字典模块及字典项。在 wls_dict_upsert 前调用,' +
79
- '用于判断哪些模块/字典项已存在。',
80
- inputSchema: {
81
- type: 'object',
82
- properties: {},
83
- required: [],
84
- },
85
- },
86
- {
87
- name: 'wls_dict_upsert',
88
- description:
89
- '新增或更新字典模块及其字典项。内部自动处理:' +
90
- '若模块不存在则创建(data=null 后自动 re-query 获取 id),' +
91
- '若已存在则直接取 id;字典项自动跳过已存在的 strSn。',
92
- inputSchema: {
93
- type: 'object',
94
- properties: {
95
- module: {
96
- type: 'object',
97
- description: 'DictModuleSaveBody: strSn(必填), strName(必填), sortPriority("1"), strLevel(2)',
98
- properties: {
99
- strSn: { type: 'string', description: '模块标识符,如 "gender"' },
100
- strName: { type: 'string', description: '模块显示名,如 "性别"' },
101
- sortPriority: { type: 'string', description: '排序,字符串类型,如 "1"' },
102
- strLevel: { type: 'number', description: '固定传 2' },
103
- },
104
- required: ['strSn', 'strName'],
105
- },
106
- items: {
107
- type: 'array',
108
- description:
109
- 'DictItemSaveBody 数组(可选)。每项字段:' +
110
- 'strSn(必填), strName(必填), strLevel(2), ' +
111
- 'dtlValue(""), dtlValueRequired(false), dtlValue2Required(false), ' +
112
- 'dtlValue3Required(false), dtlValue4Required(false)',
113
- items: { type: 'object' },
114
- },
115
- },
116
- required: ['module'],
117
- },
118
- },
119
- {
120
- name: 'wls_role_query',
121
- description:
122
- '查询角色列表。可选参数 current/size 翻页,默认 size=100。返回精简字段:id, roleName, code, sysAppNo, roleDesc。',
123
- inputSchema: {
124
- type: 'object',
125
- properties: {
126
- current: { type: 'number', description: '页码,默认 1' },
127
- size: { type: 'number', description: '每页数量,默认 100' },
128
- },
129
- required: [],
130
- },
131
- },
132
- {
133
- name: 'wls_role_upsert',
134
- description:
135
- '批量新增角色(按 code 字段自动去重;已存在则跳过)。每项必填 roleName 和 code,可选 configDesc。' +
136
- '注意:角色仅新增不更新,因角色变更通常需要业务确认。',
137
- inputSchema: {
138
- type: 'object',
139
- properties: {
140
- items: {
141
- type: 'array',
142
- description:
143
- '角色数组。字段:roleName(必填,显示名), code(必填,唯一标识), configDesc(可选,描述)',
144
- items: { type: 'object' },
145
- },
146
- },
147
- required: ['items'],
148
- },
149
- },
150
- {
151
- name: 'wls_assignable_menus_query',
152
- description:
153
- '查询全量可授权菜单列表(扁平结构,含菜单 id/menuName/permission)。' +
154
- '在 wls_role_assign_menus 前调用,AI 据此选出要分配给角色的 menuIds。',
155
- inputSchema: { type: 'object', properties: {}, required: [] },
156
- },
157
- {
158
- name: 'wls_role_assign_menus',
159
- description:
160
- '给指定角色批量分配菜单权限。menuIds 传字符串数组,内部自动拼成逗号分隔字符串提交后端。' +
161
- '该接口为全量覆盖式,应包含该角色所有菜单(含已有的,否则会被移除)。',
162
- inputSchema: {
163
- type: 'object',
164
- properties: {
165
- roleId: { type: 'string', description: '角色 id(来自 wls_role_query)' },
166
- menuIds: {
167
- type: 'array',
168
- description: '该角色应拥有的全部菜单 id 数组',
169
- items: { type: 'string' },
170
- },
171
- },
172
- required: ['roleId', 'menuIds'],
173
- },
174
- },
175
- {
176
- name: 'wls_action_query',
177
- description:
178
- '查询指定页面菜单(type=C)下的动作按钮列表(type=A)。返回 id/menuName/permission/orderNum/icon。',
179
- inputSchema: {
180
- type: 'object',
181
- properties: {
182
- menuId: { type: 'string', description: '父菜单 id(页面菜单)' },
183
- },
184
- required: ['menuId'],
185
- },
186
- },
187
- {
188
- name: 'wls_action_upsert',
189
- description:
190
- '在指定页面菜单下批量新增动作按钮(type=A),按 permission 字段自动去重。' +
191
- '权限码命名规范:{资源camelCase}_{动作} {模块}:{资源}:{动作}(与项目既有约定保持一致)。' +
192
- '常见动作:add/edit/remove/export/import/approve。',
193
- inputSchema: {
194
- type: 'object',
195
- properties: {
196
- parentId: { type: 'string', description: '页面菜单 id(动作挂在它下面)' },
197
- items: {
198
- type: 'array',
199
- description:
200
- '动作数组。字段:menuName(必填,显示名), permission(必填,权限码), icon(可选,默认list), orderNum(可选,默认1), useCache(可选,默认1)',
201
- items: { type: 'object' },
202
- },
203
- },
204
- required: ['parentId', 'items'],
205
- },
206
- },
207
- ]
208
-
209
- // ─── JSON-RPC 协议层 ────────────────────────────────────────────────────
210
-
211
- function send(obj) {
212
- process.stdout.write(JSON.stringify(obj) + '\n')
213
- }
214
-
215
- function sendResult(id, result) {
216
- send({ jsonrpc: '2.0', id, result })
217
- }
218
-
219
- function sendError(id, code, message) {
220
- send({ jsonrpc: '2.0', id, error: { code, message } })
221
- }
222
-
223
- // ─── Tool 调度 ───────────────────────────────────────────────────────────
224
-
225
- async function dispatchTool(id, toolName, toolArgs) {
226
- let config
227
- try {
228
- config = loadConfig()
229
- } catch (e) {
230
- // 配置加载失败:以文本形式返回给 AI(不是 JSON-RPC error,AI 能读)
231
- sendResult(id, { content: [{ type: 'text', text: `❌ 配置加载失败: ${e.message}` }], isError: true })
232
- return
233
- }
234
-
235
- try {
236
- let text
237
- switch (toolName) {
238
- case 'wls_menu_query':
239
- text = await handleMenuQuery(config)
240
- break
241
- case 'wls_menu_upsert':
242
- text = await handleMenuUpsert(toolArgs, config)
243
- break
244
- case 'wls_dict_query':
245
- text = await handleDictQuery(config)
246
- break
247
- case 'wls_dict_upsert':
248
- text = await handleDictUpsert(toolArgs, config)
249
- break
250
- case 'wls_role_query':
251
- text = await handleRoleQuery(toolArgs, config)
252
- break
253
- case 'wls_role_upsert':
254
- text = await handleRoleUpsert(toolArgs, config)
255
- break
256
- case 'wls_assignable_menus_query':
257
- text = await handleAssignableMenusQuery(toolArgs, config)
258
- break
259
- case 'wls_role_assign_menus':
260
- text = await handleRoleAssignMenus(toolArgs, config)
261
- break
262
- case 'wls_action_query':
263
- text = await handleActionQuery(toolArgs, config)
264
- break
265
- case 'wls_action_upsert':
266
- text = await handleActionUpsert(toolArgs, config)
267
- break
268
- default:
269
- sendError(id, -32601, `未知工具: ${toolName}`)
270
- return
271
- }
272
- sendResult(id, { content: [{ type: 'text', text }] })
273
- } catch (e) {
274
- sendResult(id, {
275
- content: [{ type: 'text', text: `❌ 工具执行异常: ${e.message}` }],
276
- isError: true,
277
- })
278
- }
279
- }
280
-
281
- // ─── 消息循环 ────────────────────────────────────────────────────────────
282
-
283
- const rl = readline.createInterface({ input: process.stdin, terminal: false })
284
-
285
- rl.on('line', async (line) => {
286
- const raw = line.trim()
287
- if (!raw) return
288
-
289
- let msg
290
- try {
291
- msg = JSON.parse(raw)
292
- } catch (e) {
293
- send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } })
294
- return
295
- }
296
-
297
- const { id, method, params = {} } = msg
298
-
299
- // Notifications(无 id)不需要响应
300
- if (id === undefined || id === null) return
301
-
302
- switch (method) {
303
- case 'initialize':
304
- sendResult(id, {
305
- protocolVersion: '2024-11-05',
306
- capabilities: { tools: {} },
307
- serverInfo: { name: 'wl-skills', version: PKG.version },
308
- })
309
- break
310
-
311
- case 'tools/list':
312
- sendResult(id, { tools: TOOLS })
313
- break
314
-
315
- case 'tools/call':
316
- await dispatchTool(id, params.name, params.arguments || {})
317
- break
318
-
319
- case 'ping':
320
- sendResult(id, {})
321
- break
322
-
323
- default:
324
- sendError(id, -32601, `Method not found: ${method}`)
325
- }
326
- })
327
-
328
- rl.on('close', () => {
329
- process.exit(0)
330
- })
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ /**
5
+ * wl-skills MCP Server
6
+ *
7
+ * 实现 MCP 协议(stdio transport,JSON-RPC 2.0)
8
+ * 暴露工具:
9
+ * wls_menu_query 查询菜单树
10
+ * wls_menu_upsert 批量新增/更新菜单
11
+ * wls_dict_query 查询字典模块
12
+ * wls_dict_upsert 新增/更新字典模块及字典项
13
+ * wls_role_query 查询角色列表
14
+ * wls_role_upsert 批量新增角色(按 code 去重)
15
+ * wls_assignable_menus_query 查询全量可授权菜单(用于角色授权)
16
+ * wls_role_assign_menus 给角色批量分配菜单权限
17
+ * wls_action_query 查询页面菜单下的动作(type=A)
18
+ * wls_action_upsert 批量新增动作(按 permission 去重)
19
+ * wls_code_scan 扫描页面目录与文件完整性
20
+ * wls_route_check 检查页面目录在路由文件中的可发现性
21
+ * wls_git_log_extract 提取最近提交摘要
22
+ * wls_audit_report_push 推送最新审计报告(可选飞书 webhook)
23
+ *
24
+ * 启动方式(由 .cursor/mcp.json 自动注入):
25
+ * node node_modules/@agile-team/wl-skills-kit/mcp/server.js
26
+ */
27
+
28
+ const readline = require('readline')
29
+ const { loadConfig } = require('./config')
30
+ const { handleMenuQuery, handleMenuUpsert } = require('./tools/menuSync')
31
+ const { handleDictQuery, handleDictUpsert } = require('./tools/dictSync')
32
+ const {
33
+ handleRoleQuery,
34
+ handleRoleUpsert,
35
+ handleRoleAssignMenus,
36
+ handleAssignableMenusQuery,
37
+ handleActionQuery,
38
+ handleActionUpsert,
39
+ } = require('./tools/permissionSync')
40
+ const {
41
+ handleCodeScan,
42
+ handleRouteCheck,
43
+ handleGitLogExtract,
44
+ handleAuditReportPush,
45
+ } = require('./tools/projectTools')
46
+
47
+ const PKG = require('../package.json')
48
+
49
+ // ─── Tool 注册表 ────────────────────────────────────────────────────────
50
+
51
+ const TOOLS = [
52
+ {
53
+ name: 'wls_menu_query',
54
+ description:
55
+ '查询当前应用的完整菜单树。自动从 .github/skills/sync/env.local.json 读取 domainId,' +
56
+ '无需传参。在 wls_menu_upsert 前调用,用于判断哪些菜单需要新增、哪些需要更新。',
57
+ inputSchema: {
58
+ type: 'object',
59
+ properties: {},
60
+ required: [],
61
+ },
62
+ },
63
+ {
64
+ name: 'wls_menu_upsert',
65
+ description:
66
+ '批量新增或更新菜单项。有 id 字段 更新;无 id 字段 → 新增。' +
67
+ '新增时响应自动包含服务端生成的 id,可链式用于创建子菜单。',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {
71
+ items: {
72
+ type: 'array',
73
+ description:
74
+ 'MenuSaveBody 数组。每项字段:' +
75
+ 'id(更新时传), sysAppNo, menuName, menuNameCode, parentId, ' +
76
+ 'type("M"=目录/"C"=菜单), path, icon, orderNum, ' +
77
+ 'useCache(1), common(2), hidden(false), editMode(false), ' +
78
+ 'component(type=C时传), permission(type=C时传)',
79
+ items: { type: 'object' },
80
+ },
81
+ },
82
+ required: ['items'],
83
+ },
84
+ },
85
+ {
86
+ name: 'wls_dict_query',
87
+ description:
88
+ '查询当前应用的所有字典模块及字典项。在 wls_dict_upsert 前调用,' +
89
+ '用于判断哪些模块/字典项已存在。',
90
+ inputSchema: {
91
+ type: 'object',
92
+ properties: {},
93
+ required: [],
94
+ },
95
+ },
96
+ {
97
+ name: 'wls_dict_upsert',
98
+ description:
99
+ '新增或更新字典模块及其字典项。内部自动处理:' +
100
+ '若模块不存在则创建(data=null 后自动 re-query 获取 id),' +
101
+ '若已存在则直接取 id;字典项自动跳过已存在的 strSn。',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ module: {
106
+ type: 'object',
107
+ description: 'DictModuleSaveBody: strSn(必填), strName(必填), sortPriority("1"), strLevel(2)',
108
+ properties: {
109
+ strSn: { type: 'string', description: '模块标识符,如 "gender"' },
110
+ strName: { type: 'string', description: '模块显示名,如 "性别"' },
111
+ sortPriority: { type: 'string', description: '排序,字符串类型,如 "1"' },
112
+ strLevel: { type: 'number', description: '固定传 2' },
113
+ },
114
+ required: ['strSn', 'strName'],
115
+ },
116
+ items: {
117
+ type: 'array',
118
+ description:
119
+ 'DictItemSaveBody 数组(可选)。每项字段:' +
120
+ 'strSn(必填), strName(必填), strLevel(2), ' +
121
+ 'dtlValue(""), dtlValueRequired(false), dtlValue2Required(false), ' +
122
+ 'dtlValue3Required(false), dtlValue4Required(false)',
123
+ items: { type: 'object' },
124
+ },
125
+ },
126
+ required: ['module'],
127
+ },
128
+ },
129
+ {
130
+ name: 'wls_role_query',
131
+ description:
132
+ '查询角色列表。可选参数 current/size 翻页,默认 size=100。返回精简字段:id, roleName, code, sysAppNo, roleDesc。',
133
+ inputSchema: {
134
+ type: 'object',
135
+ properties: {
136
+ current: { type: 'number', description: '页码,默认 1' },
137
+ size: { type: 'number', description: '每页数量,默认 100' },
138
+ },
139
+ required: [],
140
+ },
141
+ },
142
+ {
143
+ name: 'wls_role_upsert',
144
+ description:
145
+ '批量新增角色(按 code 字段自动去重;已存在则跳过)。每项必填 roleName 和 code,可选 configDesc。' +
146
+ '注意:角色仅新增不更新,因角色变更通常需要业务确认。',
147
+ inputSchema: {
148
+ type: 'object',
149
+ properties: {
150
+ items: {
151
+ type: 'array',
152
+ description:
153
+ '角色数组。字段:roleName(必填,显示名), code(必填,唯一标识), configDesc(可选,描述)',
154
+ items: { type: 'object' },
155
+ },
156
+ },
157
+ required: ['items'],
158
+ },
159
+ },
160
+ {
161
+ name: 'wls_assignable_menus_query',
162
+ description:
163
+ '查询全量可授权菜单列表(扁平结构,含菜单 id/menuName/permission)。' +
164
+ '在 wls_role_assign_menus 前调用,AI 据此选出要分配给角色的 menuIds。',
165
+ inputSchema: { type: 'object', properties: {}, required: [] },
166
+ },
167
+ {
168
+ name: 'wls_role_assign_menus',
169
+ description:
170
+ '给指定角色批量分配菜单权限。menuIds 传字符串数组,内部自动拼成逗号分隔字符串提交后端。' +
171
+ '该接口为全量覆盖式,应包含该角色所有菜单(含已有的,否则会被移除)。',
172
+ inputSchema: {
173
+ type: 'object',
174
+ properties: {
175
+ roleId: { type: 'string', description: '角色 id(来自 wls_role_query)' },
176
+ menuIds: {
177
+ type: 'array',
178
+ description: '该角色应拥有的全部菜单 id 数组',
179
+ items: { type: 'string' },
180
+ },
181
+ },
182
+ required: ['roleId', 'menuIds'],
183
+ },
184
+ },
185
+ {
186
+ name: 'wls_action_query',
187
+ description:
188
+ '查询指定页面菜单(type=C)下的动作按钮列表(type=A)。返回 id/menuName/permission/orderNum/icon。',
189
+ inputSchema: {
190
+ type: 'object',
191
+ properties: {
192
+ menuId: { type: 'string', description: '父菜单 id(页面菜单)' },
193
+ },
194
+ required: ['menuId'],
195
+ },
196
+ },
197
+ {
198
+ name: 'wls_action_upsert',
199
+ description:
200
+ '在指定页面菜单下批量新增动作按钮(type=A),按 permission 字段自动去重。' +
201
+ '权限码命名规范:{资源camelCase}_{动作} {模块}:{资源}:{动作}(与项目既有约定保持一致)。' +
202
+ '常见动作:add/edit/remove/export/import/approve。',
203
+ inputSchema: {
204
+ type: 'object',
205
+ properties: {
206
+ parentId: { type: 'string', description: '页面菜单 id(动作挂在它下面)' },
207
+ items: {
208
+ type: 'array',
209
+ description:
210
+ '动作数组。字段:menuName(必填,显示名), permission(必填,权限码), icon(可选,默认list), orderNum(可选,默认1), useCache(可选,默认1)',
211
+ items: { type: 'object' },
212
+ },
213
+ },
214
+ required: ['parentId', 'items'],
215
+ },
216
+ },
217
+ {
218
+ name: 'wls_code_scan',
219
+ description:
220
+ '扫描项目页面目录,返回 index.vue/data.ts/index.scss/api.md 完整性与 API_CONFIG 概览。' +
221
+ '默认扫描 src/views,可传 path 指定目录。适用于 convention-audit / Agent Pipeline 前置感知项目结构。',
222
+ inputSchema: {
223
+ type: 'object',
224
+ properties: {
225
+ path: { type: 'string', description: '相对项目根目录的扫描路径,默认 src/views' },
226
+ },
227
+ required: [],
228
+ },
229
+ },
230
+ {
231
+ name: 'wls_route_check',
232
+ description:
233
+ '检查 src/views 页面目录是否能在路由文件中被发现。默认查找 vite/plugins/shared/pages.ts 等常见路由文件,' +
234
+ '可传 path 和 routeFile 定制。用于 page-codegen/menu-sync 后闭环验证。',
235
+ inputSchema: {
236
+ type: 'object',
237
+ properties: {
238
+ path: { type: 'string', description: '页面扫描路径,默认 src/views' },
239
+ routeFile: { type: 'string', description: '路由文件路径,默认自动探测' },
240
+ },
241
+ required: [],
242
+ },
243
+ },
244
+ {
245
+ name: 'wls_git_log_extract',
246
+ description:
247
+ '提取最近 N 次 git commit 摘要,用于 convention-audit 的 Git 规范检查或 changelog-gen 的数据源。',
248
+ inputSchema: {
249
+ type: 'object',
250
+ properties: {
251
+ n: { type: 'number', description: '提取数量,默认 20,最大 100' },
252
+ },
253
+ required: [],
254
+ },
255
+ },
256
+ {
257
+ name: 'wls_audit_report_push',
258
+ description:
259
+ '将最新审计报告推送到飞书机器人 webhook。未配置 env.local.json 的 feishu_webhook 时静默跳过,不影响其他流程。',
260
+ inputSchema: {
261
+ type: 'object',
262
+ properties: {
263
+ reportPath: { type: 'string', description: '审计报告路径,不传则自动选择 .github/reports 下最新 AUDIT_*.md 或规范审查报告.md' },
264
+ },
265
+ required: [],
266
+ },
267
+ },
268
+ ]
269
+
270
+ // ─── JSON-RPC 协议层 ────────────────────────────────────────────────────
271
+
272
+ function send(obj) {
273
+ process.stdout.write(JSON.stringify(obj) + '\n')
274
+ }
275
+
276
+ function sendResult(id, result) {
277
+ send({ jsonrpc: '2.0', id, result })
278
+ }
279
+
280
+ function sendError(id, code, message) {
281
+ send({ jsonrpc: '2.0', id, error: { code, message } })
282
+ }
283
+
284
+ // ─── Tool 调度 ───────────────────────────────────────────────────────────
285
+
286
+ async function dispatchTool(id, toolName, toolArgs) {
287
+ let config
288
+ const needsBackendConfig = ![
289
+ 'wls_code_scan',
290
+ 'wls_route_check',
291
+ 'wls_git_log_extract',
292
+ 'wls_audit_report_push',
293
+ ].includes(toolName)
294
+ if (needsBackendConfig) {
295
+ try {
296
+ config = loadConfig()
297
+ } catch (e) {
298
+ // 配置加载失败:以文本形式返回给 AI(不是 JSON-RPC error,AI 能读)
299
+ sendResult(id, { content: [{ type: 'text', text: `❌ 配置加载失败: ${e.message}` }], isError: true })
300
+ return
301
+ }
302
+ }
303
+
304
+ try {
305
+ let text
306
+ switch (toolName) {
307
+ case 'wls_menu_query':
308
+ text = await handleMenuQuery(config)
309
+ break
310
+ case 'wls_menu_upsert':
311
+ text = await handleMenuUpsert(toolArgs, config)
312
+ break
313
+ case 'wls_dict_query':
314
+ text = await handleDictQuery(config)
315
+ break
316
+ case 'wls_dict_upsert':
317
+ text = await handleDictUpsert(toolArgs, config)
318
+ break
319
+ case 'wls_role_query':
320
+ text = await handleRoleQuery(toolArgs, config)
321
+ break
322
+ case 'wls_role_upsert':
323
+ text = await handleRoleUpsert(toolArgs, config)
324
+ break
325
+ case 'wls_assignable_menus_query':
326
+ text = await handleAssignableMenusQuery(toolArgs, config)
327
+ break
328
+ case 'wls_role_assign_menus':
329
+ text = await handleRoleAssignMenus(toolArgs, config)
330
+ break
331
+ case 'wls_action_query':
332
+ text = await handleActionQuery(toolArgs, config)
333
+ break
334
+ case 'wls_action_upsert':
335
+ text = await handleActionUpsert(toolArgs, config)
336
+ break
337
+ case 'wls_code_scan':
338
+ text = await handleCodeScan(toolArgs)
339
+ break
340
+ case 'wls_route_check':
341
+ text = await handleRouteCheck(toolArgs)
342
+ break
343
+ case 'wls_git_log_extract':
344
+ text = await handleGitLogExtract(toolArgs)
345
+ break
346
+ case 'wls_audit_report_push':
347
+ text = await handleAuditReportPush(toolArgs)
348
+ break
349
+ default:
350
+ sendError(id, -32601, `未知工具: ${toolName}`)
351
+ return
352
+ }
353
+ sendResult(id, { content: [{ type: 'text', text }] })
354
+ } catch (e) {
355
+ sendResult(id, {
356
+ content: [{ type: 'text', text: `❌ 工具执行异常: ${e.message}` }],
357
+ isError: true,
358
+ })
359
+ }
360
+ }
361
+
362
+ // ─── 消息循环 ────────────────────────────────────────────────────────────
363
+
364
+ const rl = readline.createInterface({ input: process.stdin, terminal: false })
365
+
366
+ rl.on('line', async (line) => {
367
+ const raw = line.trim()
368
+ if (!raw) return
369
+
370
+ let msg
371
+ try {
372
+ msg = JSON.parse(raw)
373
+ } catch (e) {
374
+ send({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } })
375
+ return
376
+ }
377
+
378
+ const { id, method, params = {} } = msg
379
+
380
+ // Notifications(无 id)不需要响应
381
+ if (id === undefined || id === null) return
382
+
383
+ switch (method) {
384
+ case 'initialize':
385
+ sendResult(id, {
386
+ protocolVersion: '2024-11-05',
387
+ capabilities: { tools: {} },
388
+ serverInfo: { name: 'wl-skills', version: PKG.version },
389
+ })
390
+ break
391
+
392
+ case 'tools/list':
393
+ sendResult(id, { tools: TOOLS })
394
+ break
395
+
396
+ case 'tools/call':
397
+ await dispatchTool(id, params.name, params.arguments || {})
398
+ break
399
+
400
+ case 'ping':
401
+ sendResult(id, {})
402
+ break
403
+
404
+ default:
405
+ sendError(id, -32601, `Method not found: ${method}`)
406
+ }
407
+ })
408
+
409
+ rl.on('close', () => {
410
+ process.exit(0)
411
+ })