@agile-team/wl-skills-kit 2.3.8 → 2.4.1

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 (28) hide show
  1. package/CHANGELOG.md +495 -447
  2. package/README.md +286 -261
  3. package/bin/wl-skills.js +796 -531
  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/reports//350/247/204/350/214/203/345/256/241/346/237/245/346/212/245/345/221/212.md +21 -0
  14. package/files/.github/skills/_compat/headers/cursor-mdc.txt +1 -1
  15. package/files/.github/skills/_compat/headers/kiro.txt +1 -1
  16. package/files/.github/skills/_compat/headers/trae.txt +1 -1
  17. package/files/.github/skills/_pipeline.md +91 -0
  18. package/files/.github/skills/_registry.md +4 -2
  19. package/files/.github/skills/core/convention-audit/SKILL.md +14 -1
  20. package/files/.github/skills/core/page-codegen/SKILL.md +3 -3
  21. package/files/.github/skills/core/page-codegen/USAGE.md +1 -1
  22. package/files/.github/skills/core/page-codegen/templates/domains/_CONTRIBUTING.md +1 -1
  23. package/files/.github/skills/core/template-extract/SKILL.md +1 -1
  24. package/files/.github/skills/sync/env.local.json +20 -18
  25. package/files/.github/standards/index.md +2 -2
  26. package/mcp/server.js +411 -330
  27. package/mcp/tools/projectTools.js +228 -0
  28. package/package.json +41 -40
@@ -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.3.8",
4
- "description": "AI Skill 模板包 v2.3.8 — 13 条编码规范 + 8 个 AI Skill,一条命令导入 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
- "README.md",
14
- "CHANGELOG.md"
15
- ],
16
- "keywords": [
17
- "ai",
18
- "skills",
19
- "copilot",
20
- "vue3",
21
- "template",
22
- "codegen",
23
- "cursor",
24
- "windsurf",
25
- "kiro",
26
- "agents"
27
- ],
28
- "author": "JHLC Frontend Team",
29
- "license": "UNLICENSED",
30
- "repository": {
31
- "type": "git",
32
- "url": "git+https://github.com/ChenyCHENYU/wl-skills-kit.git"
33
- },
34
- "engines": {
35
- "node": ">=16.0.0"
36
- },
37
- "devDependencies": {
38
- "xlsx": "^0.18.5"
39
- }
40
- }
1
+ {
2
+ "name": "@agile-team/wl-skills-kit",
3
+ "version": "2.4.1",
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
+ }
41
+ }