@agile-team/wl-skills-kit 2.7.1 → 2.7.3
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 +44 -0
- package/README.md +16 -1
- package/bin/wl-skills.js +1 -1
- package/files/.github/copilot-instructions.md +12 -0
- package/files/.github/guides/architecture.md +1 -1
- package/files/.github/skills/_best-practices.md +220 -0
- package/files/.github/skills/_registry.md +23 -19
- package/files/.github/skills/sync/_mcp-guardrail.md +109 -0
- package/files/.github/skills/sync/dict-sync/SKILL.md +208 -451
- package/files/.github/skills/sync/menu-sync/SKILL.md +34 -27
- package/files/.github/skills/sync/menu-sync/USAGE.md +17 -11
- package/files/.github/skills/sync/permission-sync/SKILL.md +240 -258
- package/mcp/api/client.js +83 -76
- package/mcp/server.js +21 -0
- package/mcp/tools/dictSync.js +6 -1
- package/mcp/tools/menuSync.js +11 -0
- package/package.json +5 -4
package/mcp/api/client.js
CHANGED
|
@@ -1,76 +1,83 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const https = require('https')
|
|
4
|
-
const http = require('http')
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* 带鉴权的 HTTP 客户端(兼容 Node 16+,无额外依赖)
|
|
8
|
-
*
|
|
9
|
-
* @param {string} urlPath - 接口路径(以 / 开头),拼接到 config.gatewayPath 后
|
|
10
|
-
* @param {{ method?: string, body?: unknown }} options
|
|
11
|
-
* @param {{ gatewayPath: string, token: string }} config
|
|
12
|
-
* @returns {Promise<{ ok: boolean, data: any, error?: string, code?: number }>}
|
|
13
|
-
*/
|
|
14
|
-
function wlsFetch(urlPath, options, config) {
|
|
15
|
-
const fullUrl = config.gatewayPath + urlPath
|
|
16
|
-
const isHttps = fullUrl.startsWith('https')
|
|
17
|
-
const lib = isHttps ? https : http
|
|
18
|
-
const bodyStr = options.body ? JSON.stringify(options.body) : null
|
|
19
|
-
|
|
20
|
-
let urlObj
|
|
21
|
-
try {
|
|
22
|
-
urlObj = new URL(fullUrl)
|
|
23
|
-
} catch (e) {
|
|
24
|
-
return Promise.reject(new Error(`无效的 URL: ${fullUrl}`))
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const reqOptions = {
|
|
28
|
-
hostname: urlObj.hostname,
|
|
29
|
-
port: urlObj.port || (isHttps ? 443 : 80),
|
|
30
|
-
path: urlObj.pathname + urlObj.search,
|
|
31
|
-
method: options.method || 'GET',
|
|
32
|
-
headers: {
|
|
33
|
-
'Content-Type': 'application/json',
|
|
34
|
-
'Authorization': `Bearer ${config.token}`,
|
|
35
|
-
},
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (bodyStr) {
|
|
39
|
-
reqOptions.headers['Content-Length'] = Buffer.byteLength(bodyStr)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return new Promise((resolve, reject) => {
|
|
43
|
-
const req = lib.request(reqOptions, (res) => {
|
|
44
|
-
let data = ''
|
|
45
|
-
res.on('data', (chunk) => { data += chunk })
|
|
46
|
-
res.on('end', () => {
|
|
47
|
-
try {
|
|
48
|
-
const json = JSON.parse(data)
|
|
49
|
-
if (json.code === 2000) {
|
|
50
|
-
resolve({ ok: true, data: json.data, code: json.code })
|
|
51
|
-
} else {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
req.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const https = require('https')
|
|
4
|
+
const http = require('http')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 带鉴权的 HTTP 客户端(兼容 Node 16+,无额外依赖)
|
|
8
|
+
*
|
|
9
|
+
* @param {string} urlPath - 接口路径(以 / 开头),拼接到 config.gatewayPath 后
|
|
10
|
+
* @param {{ method?: string, body?: unknown }} options
|
|
11
|
+
* @param {{ gatewayPath: string, token: string }} config
|
|
12
|
+
* @returns {Promise<{ ok: boolean, data: any, error?: string, code?: number }>}
|
|
13
|
+
*/
|
|
14
|
+
function wlsFetch(urlPath, options, config) {
|
|
15
|
+
const fullUrl = config.gatewayPath + urlPath
|
|
16
|
+
const isHttps = fullUrl.startsWith('https')
|
|
17
|
+
const lib = isHttps ? https : http
|
|
18
|
+
const bodyStr = options.body ? JSON.stringify(options.body) : null
|
|
19
|
+
|
|
20
|
+
let urlObj
|
|
21
|
+
try {
|
|
22
|
+
urlObj = new URL(fullUrl)
|
|
23
|
+
} catch (e) {
|
|
24
|
+
return Promise.reject(new Error(`无效的 URL: ${fullUrl}`))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const reqOptions = {
|
|
28
|
+
hostname: urlObj.hostname,
|
|
29
|
+
port: urlObj.port || (isHttps ? 443 : 80),
|
|
30
|
+
path: urlObj.pathname + urlObj.search,
|
|
31
|
+
method: options.method || 'GET',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'Authorization': `Bearer ${config.token}`,
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (bodyStr) {
|
|
39
|
+
reqOptions.headers['Content-Length'] = Buffer.byteLength(bodyStr)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const req = lib.request(reqOptions, (res) => {
|
|
44
|
+
let data = ''
|
|
45
|
+
res.on('data', (chunk) => { data += chunk })
|
|
46
|
+
res.on('end', () => {
|
|
47
|
+
try {
|
|
48
|
+
const json = JSON.parse(data)
|
|
49
|
+
if (json.code === 2000) {
|
|
50
|
+
resolve({ ok: true, data: json.data, code: json.code })
|
|
51
|
+
} else {
|
|
52
|
+
// 友好提示:401/4004 等典型错误附带排查指引
|
|
53
|
+
let hint = ''
|
|
54
|
+
if (json.code === 401 || /token|未登录|鉴权/i.test(json.message || '')) {
|
|
55
|
+
hint = '(Token 可能已过期,请重新登录后更新 env.local.json 的 token 字段,仅填纯 JWT 不含 bearer 前缀)'
|
|
56
|
+
} else if (json.code === 4004 || /url:|not\s*found/i.test(json.message || '')) {
|
|
57
|
+
hint = '(接口未找到。可能是 gatewayPath 配置缺失前缀或后端环境差异;请检查 env.local.json 的 gatewayPath。不要让 AI 绕开 MCP 自己拼 HTTP)'
|
|
58
|
+
}
|
|
59
|
+
resolve({
|
|
60
|
+
ok: false,
|
|
61
|
+
data: null,
|
|
62
|
+
error: (json.message || `服务端返回 code=${json.code}`) + hint,
|
|
63
|
+
code: json.code,
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
} catch (e) {
|
|
67
|
+
reject(new Error(`响应解析失败: ${e.message},原始内容: ${data.slice(0, 200)}`))
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
req.on('error', (e) => reject(new Error(`请求失败: ${e.message}`)))
|
|
73
|
+
req.setTimeout(15000, () => {
|
|
74
|
+
req.destroy()
|
|
75
|
+
reject(new Error('请求超时(15s)'))
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
if (bodyStr) req.write(bodyStr)
|
|
79
|
+
req.end()
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { wlsFetch }
|
package/mcp/server.js
CHANGED
|
@@ -122,7 +122,28 @@ function startServer() {
|
|
|
122
122
|
|
|
123
123
|
// 仅在直接执行时启动监听;被 require(如测试)时不启动
|
|
124
124
|
if (require.main === module) {
|
|
125
|
+
printBanner();
|
|
125
126
|
startServer();
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
/**
|
|
130
|
+
* 启动 banner — 写入 stderr,不污染 stdout 的 JSON-RPC 通道
|
|
131
|
+
* 让用户在编辑器 MCP 输出面板能直观看到:版本、工具数量、项目根
|
|
132
|
+
*/
|
|
133
|
+
function printBanner() {
|
|
134
|
+
const projectRoot = process.env.WL_PROJECT_ROOT || process.cwd();
|
|
135
|
+
const lines = [
|
|
136
|
+
"",
|
|
137
|
+
"═══════════════════════════════════════════════════",
|
|
138
|
+
` wl-skills MCP Server v${PKG.version}`,
|
|
139
|
+
"═══════════════════════════════════════════════════",
|
|
140
|
+
` 项目根 (WL_PROJECT_ROOT): ${projectRoot}`,
|
|
141
|
+
` 已注册工具 (${TOOLS.length}):`,
|
|
142
|
+
...TOOLS.map((t) => ` • ${t.name}`),
|
|
143
|
+
"═══════════════════════════════════════════════════",
|
|
144
|
+
"",
|
|
145
|
+
];
|
|
146
|
+
process.stderr.write(lines.join("\n") + "\n");
|
|
147
|
+
}
|
|
148
|
+
|
|
128
149
|
module.exports = { TOOLS, HANDLERS, dispatchTool, startServer };
|
package/mcp/tools/dictSync.js
CHANGED
|
@@ -170,4 +170,9 @@ async function handleDictUpsert(args, config) {
|
|
|
170
170
|
return output
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
module.exports = {
|
|
173
|
+
module.exports = {
|
|
174
|
+
handleDictQuery,
|
|
175
|
+
handleDictUpsert,
|
|
176
|
+
// 导出纯工具函数供单测覆盖
|
|
177
|
+
_internal: { extractModules },
|
|
178
|
+
}
|
package/mcp/tools/menuSync.js
CHANGED
|
@@ -413,4 +413,15 @@ module.exports = {
|
|
|
413
413
|
handleMenuQuery,
|
|
414
414
|
handleMenuUpsert,
|
|
415
415
|
handleMenuSyncFromReport,
|
|
416
|
+
// 导出纯工具函数供单测覆盖
|
|
417
|
+
_internal: {
|
|
418
|
+
cleanCell,
|
|
419
|
+
splitMarkdownRow,
|
|
420
|
+
isDividerRow,
|
|
421
|
+
parseBoolean,
|
|
422
|
+
normalizeTree,
|
|
423
|
+
flattenMenus,
|
|
424
|
+
findExisting,
|
|
425
|
+
parseReport,
|
|
426
|
+
},
|
|
416
427
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agile-team/wl-skills-kit",
|
|
3
|
-
"version": "2.7.
|
|
4
|
-
"description": "AI Skill 模板包 v2.7.
|
|
3
|
+
"version": "2.7.3",
|
|
4
|
+
"description": "AI Skill 模板包 v2.7.3 — 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"
|
|
@@ -43,9 +43,10 @@
|
|
|
43
43
|
"prepare": "husky",
|
|
44
44
|
"cz": "git-cz",
|
|
45
45
|
"lint": "eslint .",
|
|
46
|
+
"lint:skills": "node scripts/lint-skills.js",
|
|
46
47
|
"test": "vitest run",
|
|
47
48
|
"version:verify": "node scripts/verify-version.js",
|
|
48
|
-
"prepublishOnly": "node scripts/verify-version.js && vitest run"
|
|
49
|
+
"prepublishOnly": "node scripts/verify-version.js && node scripts/lint-skills.js && vitest run"
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
52
|
"xlsx": "^0.18.5"
|
|
@@ -74,4 +75,4 @@
|
|
|
74
75
|
"eslint --fix --no-cache"
|
|
75
76
|
]
|
|
76
77
|
}
|
|
77
|
-
}
|
|
78
|
+
}
|