@agile-team/wl-skills-kit 2.7.2 → 2.8.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/CHANGELOG.md +37 -0
- package/README.md +40 -12
- package/bin/wl-skills.js +147 -3
- package/files/.github/copilot-instructions.md +18 -0
- package/files/.github/guides/architecture.md +6 -4
- package/files/.github/skills/_best-practices.md +230 -220
- package/files/.github/skills/core/page-codegen/SKILL.md +5 -5
- package/files/.github/skills/sync/_mcp-guardrail.md +109 -109
- package/files/.github/skills/sync/dict-sync/SKILL.md +208 -208
- package/files/.github/skills/sync/permission-sync/SKILL.md +240 -275
- package/files/docs/mock-architecture.md +321 -0
- package/files/mock/_utils.ts +35 -0
- package/mcp/api/client.js +83 -83
- package/mcp/tools/dictSync.js +178 -173
- package/mcp/tools/menuSync.js +11 -0
- package/package.json +2 -2
package/mcp/tools/dictSync.js
CHANGED
|
@@ -1,173 +1,178 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { queryDictModules, saveDictModule, saveDictItem } = require('../api/dictApi')
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* 从字典树查询响应中提取模块列表
|
|
7
|
-
* 响应结构: { dictionary: { children: DictModule[] } }
|
|
8
|
-
*
|
|
9
|
-
* @param {any} data - queryDictModules 返回的 result.data
|
|
10
|
-
* @returns {object[]}
|
|
11
|
-
*/
|
|
12
|
-
function extractModules(data) {
|
|
13
|
-
if (!data) return []
|
|
14
|
-
if (data.dictionary && Array.isArray(data.dictionary.children)) {
|
|
15
|
-
return data.dictionary.children
|
|
16
|
-
}
|
|
17
|
-
if (Array.isArray(data)) return data
|
|
18
|
-
return []
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* wls_dict_query 工具处理器
|
|
23
|
-
* 查询当前应用的所有字典模块及字典项
|
|
24
|
-
*
|
|
25
|
-
* @param {object} config
|
|
26
|
-
* @returns {Promise<string>}
|
|
27
|
-
*/
|
|
28
|
-
async function handleDictQuery(config) {
|
|
29
|
-
const result = await queryDictModules(config)
|
|
30
|
-
|
|
31
|
-
if (!result.ok) {
|
|
32
|
-
return `❌ 查询字典失败: ${result.error} (code: ${result.code})`
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const modules = extractModules(result.data)
|
|
36
|
-
|
|
37
|
-
if (modules.length === 0) {
|
|
38
|
-
return '✅ 字典查询成功,当前应用暂无字典数据'
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
return `✅ 字典查询成功,共 ${modules.length} 个模块\n\n${JSON.stringify(modules, null, 2)}`
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* wls_dict_upsert 工具处理器
|
|
46
|
-
* 新增或更新字典模块及字典项(完整幂等流程)
|
|
47
|
-
*
|
|
48
|
-
* 流程:
|
|
49
|
-
* ① 查询现有模块,检查 strSn 是否已存在
|
|
50
|
-
* ② 若不存在:创建模块(data=null)→ 重新查询 → 用 strSn 定位拿 id
|
|
51
|
-
* ③ 若已存在:跳过模块创建,直接取已有 id
|
|
52
|
-
* ④ 遍历 items,跳过 strSn 已存在的,其余逐条创建
|
|
53
|
-
*
|
|
54
|
-
* @param {{ module: object, items?: object[] }} args
|
|
55
|
-
* @param {object} config
|
|
56
|
-
* @returns {Promise<string>}
|
|
57
|
-
*/
|
|
58
|
-
async function handleDictUpsert(args, config) {
|
|
59
|
-
const { module: moduleBody, items = [] } = args
|
|
60
|
-
|
|
61
|
-
if (!moduleBody || !moduleBody.strSn) {
|
|
62
|
-
return '❌ 参数错误:module.strSn 必填'
|
|
63
|
-
}
|
|
64
|
-
if (!moduleBody.strName) {
|
|
65
|
-
return '❌ 参数错误:module.strName 必填'
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ① 查询现有模块
|
|
69
|
-
const queryResult = await queryDictModules(config)
|
|
70
|
-
if (!queryResult.ok) {
|
|
71
|
-
return `❌ 查询字典失败: ${queryResult.error}`
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const existingModules = extractModules(queryResult.data)
|
|
75
|
-
let targetModule = existingModules.find((m) => m.strSn === moduleBody.strSn)
|
|
76
|
-
let moduleAction
|
|
77
|
-
|
|
78
|
-
// ② 模块不存在时创建
|
|
79
|
-
if (!targetModule) {
|
|
80
|
-
const saveBody = {
|
|
81
|
-
strSn: moduleBody.strSn,
|
|
82
|
-
strName: moduleBody.strName,
|
|
83
|
-
sortPriority: moduleBody.sortPriority != null ? String(moduleBody.sortPriority) : '1',
|
|
84
|
-
strLevel: 2,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const createResult = await saveDictModule(saveBody, config)
|
|
88
|
-
if (!createResult.ok) {
|
|
89
|
-
return `❌ 创建字典模块失败: ${createResult.error}`
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// ③ 重新查询(data=null,只能靠 re-query 拿 id)
|
|
93
|
-
const reQueryResult = await queryDictModules(config)
|
|
94
|
-
if (!reQueryResult.ok) {
|
|
95
|
-
return `❌ 重新查询字典失败: ${reQueryResult.error}`
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const freshModules = extractModules(reQueryResult.data)
|
|
99
|
-
targetModule = freshModules.find((m) => m.strSn === moduleBody.strSn)
|
|
100
|
-
|
|
101
|
-
if (!targetModule) {
|
|
102
|
-
return `❌ 字典模块创建后未能找到(strSn="${moduleBody.strSn}"),请在字典管理后台确认`
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
moduleAction = '✅ 已创建'
|
|
106
|
-
} else {
|
|
107
|
-
moduleAction = '⏭ 已存在(跳过创建)'
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const moduleId = targetModule.id
|
|
111
|
-
|
|
112
|
-
// ④ 处理字典项
|
|
113
|
-
const existingItems = Array.isArray(targetModule.dictionaries)
|
|
114
|
-
? targetModule.dictionaries
|
|
115
|
-
: []
|
|
116
|
-
const existingSns = new Set(existingItems.map((i) => i.strSn))
|
|
117
|
-
|
|
118
|
-
const itemResults = []
|
|
119
|
-
|
|
120
|
-
for (const item of items) {
|
|
121
|
-
if (existingSns.has(item.strSn)) {
|
|
122
|
-
itemResults.push({
|
|
123
|
-
strSn: item.strSn,
|
|
124
|
-
strName: item.strName || '',
|
|
125
|
-
status: '⏭ 已存在(跳过)',
|
|
126
|
-
})
|
|
127
|
-
continue
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const itemBody = {
|
|
131
|
-
moduleId,
|
|
132
|
-
strSn: item.strSn,
|
|
133
|
-
strName: item.strName,
|
|
134
|
-
strLevel: 2,
|
|
135
|
-
dtlValue: item.dtlValue != null ? item.dtlValue : '',
|
|
136
|
-
dtlValueRequired: item.dtlValueRequired || false,
|
|
137
|
-
dtlValue2Required: item.dtlValue2Required || false,
|
|
138
|
-
dtlValue3Required: item.dtlValue3Required || false,
|
|
139
|
-
dtlValue4Required: item.dtlValue4Required || false,
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const createResult = await saveDictItem(itemBody, config)
|
|
143
|
-
if (createResult.ok) {
|
|
144
|
-
itemResults.push({ strSn: item.strSn, strName: item.strName || '', status: '✅ 已创建' })
|
|
145
|
-
} else {
|
|
146
|
-
itemResults.push({
|
|
147
|
-
strSn: item.strSn,
|
|
148
|
-
strName: item.strName || '',
|
|
149
|
-
status: `❌ 失败: ${createResult.error}`,
|
|
150
|
-
})
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const createdCount = itemResults.filter((r) => r.status.startsWith('✅')).length
|
|
155
|
-
const skippedCount = itemResults.filter((r) => r.status.startsWith('⏭')).length
|
|
156
|
-
const failedCount = itemResults.filter((r) => r.status.startsWith('❌')).length
|
|
157
|
-
|
|
158
|
-
let output = `字典模块 "${moduleBody.strSn}" (${moduleBody.strName}):${moduleAction}\n`
|
|
159
|
-
output += `模块 ID:${moduleId}\n`
|
|
160
|
-
|
|
161
|
-
if (items.length > 0) {
|
|
162
|
-
output += `字典项:创建 ${createdCount},跳过 ${skippedCount},失败 ${failedCount}\n\n`
|
|
163
|
-
output += '| strSn | strName | 状态 |\n'
|
|
164
|
-
output += '|---|---|---|\n'
|
|
165
|
-
for (const r of itemResults) {
|
|
166
|
-
output += `| ${r.strSn} | ${r.strName} | ${r.status} |\n`
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return output
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
module.exports = {
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { queryDictModules, saveDictModule, saveDictItem } = require('../api/dictApi')
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 从字典树查询响应中提取模块列表
|
|
7
|
+
* 响应结构: { dictionary: { children: DictModule[] } }
|
|
8
|
+
*
|
|
9
|
+
* @param {any} data - queryDictModules 返回的 result.data
|
|
10
|
+
* @returns {object[]}
|
|
11
|
+
*/
|
|
12
|
+
function extractModules(data) {
|
|
13
|
+
if (!data) return []
|
|
14
|
+
if (data.dictionary && Array.isArray(data.dictionary.children)) {
|
|
15
|
+
return data.dictionary.children
|
|
16
|
+
}
|
|
17
|
+
if (Array.isArray(data)) return data
|
|
18
|
+
return []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* wls_dict_query 工具处理器
|
|
23
|
+
* 查询当前应用的所有字典模块及字典项
|
|
24
|
+
*
|
|
25
|
+
* @param {object} config
|
|
26
|
+
* @returns {Promise<string>}
|
|
27
|
+
*/
|
|
28
|
+
async function handleDictQuery(config) {
|
|
29
|
+
const result = await queryDictModules(config)
|
|
30
|
+
|
|
31
|
+
if (!result.ok) {
|
|
32
|
+
return `❌ 查询字典失败: ${result.error} (code: ${result.code})`
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const modules = extractModules(result.data)
|
|
36
|
+
|
|
37
|
+
if (modules.length === 0) {
|
|
38
|
+
return '✅ 字典查询成功,当前应用暂无字典数据'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return `✅ 字典查询成功,共 ${modules.length} 个模块\n\n${JSON.stringify(modules, null, 2)}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* wls_dict_upsert 工具处理器
|
|
46
|
+
* 新增或更新字典模块及字典项(完整幂等流程)
|
|
47
|
+
*
|
|
48
|
+
* 流程:
|
|
49
|
+
* ① 查询现有模块,检查 strSn 是否已存在
|
|
50
|
+
* ② 若不存在:创建模块(data=null)→ 重新查询 → 用 strSn 定位拿 id
|
|
51
|
+
* ③ 若已存在:跳过模块创建,直接取已有 id
|
|
52
|
+
* ④ 遍历 items,跳过 strSn 已存在的,其余逐条创建
|
|
53
|
+
*
|
|
54
|
+
* @param {{ module: object, items?: object[] }} args
|
|
55
|
+
* @param {object} config
|
|
56
|
+
* @returns {Promise<string>}
|
|
57
|
+
*/
|
|
58
|
+
async function handleDictUpsert(args, config) {
|
|
59
|
+
const { module: moduleBody, items = [] } = args
|
|
60
|
+
|
|
61
|
+
if (!moduleBody || !moduleBody.strSn) {
|
|
62
|
+
return '❌ 参数错误:module.strSn 必填'
|
|
63
|
+
}
|
|
64
|
+
if (!moduleBody.strName) {
|
|
65
|
+
return '❌ 参数错误:module.strName 必填'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ① 查询现有模块
|
|
69
|
+
const queryResult = await queryDictModules(config)
|
|
70
|
+
if (!queryResult.ok) {
|
|
71
|
+
return `❌ 查询字典失败: ${queryResult.error}`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const existingModules = extractModules(queryResult.data)
|
|
75
|
+
let targetModule = existingModules.find((m) => m.strSn === moduleBody.strSn)
|
|
76
|
+
let moduleAction
|
|
77
|
+
|
|
78
|
+
// ② 模块不存在时创建
|
|
79
|
+
if (!targetModule) {
|
|
80
|
+
const saveBody = {
|
|
81
|
+
strSn: moduleBody.strSn,
|
|
82
|
+
strName: moduleBody.strName,
|
|
83
|
+
sortPriority: moduleBody.sortPriority != null ? String(moduleBody.sortPriority) : '1',
|
|
84
|
+
strLevel: 2,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const createResult = await saveDictModule(saveBody, config)
|
|
88
|
+
if (!createResult.ok) {
|
|
89
|
+
return `❌ 创建字典模块失败: ${createResult.error}`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ③ 重新查询(data=null,只能靠 re-query 拿 id)
|
|
93
|
+
const reQueryResult = await queryDictModules(config)
|
|
94
|
+
if (!reQueryResult.ok) {
|
|
95
|
+
return `❌ 重新查询字典失败: ${reQueryResult.error}`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const freshModules = extractModules(reQueryResult.data)
|
|
99
|
+
targetModule = freshModules.find((m) => m.strSn === moduleBody.strSn)
|
|
100
|
+
|
|
101
|
+
if (!targetModule) {
|
|
102
|
+
return `❌ 字典模块创建后未能找到(strSn="${moduleBody.strSn}"),请在字典管理后台确认`
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
moduleAction = '✅ 已创建'
|
|
106
|
+
} else {
|
|
107
|
+
moduleAction = '⏭ 已存在(跳过创建)'
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const moduleId = targetModule.id
|
|
111
|
+
|
|
112
|
+
// ④ 处理字典项
|
|
113
|
+
const existingItems = Array.isArray(targetModule.dictionaries)
|
|
114
|
+
? targetModule.dictionaries
|
|
115
|
+
: []
|
|
116
|
+
const existingSns = new Set(existingItems.map((i) => i.strSn))
|
|
117
|
+
|
|
118
|
+
const itemResults = []
|
|
119
|
+
|
|
120
|
+
for (const item of items) {
|
|
121
|
+
if (existingSns.has(item.strSn)) {
|
|
122
|
+
itemResults.push({
|
|
123
|
+
strSn: item.strSn,
|
|
124
|
+
strName: item.strName || '',
|
|
125
|
+
status: '⏭ 已存在(跳过)',
|
|
126
|
+
})
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const itemBody = {
|
|
131
|
+
moduleId,
|
|
132
|
+
strSn: item.strSn,
|
|
133
|
+
strName: item.strName,
|
|
134
|
+
strLevel: 2,
|
|
135
|
+
dtlValue: item.dtlValue != null ? item.dtlValue : '',
|
|
136
|
+
dtlValueRequired: item.dtlValueRequired || false,
|
|
137
|
+
dtlValue2Required: item.dtlValue2Required || false,
|
|
138
|
+
dtlValue3Required: item.dtlValue3Required || false,
|
|
139
|
+
dtlValue4Required: item.dtlValue4Required || false,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const createResult = await saveDictItem(itemBody, config)
|
|
143
|
+
if (createResult.ok) {
|
|
144
|
+
itemResults.push({ strSn: item.strSn, strName: item.strName || '', status: '✅ 已创建' })
|
|
145
|
+
} else {
|
|
146
|
+
itemResults.push({
|
|
147
|
+
strSn: item.strSn,
|
|
148
|
+
strName: item.strName || '',
|
|
149
|
+
status: `❌ 失败: ${createResult.error}`,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const createdCount = itemResults.filter((r) => r.status.startsWith('✅')).length
|
|
155
|
+
const skippedCount = itemResults.filter((r) => r.status.startsWith('⏭')).length
|
|
156
|
+
const failedCount = itemResults.filter((r) => r.status.startsWith('❌')).length
|
|
157
|
+
|
|
158
|
+
let output = `字典模块 "${moduleBody.strSn}" (${moduleBody.strName}):${moduleAction}\n`
|
|
159
|
+
output += `模块 ID:${moduleId}\n`
|
|
160
|
+
|
|
161
|
+
if (items.length > 0) {
|
|
162
|
+
output += `字典项:创建 ${createdCount},跳过 ${skippedCount},失败 ${failedCount}\n\n`
|
|
163
|
+
output += '| strSn | strName | 状态 |\n'
|
|
164
|
+
output += '|---|---|---|\n'
|
|
165
|
+
for (const r of itemResults) {
|
|
166
|
+
output += `| ${r.strSn} | ${r.strName} | ${r.status} |\n`
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return output
|
|
171
|
+
}
|
|
172
|
+
|
|
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.
|
|
4
|
-
"description": "AI Skill 模板包 v2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
|
+
"description": "AI Skill 模板包 v2.8.0 — 13 条编码规范 + 10 个 AI Skill + 17 个 MCP Tool + Mock 架构体系,一条命令导入 Vue 3 项目",
|
|
5
5
|
"main": "./bin/wl-skills.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"wl-skills": "bin/wl-skills.js"
|