@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.
- package/CHANGELOG.md +495 -404
- package/README.md +286 -261
- package/bin/wl-skills.js +796 -503
- package/docs/ai/345/205/250/346/231/257/345/210/206/346/236/220.md +144 -0
- package/docs/input-spec-api.md +263 -0
- package/docs/input-spec-detailed-design.md +238 -0
- package/docs/input-spec-page-spec.md +371 -0
- package/docs/input-spec-prototype.md +176 -0
- 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
- package/files/.github/copilot-instructions.md +3 -3
- package/files/.github/guides/architecture.md +11 -11
- package/files/.github/guides/usage.md +5 -4
- package/files/.github/skills/_compat/headers/cursor-mdc.txt +1 -1
- package/files/.github/skills/_compat/headers/kiro.txt +1 -1
- package/files/.github/skills/_compat/headers/trae.txt +1 -1
- package/files/.github/skills/_pipeline.md +91 -0
- package/files/.github/skills/_registry.md +4 -2
- package/files/.github/skills/core/convention-audit/SKILL.md +241 -65
- package/files/.github/skills/core/page-codegen/SKILL.md +3 -3
- package/files/.github/skills/core/page-codegen/USAGE.md +1 -1
- package/files/.github/skills/core/page-codegen/templates/domains/_CONTRIBUTING.md +1 -1
- package/files/.github/skills/core/template-extract/SKILL.md +1 -1
- package/files/.github/skills/sync/env.local.json +20 -18
- package/files/.github/standards/02-code-structure.md +34 -4
- package/files/.github/standards/08-git.md +24 -0
- package/files/.github/standards/12-base-table.md +44 -0
- package/files/.github/standards/index.md +2 -2
- package/mcp/server.js +411 -330
- package/mcp/tools/projectTools.js +228 -0
- package/package.json +40 -39
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const { execFileSync } = require('child_process')
|
|
6
|
+
const https = require('https')
|
|
7
|
+
|
|
8
|
+
function getProjectRoot() {
|
|
9
|
+
return process.env.WL_PROJECT_ROOT ? path.resolve(process.env.WL_PROJECT_ROOT) : process.cwd()
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizePath(p) {
|
|
13
|
+
return p.replace(/\\/g, '/')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function safeResolve(root, inputPath) {
|
|
17
|
+
const full = inputPath ? path.resolve(root, inputPath) : root
|
|
18
|
+
if (full !== root && !full.startsWith(root + path.sep)) {
|
|
19
|
+
throw new Error('路径越界:只能扫描项目根目录内的文件')
|
|
20
|
+
}
|
|
21
|
+
return full
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function walkFiles(dir, baseDir, files) {
|
|
25
|
+
files = files || []
|
|
26
|
+
if (!fs.existsSync(dir)) return files
|
|
27
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
28
|
+
for (const entry of entries) {
|
|
29
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist') continue
|
|
30
|
+
const full = path.join(dir, entry.name)
|
|
31
|
+
if (entry.isDirectory()) walkFiles(full, baseDir, files)
|
|
32
|
+
else files.push(normalizePath(path.relative(baseDir, full)))
|
|
33
|
+
}
|
|
34
|
+
return files
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function findPageDirs(root, scanRel) {
|
|
38
|
+
const scanDir = safeResolve(root, scanRel || 'src/views')
|
|
39
|
+
const files = walkFiles(scanDir, root)
|
|
40
|
+
const dirs = new Map()
|
|
41
|
+
for (const rel of files) {
|
|
42
|
+
const name = path.basename(rel)
|
|
43
|
+
const dir = normalizePath(path.dirname(rel))
|
|
44
|
+
if (!dirs.has(dir)) dirs.set(dir, new Set())
|
|
45
|
+
dirs.get(dir).add(name)
|
|
46
|
+
}
|
|
47
|
+
const pages = []
|
|
48
|
+
for (const [dir, names] of dirs.entries()) {
|
|
49
|
+
if (!names.has('index.vue')) continue
|
|
50
|
+
const dataPath = path.join(root, dir, 'data.ts')
|
|
51
|
+
let apiConfigCount = 0
|
|
52
|
+
if (fs.existsSync(dataPath)) {
|
|
53
|
+
const content = fs.readFileSync(dataPath, 'utf8')
|
|
54
|
+
apiConfigCount = (content.match(/API_CONFIG/g) || []).length
|
|
55
|
+
}
|
|
56
|
+
pages.push({
|
|
57
|
+
dir,
|
|
58
|
+
hasIndexVue: names.has('index.vue'),
|
|
59
|
+
hasDataTs: names.has('data.ts'),
|
|
60
|
+
hasIndexScss: names.has('index.scss'),
|
|
61
|
+
hasApiMd: names.has('api.md'),
|
|
62
|
+
apiConfigCount,
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
pages.sort((a, b) => a.dir.localeCompare(b.dir))
|
|
66
|
+
return pages
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function formatPagesTable(pages) {
|
|
70
|
+
const lines = ['| 页面目录 | index.vue | data.ts | index.scss | api.md | API_CONFIG |', '|---|---:|---:|---:|---:|---:|']
|
|
71
|
+
for (const page of pages) {
|
|
72
|
+
lines.push(
|
|
73
|
+
`| ${page.dir} | ${page.hasIndexVue ? '✅' : '❌'} | ${page.hasDataTs ? '✅' : '❌'} | ${page.hasIndexScss ? '✅' : '❌'} | ${page.hasApiMd ? '✅' : '—'} | ${page.apiConfigCount} |`
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
return lines.join('\n')
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function handleCodeScan(args) {
|
|
80
|
+
const root = getProjectRoot()
|
|
81
|
+
const scanPath = args && args.path ? args.path : 'src/views'
|
|
82
|
+
const pages = findPageDirs(root, scanPath)
|
|
83
|
+
const missingData = pages.filter((p) => !p.hasDataTs).length
|
|
84
|
+
const missingScss = pages.filter((p) => !p.hasIndexScss).length
|
|
85
|
+
const missingApi = pages.filter((p) => !p.hasApiMd).length
|
|
86
|
+
const apiPages = pages.filter((p) => p.apiConfigCount > 0).length
|
|
87
|
+
|
|
88
|
+
if (pages.length === 0) {
|
|
89
|
+
return `⚠️ 未在 ${scanPath} 下发现包含 index.vue 的页面目录`
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return [
|
|
93
|
+
`✅ 代码结构扫描完成:${scanPath}`,
|
|
94
|
+
'',
|
|
95
|
+
`- 页面目录:${pages.length}`,
|
|
96
|
+
`- 含 API_CONFIG:${apiPages}`,
|
|
97
|
+
`- 缺 data.ts:${missingData}`,
|
|
98
|
+
`- 缺 index.scss:${missingScss}`,
|
|
99
|
+
`- 缺 api.md:${missingApi}(需结合场景判断是否必须)`,
|
|
100
|
+
'',
|
|
101
|
+
formatPagesTable(pages),
|
|
102
|
+
].join('\n')
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function findRouteFile(root, inputPath) {
|
|
106
|
+
if (inputPath) {
|
|
107
|
+
const full = safeResolve(root, inputPath)
|
|
108
|
+
return fs.existsSync(full) ? full : null
|
|
109
|
+
}
|
|
110
|
+
const candidates = [
|
|
111
|
+
'vite/plugins/shared/pages.ts',
|
|
112
|
+
'src/router/pages.ts',
|
|
113
|
+
'src/router/routes.ts',
|
|
114
|
+
'src/router/index.ts',
|
|
115
|
+
]
|
|
116
|
+
for (const rel of candidates) {
|
|
117
|
+
const full = path.join(root, rel)
|
|
118
|
+
if (fs.existsSync(full)) return full
|
|
119
|
+
}
|
|
120
|
+
return null
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function handleRouteCheck(args) {
|
|
124
|
+
const root = getProjectRoot()
|
|
125
|
+
const scanPath = args && args.path ? args.path : 'src/views'
|
|
126
|
+
const routeFile = findRouteFile(root, args && args.routeFile)
|
|
127
|
+
if (!routeFile) {
|
|
128
|
+
return '⚠️ 未找到路由文件,默认检查路径:vite/plugins/shared/pages.ts / src/router/pages.ts / src/router/routes.ts / src/router/index.ts'
|
|
129
|
+
}
|
|
130
|
+
const routeContent = fs.readFileSync(routeFile, 'utf8').replace(/\\/g, '/')
|
|
131
|
+
const pages = findPageDirs(root, scanPath)
|
|
132
|
+
const rows = []
|
|
133
|
+
for (const page of pages) {
|
|
134
|
+
const viewRel = page.dir.replace(/^src\/views\//, '')
|
|
135
|
+
const lastSegment = viewRel.split('/').filter(Boolean).pop() || viewRel
|
|
136
|
+
const registered = routeContent.includes(viewRel) || routeContent.includes(page.dir) || routeContent.includes(lastSegment)
|
|
137
|
+
rows.push({ dir: page.dir, registered })
|
|
138
|
+
}
|
|
139
|
+
const miss = rows.filter((r) => !r.registered)
|
|
140
|
+
const relRoute = normalizePath(path.relative(root, routeFile))
|
|
141
|
+
const lines = [`✅ 路由检查完成:${relRoute}`, '', `- 页面目录:${rows.length}`, `- 疑似未注册:${miss.length}`, '', '| 页面目录 | 路由文件中可发现 |', '|---|---:|']
|
|
142
|
+
for (const row of rows) lines.push(`| ${row.dir} | ${row.registered ? '✅' : '⚠️'} |`)
|
|
143
|
+
return lines.join('\n')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function handleGitLogExtract(args) {
|
|
147
|
+
const root = getProjectRoot()
|
|
148
|
+
const n = Math.max(1, Math.min(Number((args && args.n) || 20), 100))
|
|
149
|
+
let output
|
|
150
|
+
try {
|
|
151
|
+
output = execFileSync('git', ['log', `-${n}`, '--pretty=format:%h%x09%s%x09%an%x09%ad', '--date=short'], { cwd: root, encoding: 'utf8' })
|
|
152
|
+
} catch (e) {
|
|
153
|
+
return `❌ git log 提取失败:${e.message}`
|
|
154
|
+
}
|
|
155
|
+
if (!output.trim()) return '⚠️ 未提取到 git log'
|
|
156
|
+
const lines = ['✅ 最近提交摘要', '', '| hash | message | author | date |', '|---|---|---|---|']
|
|
157
|
+
for (const row of output.split('\n')) {
|
|
158
|
+
const [hash, message, author, date] = row.split('\t')
|
|
159
|
+
lines.push(`| ${hash} | ${String(message || '').replace(/\|/g, '\\|')} | ${author || ''} | ${date || ''} |`)
|
|
160
|
+
}
|
|
161
|
+
return lines.join('\n')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function readEnvLocal(root) {
|
|
165
|
+
const envPath = path.join(root, '.github', 'skills', 'sync', 'env.local.json')
|
|
166
|
+
if (!fs.existsSync(envPath)) return null
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(fs.readFileSync(envPath, 'utf8'))
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return null
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function findLatestAuditReport(root, inputPath) {
|
|
175
|
+
if (inputPath) {
|
|
176
|
+
const full = safeResolve(root, inputPath)
|
|
177
|
+
return fs.existsSync(full) ? full : null
|
|
178
|
+
}
|
|
179
|
+
const reportDir = path.join(root, '.github', 'reports')
|
|
180
|
+
if (!fs.existsSync(reportDir)) return null
|
|
181
|
+
const files = fs
|
|
182
|
+
.readdirSync(reportDir)
|
|
183
|
+
.filter((name) => /^AUDIT_.*\.md$|规范审查报告\.md$/.test(name))
|
|
184
|
+
.map((name) => path.join(reportDir, name))
|
|
185
|
+
.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs)
|
|
186
|
+
return files[0] || null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function postWebhook(webhook, payload) {
|
|
190
|
+
return new Promise((resolve) => {
|
|
191
|
+
const body = JSON.stringify(payload)
|
|
192
|
+
const req = https.request(
|
|
193
|
+
webhook,
|
|
194
|
+
{ method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } },
|
|
195
|
+
(res) => {
|
|
196
|
+
const chunks = []
|
|
197
|
+
res.on('data', (chunk) => chunks.push(chunk))
|
|
198
|
+
res.on('end', () => resolve({ ok: res.statusCode >= 200 && res.statusCode < 300, statusCode: res.statusCode, body: Buffer.concat(chunks).toString('utf8') }))
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
req.on('error', (e) => resolve({ ok: false, error: e.message }))
|
|
202
|
+
req.write(body)
|
|
203
|
+
req.end()
|
|
204
|
+
})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async function handleAuditReportPush(args) {
|
|
208
|
+
const root = getProjectRoot()
|
|
209
|
+
const env = readEnvLocal(root)
|
|
210
|
+
const webhook = env && env.feishu_webhook
|
|
211
|
+
if (!webhook || String(webhook).includes('你的') || String(webhook).includes('webhook')) {
|
|
212
|
+
return 'ℹ️ 未配置 env.local.json 的 feishu_webhook,已跳过审计报告推送'
|
|
213
|
+
}
|
|
214
|
+
const report = findLatestAuditReport(root, args && args.reportPath)
|
|
215
|
+
if (!report) return '⚠️ 未找到可推送的审计报告'
|
|
216
|
+
const rel = normalizePath(path.relative(root, report))
|
|
217
|
+
const content = fs.readFileSync(report, 'utf8').slice(0, 3500)
|
|
218
|
+
const result = await postWebhook(webhook, { msg_type: 'text', content: { text: `wl-skills 审计报告:${rel}\n\n${content}` } })
|
|
219
|
+
if (!result.ok) return `❌ 飞书推送失败:${result.error || result.statusCode}`
|
|
220
|
+
return `✅ 审计报告已推送:${rel}`
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
module.exports = {
|
|
224
|
+
handleCodeScan,
|
|
225
|
+
handleRouteCheck,
|
|
226
|
+
handleGitLogExtract,
|
|
227
|
+
handleAuditReportPush,
|
|
228
|
+
}
|
package/package.json
CHANGED
|
@@ -1,40 +1,41 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@agile-team/wl-skills-kit",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "AI Skill 模板包 —
|
|
5
|
-
"main": "./bin/wl-skills.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"wl-skills": "bin/wl-skills.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"bin/",
|
|
11
|
-
"files/",
|
|
12
|
-
"mcp/",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@agile-team/wl-skills-kit",
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "AI Skill 模板包 v2.4.0 — 13 条编码规范 + 9 个 AI Skill + 14 个 MCP Tool,一条命令导入 Vue 3 项目",
|
|
5
|
+
"main": "./bin/wl-skills.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"wl-skills": "bin/wl-skills.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"files/",
|
|
12
|
+
"mcp/",
|
|
13
|
+
"docs/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"CHANGELOG.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"ai",
|
|
19
|
+
"skills",
|
|
20
|
+
"copilot",
|
|
21
|
+
"vue3",
|
|
22
|
+
"template",
|
|
23
|
+
"codegen",
|
|
24
|
+
"cursor",
|
|
25
|
+
"windsurf",
|
|
26
|
+
"kiro",
|
|
27
|
+
"agents"
|
|
28
|
+
],
|
|
29
|
+
"author": "JHLC Frontend Team",
|
|
30
|
+
"license": "UNLICENSED",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/ChenyCHENYU/wl-skills-kit.git"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=16.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"xlsx": "^0.18.5"
|
|
40
|
+
}
|
|
40
41
|
}
|