moluoxixi-ai-rules 0.1.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 (49) hide show
  1. package/LICENSE +21 -0
  2. package/README-zh.md +246 -0
  3. package/README.md +246 -0
  4. package/dist/constants/hosts.d.ts +34 -0
  5. package/dist/constants/hosts.js +71 -0
  6. package/dist/constants/skills.d.ts +91 -0
  7. package/dist/constants/skills.js +156 -0
  8. package/dist/scripts/cli.d.ts +2 -0
  9. package/dist/scripts/cli.js +145 -0
  10. package/dist/scripts/host-setup.d.ts +2 -0
  11. package/dist/scripts/host-setup.js +147 -0
  12. package/dist/scripts/lib/install.d.ts +78 -0
  13. package/dist/scripts/lib/install.js +367 -0
  14. package/dist/scripts/lib/links.d.ts +8 -0
  15. package/dist/scripts/lib/links.js +18 -0
  16. package/dist/scripts/lib/skill-projection.d.ts +14 -0
  17. package/dist/scripts/lib/skill-projection.js +54 -0
  18. package/dist/scripts/lib/tool.d.ts +40 -0
  19. package/dist/scripts/lib/tool.js +125 -0
  20. package/dist/scripts/lib/vendor-sync.d.ts +3 -0
  21. package/dist/scripts/lib/vendor-sync.js +129 -0
  22. package/dist/scripts/lib/vendors.d.ts +29 -0
  23. package/dist/scripts/lib/vendors.js +155 -0
  24. package/dist/scripts/lib/verify.d.ts +7 -0
  25. package/dist/scripts/lib/verify.js +84 -0
  26. package/dist/scripts/sync-vendors.d.ts +1 -0
  27. package/dist/scripts/sync-vendors.js +173 -0
  28. package/dist/scripts/verify-host.d.ts +2 -0
  29. package/dist/scripts/verify-host.js +42 -0
  30. package/package.json +60 -0
  31. package/rules/AGENTS.md +30 -0
  32. package/skills/skill-validation-standard/SKILL.md +45 -0
  33. package/skills/skill-validation-standard/__test__/verify-rules.test.ts +108 -0
  34. package/skills/skill-validation-standard/scripts/verify-rules.mjs +186 -0
  35. package/skills/workflow/frontend-code-standard/SKILL.md +58 -0
  36. package/skills/workflow/frontend-code-standard/__test__/verify-rules.test.ts +227 -0
  37. package/skills/workflow/frontend-code-standard/scripts/verify-rules.mjs +537 -0
  38. package/skills/workflow/java-code-standard/SKILL.md +58 -0
  39. package/skills/workflow/java-code-standard/__test__/verify-rules.test.ts +48 -0
  40. package/skills/workflow/java-code-standard/scripts/verify-rules.mjs +215 -0
  41. package/skills/workflow/nestjs-code-standard/SKILL.md +61 -0
  42. package/skills/workflow/nestjs-code-standard/__test__/verify-rules.test.ts +48 -0
  43. package/skills/workflow/nestjs-code-standard/scripts/verify-rules.mjs +219 -0
  44. package/skills/workflow/node-code-standard/SKILL.md +62 -0
  45. package/skills/workflow/node-code-standard/__test__/verify-rules.test.ts +48 -0
  46. package/skills/workflow/node-code-standard/scripts/verify-rules.mjs +213 -0
  47. package/skills/workflow/project-knowledge-read-workflow/SKILL.md +102 -0
  48. package/skills/workflow/project-knowledge-refresh-workflow/SKILL.md +149 -0
  49. package/skills/workflow/software-development-workflow/SKILL.md +85 -0
@@ -0,0 +1,537 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const skillRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
7
+ const PUBLIC_ENTRY_FILENAMES = ['index.ts']
8
+ const STYLE_ENTRY_FILENAMES = ['index.css', 'index.scss', 'index.less']
9
+ const COMPONENT_IMPLEMENTATION_FILENAMES = ['index.vue', 'index.tsx']
10
+ const MODULE_IMPLEMENTATION_FILENAMES = ['index.vue', 'index.tsx']
11
+ const HOST_FRAMEWORK_DEPENDENCIES = ['vue', 'react', 'react-dom']
12
+ const MAX_DEPTH = 10
13
+ const IGNORED_DIRECTORIES = [
14
+ 'node_modules',
15
+ '.git',
16
+ 'dist',
17
+ 'build',
18
+ 'coverage',
19
+ 'generated',
20
+ '.nuxt',
21
+ '.output',
22
+ '.next',
23
+ '.turbo',
24
+ '__test__',
25
+ '__mocks__',
26
+ '__fixtures__',
27
+ '__snapshots__',
28
+ '__demos__',
29
+ '__stories__',
30
+ '__e2e__',
31
+ 'assets',
32
+ 'images',
33
+ 'icons',
34
+ 'fonts',
35
+ 'styles',
36
+ 'public',
37
+ ]
38
+
39
+ function readSkillFile(...segments) {
40
+ return fs.readFileSync(path.join(skillRoot, ...segments), 'utf8')
41
+ }
42
+
43
+ function assertContains(content, pattern, message) {
44
+ if (!pattern.test(content))
45
+ throw new Error(message)
46
+ }
47
+
48
+ function findExistingFiles(directory, filenames) {
49
+ return filenames.filter(filename => fs.existsSync(path.join(directory, filename)))
50
+ }
51
+
52
+ function relativeDirectory(root, directory) {
53
+ const relative = path.relative(root, directory)
54
+
55
+ return relative === '' ? '.' : relative.split(path.sep).join('/')
56
+ }
57
+
58
+ function assertSingleExistingFile(directory, filenames, label) {
59
+ const existingFiles = findExistingFiles(directory, filenames)
60
+
61
+ if (existingFiles.length === 0)
62
+ throw new Error(`${label} 缺少唯一入口:${filenames.join('、')}`)
63
+
64
+ if (existingFiles.length > 1)
65
+ throw new Error(`${label} 只能存在一个入口:${filenames.join('、')}`)
66
+
67
+ return existingFiles[0]
68
+ }
69
+
70
+ function assertExplicitValueExports(filePath, label) {
71
+ const content = fs.readFileSync(filePath, 'utf8')
72
+ const wildcardExportLine = content
73
+ .split('\n')
74
+ .map(line => line.trimStart())
75
+ .find(line => line.startsWith('export *'))
76
+
77
+ if (wildcardExportLine)
78
+ throw new Error(`${label} 严禁使用 value wildcard export,请改为显式命名导出或 export type *`)
79
+ }
80
+
81
+ function assertEntryContract(directory, entryFilename, label) {
82
+ assertExplicitValueExports(path.join(directory, entryFilename), label)
83
+ }
84
+
85
+ // 组件包的类型门面属于公共 API 契约,必须由根入口显式暴露。
86
+ function assertComponentTypeFacade(componentRoot, srcPath) {
87
+ const typesPath = path.join(srcPath, 'types')
88
+
89
+ if (!fs.existsSync(typesPath) || !fs.statSync(typesPath).isDirectory())
90
+ throw new Error('组件包缺少 src/types/ 类型契约目录')
91
+
92
+ const typeEntry = assertSingleExistingFile(typesPath, PUBLIC_ENTRY_FILENAMES, '组件包 src/types 类型门面')
93
+ const rootEntryContent = fs.readFileSync(path.join(componentRoot, 'index.ts'), 'utf8')
94
+
95
+ assertEntryContract(typesPath, typeEntry, '组件包 src/types 类型门面')
96
+
97
+ if (!rootEntryContent.includes('export type') || !rootEntryContent.includes('./src/types'))
98
+ throw new Error('组件包根目录 index.ts 必须通过 export type * from \'./src/types\' 暴露类型契约')
99
+
100
+ return typeEntry
101
+ }
102
+
103
+ function readPackageJson(directory, label) {
104
+ const packagePath = path.join(directory, 'package.json')
105
+
106
+ if (!fs.existsSync(packagePath))
107
+ throw new Error(`${label} 缺少 package.json`)
108
+
109
+ return JSON.parse(fs.readFileSync(packagePath, 'utf8'))
110
+ }
111
+
112
+ function assertHostFrameworkDepsInPeerDependencies(packageJson, label) {
113
+ const dependencies = packageJson.dependencies ?? {}
114
+
115
+ for (const dependencyName of HOST_FRAMEWORK_DEPENDENCIES) {
116
+ if (Object.hasOwn(dependencies, dependencyName))
117
+ throw new Error(`${label} 宿主框架依赖 ${dependencyName} 必须放在 peerDependencies 中`)
118
+ }
119
+ }
120
+
121
+ function printPass(message, details = {}) {
122
+ console.log(`PASS ${message}`)
123
+
124
+ for (const [key, value] of Object.entries(details))
125
+ console.log(`${key}: ${value}`)
126
+ }
127
+
128
+ function getOption(args, name) {
129
+ const index = args.indexOf(name)
130
+
131
+ if (index === -1)
132
+ throw new Error(`缺少参数 ${name}`)
133
+
134
+ const value = args[index + 1]
135
+
136
+ if (!value || value.startsWith('--'))
137
+ throw new Error(`参数 ${name} 必须提供值`)
138
+
139
+ return value
140
+ }
141
+
142
+ function getListAfter(args, name, options = {}) {
143
+ const index = args.indexOf(name)
144
+
145
+ if (index === -1)
146
+ throw new Error(`缺少参数 ${name}`)
147
+
148
+ const values = args.slice(index + 1).filter(value => !value.startsWith('--'))
149
+
150
+ if (values.length === 2 && options.allowStableTwoUse)
151
+ return values
152
+
153
+ if (values.length < 3)
154
+ throw new Error(`${name} 至少需要 3 个明确使用点;仅 2 个使用点时必须显式传入 --stable-two-use,表示这是复杂且稳定的拆分例外`)
155
+
156
+ return values
157
+ }
158
+
159
+ function pathLooksLikeFile(inputPath) {
160
+ return path.extname(inputPath) !== ''
161
+ }
162
+
163
+ function normalizeDirectory(inputPath) {
164
+ const resolved = path.resolve(process.cwd(), inputPath)
165
+
166
+ return pathLooksLikeFile(resolved) ? path.dirname(resolved) : resolved
167
+ }
168
+
169
+ function nearestCommonAncestor(paths) {
170
+ const partsList = paths.map(inputPath => normalizeDirectory(inputPath).split(path.sep))
171
+ const [firstParts] = partsList
172
+ const commonParts = []
173
+
174
+ for (const [index, part] of firstParts.entries()) {
175
+ if (!partsList.every(parts => parts[index] === part))
176
+ break
177
+
178
+ commonParts.push(part)
179
+ }
180
+
181
+ if (commonParts.length === 0)
182
+ throw new Error('无法计算共享边界,请确认使用点路径属于同一文件系统根')
183
+
184
+ return commonParts.join(path.sep)
185
+ }
186
+
187
+ function assertTargetInsideAncestor(target, ancestor, uses) {
188
+ const resolvedTarget = path.resolve(process.cwd(), target)
189
+ const relative = path.relative(ancestor, resolvedTarget)
190
+
191
+ if (relative.startsWith('..') || path.isAbsolute(relative))
192
+ throw new Error(`抽离目标必须位于共享边界内:${ancestor}`)
193
+
194
+ if (relative === '')
195
+ return
196
+
197
+ const directSegments = relative.split(path.sep).filter(Boolean)
198
+
199
+ if (uses.some((usePath) => {
200
+ const useRelative = path.relative(ancestor, normalizeDirectory(usePath))
201
+ const [firstSegment] = useRelative.split(path.sep).filter(Boolean)
202
+
203
+ return firstSegment === directSegments[0]
204
+ })) {
205
+ throw new Error(`抽离目标必须位于允许的共享边界目录:${ancestor}`)
206
+ }
207
+
208
+ if (directSegments.length === 1)
209
+ return
210
+
211
+ throw new Error(`抽离目标必须位于允许的共享边界目录:${ancestor}`)
212
+ }
213
+
214
+ function assertCodeDirectoryEntries(directory, root, options = {}, depth = 0) {
215
+ if (depth > MAX_DEPTH)
216
+ throw new Error(`目录深度超过 ${MAX_DEPTH} 层,可能存在循环引用或目录结构异常`)
217
+
218
+ const entries = fs.readdirSync(directory, { withFileTypes: true })
219
+ const parentHasPublicEntry = findExistingFiles(directory, PUBLIC_ENTRY_FILENAMES).length > 0
220
+ const implementationEntries = options.implementationEntries ?? COMPONENT_IMPLEMENTATION_FILENAMES
221
+
222
+ for (const entry of entries) {
223
+ if (!entry.isDirectory())
224
+ continue
225
+
226
+ if (IGNORED_DIRECTORIES.includes(entry.name))
227
+ continue
228
+
229
+ const childDirectory = path.join(directory, entry.name)
230
+ const relative = relativeDirectory(root, childDirectory)
231
+ const childPublicEntries = findExistingFiles(childDirectory, PUBLIC_ENTRY_FILENAMES)
232
+ const childImplementationEntries = findExistingFiles(childDirectory, implementationEntries)
233
+ const isImplementationSrc = entry.name === 'src' && parentHasPublicEntry && childImplementationEntries.length === 1
234
+
235
+ if (!isImplementationSrc) {
236
+ const childEntry = assertSingleExistingFile(childDirectory, PUBLIC_ENTRY_FILENAMES, `目录 ${relative}/ 聚合入口`)
237
+ assertEntryContract(childDirectory, childEntry, `目录 ${relative}/ 聚合入口`)
238
+ }
239
+
240
+ if (childPublicEntries.length > 0 || isImplementationSrc || options.descendIntoMissingEntryDirectory)
241
+ assertCodeDirectoryEntries(childDirectory, root, options, depth + 1)
242
+ }
243
+ }
244
+
245
+ function assertComponentPackage(root, options = {}) {
246
+ const componentRoot = path.resolve(process.cwd(), root)
247
+ const readmePath = path.join(componentRoot, 'README.md')
248
+ const srcPath = path.join(componentRoot, 'src')
249
+ const publicEntry = assertSingleExistingFile(componentRoot, PUBLIC_ENTRY_FILENAMES, '复杂组件包根目录公共入口')
250
+ const rootImplementationEntries = findExistingFiles(componentRoot, COMPONENT_IMPLEMENTATION_FILENAMES)
251
+ const requiresReadme = !options.privatePackage
252
+
253
+ assertEntryContract(componentRoot, publicEntry, '复杂组件包根目录公共入口')
254
+
255
+ if (requiresReadme && !fs.existsSync(readmePath))
256
+ throw new Error('独立公共组件包根目录缺少 README.md')
257
+
258
+ if (!fs.existsSync(srcPath) || !fs.statSync(srcPath).isDirectory())
259
+ throw new Error('复杂组件包根目录缺少 src/ 实现目录')
260
+
261
+ if (rootImplementationEntries.length > 0)
262
+ throw new Error(`复杂组件包根目录不得放置实现入口:${rootImplementationEntries.join('、')}`)
263
+
264
+ const srcImplementationEntry = assertSingleExistingFile(srcPath, COMPONENT_IMPLEMENTATION_FILENAMES, '复杂组件包 src/ 实现入口')
265
+ const typeEntry = assertComponentTypeFacade(componentRoot, srcPath)
266
+
267
+ if (fs.existsSync(readmePath)) {
268
+ const readme = fs.readFileSync(readmePath, 'utf8').trim()
269
+
270
+ if (readme.length === 0)
271
+ throw new Error('复杂组件 README.md 不得为空,必须描述组件如何使用')
272
+
273
+ if (!/(使用|用法|Usage|Props|Events|Emits|Expose|Slots|API|Ref|Children)/.test(readme))
274
+ throw new Error('复杂组件 README.md 必须包含使用方式或接口契约说明')
275
+ }
276
+
277
+ assertCodeDirectoryEntries(componentRoot, componentRoot, { implementationEntries: COMPONENT_IMPLEMENTATION_FILENAMES })
278
+
279
+ printPass(options.privatePackage ? 'frontend private complex component package structure is valid' : 'frontend complex component package structure is valid', {
280
+ componentRoot,
281
+ entry: publicEntry,
282
+ implementationEntry: srcImplementationEntry,
283
+ typeEntry,
284
+ })
285
+ }
286
+
287
+ function assertLibraryPackage(root, options = {}) {
288
+ const libraryRoot = path.resolve(process.cwd(), root)
289
+ const readmePath = path.join(libraryRoot, 'README.md')
290
+ const srcPath = path.join(libraryRoot, 'src')
291
+ const publicEntry = assertSingleExistingFile(libraryRoot, PUBLIC_ENTRY_FILENAMES, '库根目录公共入口')
292
+ const rootImplementationEntries = findExistingFiles(libraryRoot, COMPONENT_IMPLEMENTATION_FILENAMES)
293
+ const packageJson = readPackageJson(libraryRoot, '库根目录')
294
+
295
+ if (!fs.existsSync(readmePath))
296
+ throw new Error('库根目录缺少 README.md')
297
+
298
+ if (!fs.existsSync(srcPath) || !fs.statSync(srcPath).isDirectory())
299
+ throw new Error('库根目录缺少 src/ 实现目录')
300
+
301
+ if (rootImplementationEntries.length > 0)
302
+ throw new Error(`库根目录不得放置组件实现入口:${rootImplementationEntries.join('、')}`)
303
+
304
+ const srcEntry = assertSingleExistingFile(srcPath, PUBLIC_ENTRY_FILENAMES, '库 src/ 聚合入口')
305
+
306
+ assertEntryContract(libraryRoot, publicEntry, '库根目录公共入口')
307
+ assertEntryContract(srcPath, srcEntry, '库 src/ 聚合入口')
308
+ assertHostFrameworkDepsInPeerDependencies(packageJson, '库 package.json')
309
+
310
+ if (options.requireComponents) {
311
+ if (!Object.hasOwn(packageJson, 'sideEffects'))
312
+ throw new Error('UI 组件库 package.json 必须明确声明 sideEffects 范围')
313
+ }
314
+ else if (packageJson.sideEffects !== false) {
315
+ throw new Error('工具库 package.json 必须声明 "sideEffects": false')
316
+ }
317
+
318
+ const readme = fs.readFileSync(readmePath, 'utf8').trim()
319
+
320
+ if (readme.length === 0)
321
+ throw new Error('库 README.md 不得为空,必须描述库如何使用')
322
+
323
+ if (!/(使用|用法|Usage|API|Exports|组件|工具|安装|Install)/.test(readme))
324
+ throw new Error('库 README.md 必须包含使用方式或公开 API 说明')
325
+
326
+ if (options.requireComponents) {
327
+ const componentsPath = path.join(srcPath, 'components')
328
+
329
+ if (!fs.existsSync(componentsPath) || !fs.statSync(componentsPath).isDirectory())
330
+ throw new Error('UI 组件库缺少 src/components/ 组件目录')
331
+
332
+ const componentDirs = fs.readdirSync(componentsPath, { withFileTypes: true })
333
+ .filter(entry => entry.isDirectory())
334
+
335
+ if (componentDirs.length === 0)
336
+ throw new Error('UI 组件库 src/components/ 下至少需要一个复杂组件包')
337
+
338
+ for (const componentDir of componentDirs)
339
+ assertComponentPackage(path.join(componentsPath, componentDir.name))
340
+ }
341
+
342
+ assertCodeDirectoryEntries(libraryRoot, libraryRoot, { implementationEntries: COMPONENT_IMPLEMENTATION_FILENAMES })
343
+
344
+ printPass(options.requireComponents ? 'frontend UI component library structure is valid' : 'frontend utility library structure is valid', {
345
+ libraryRoot,
346
+ entry: publicEntry,
347
+ srcEntry,
348
+ })
349
+ }
350
+
351
+ function assertModule(root) {
352
+ const moduleRoot = path.resolve(process.cwd(), root)
353
+ const srcPath = path.join(moduleRoot, 'src')
354
+ const implementationEntry = assertSingleExistingFile(moduleRoot, MODULE_IMPLEMENTATION_FILENAMES, '单个模块根目录实现入口')
355
+ const publicEntries = findExistingFiles(moduleRoot, PUBLIC_ENTRY_FILENAMES)
356
+
357
+ if (fs.existsSync(srcPath))
358
+ throw new Error('单个模块不得再嵌套 src/ 目录')
359
+
360
+ if (publicEntries.length > 0)
361
+ throw new Error(`单个模块根目录不得创建公共入口:${publicEntries.join('、')}`)
362
+
363
+ assertCodeDirectoryEntries(moduleRoot, moduleRoot, { implementationEntries: MODULE_IMPLEMENTATION_FILENAMES })
364
+
365
+ printPass('frontend module structure is valid', {
366
+ moduleRoot,
367
+ implementationEntry,
368
+ })
369
+ }
370
+
371
+ // 私有叶子例外只校验文件边界;源码级 API 识别应交给成熟 SFC/AST 工具。
372
+ function assertPrivateLeafComponent(root) {
373
+ const componentPath = path.resolve(process.cwd(), root)
374
+ const filename = path.basename(componentPath)
375
+ const componentDirectory = path.dirname(componentPath)
376
+ const componentName = path.basename(filename, path.extname(filename))
377
+
378
+ if (!/^[A-Z][\w-]*\.(vue|tsx)$/.test(filename))
379
+ throw new Error('私有叶子组件例外必须使用 ComponentName.vue 或 ComponentName.tsx 文件')
380
+
381
+ if (!fs.existsSync(componentPath) || !fs.statSync(componentPath).isFile())
382
+ throw new Error('私有叶子组件例外路径必须指向真实文件')
383
+
384
+ for (const entry of fs.readdirSync(componentDirectory, { withFileTypes: true })) {
385
+ if (entry.name === filename)
386
+ continue
387
+
388
+ if (entry.isDirectory()) {
389
+ if (!['__test__', '__demos__', '__stories__'].includes(entry.name))
390
+ throw new Error(`私有叶子组件例外同级只允许 __test__、__demos__、__stories__ 目录;发现:${entry.name},必须升级为 component-package`)
391
+
392
+ continue
393
+ }
394
+
395
+ const siblingExt = path.extname(entry.name)
396
+ const siblingBase = path.basename(entry.name, siblingExt)
397
+ const isSameNameStyle = siblingBase === componentName && STYLE_ENTRY_FILENAMES.includes(`index${siblingExt}`)
398
+ const isAnotherSimpleComponent = /^[A-Z][\w-]*\.(vue|tsx)$/.test(entry.name)
399
+ const isDedicatedCodeFile = /\.(ts|js|vue|tsx|jsx)$/.test(entry.name)
400
+
401
+ if (!isSameNameStyle && !isAnotherSimpleComponent && isDedicatedCodeFile)
402
+ throw new Error(`私有叶子组件例外同级存在专属附属文件:${entry.name},必须升级为 component-package`)
403
+ }
404
+
405
+ printPass('frontend private leaf component exception is valid', {
406
+ componentPath,
407
+ })
408
+ }
409
+
410
+ function printHelp() {
411
+ console.log(`用法: node verify-rules.mjs [command] [options]
412
+
413
+ 命令:
414
+ self 校验本 skill 的规则完整性(默认)
415
+ leaf, private-leaf 校验私有叶子组件例外
416
+ component, package 校验复杂组件包结构
417
+ module 校验业务模块结构
418
+ utility, tool-library 校验工具库结构
419
+ ui-library, component-library 校验 UI 组件库结构
420
+ hoist 校验公共代码抽离位置是否落在允许的共享边界内
421
+ --help 显示帮助信息
422
+
423
+ 选项:
424
+ --root <path> 指定组件、模块或库根目录
425
+ --private 将复杂组件包按模块或组件内部私有子组件校验,README.md 不强制
426
+ --target <path> 指定抽离目标目录
427
+ --uses <path1> <path2> ... 指定至少 3 个使用点路径
428
+ --stable-two-use 仅 2 个使用点时,声明这是复杂且稳定的拆分例外
429
+ `)
430
+ }
431
+
432
+ function verifySelf() {
433
+ const skill = readSkillFile('SKILL.md')
434
+
435
+ assertContains(skill, /frontend-code-standard/, 'SKILL.md 必须声明 skill 名称')
436
+ assertContains(skill, /触发时机:当用户要求新建、编写、重构、拆分前端组件、业务模块、工具函数或类型声明时触发/, 'SKILL.md 必须声明用户指定的触发时机')
437
+ assertContains(skill, /目录门面模式(Facade)、禁止深度导入、规范 src\/ 隔离边界、强制同步交付测试用例,并应用 Vue 3\.5\+ 现代语法/, 'SKILL.md 必须说明规则覆盖范围')
438
+ assertContains(skill, /前端工程架构与代码规范/, 'SKILL.md 必须使用前端工程架构规范标题')
439
+ assertContains(skill, /适用边界/, 'SKILL.md 必须覆盖适用边界')
440
+ assertContains(skill, /项目事实优先/, 'SKILL.md 必须声明项目事实优先')
441
+ assertContains(skill, /Vue 3\.5\+ 约束范围/, 'SKILL.md 必须声明 Vue 3.5+ 约束范围')
442
+ assertContains(skill, /业务路由模块例外/, 'SKILL.md 必须覆盖业务路由模块例外')
443
+ assertContains(skill, /严禁为了制造层级再包一层 `src\/`/, 'SKILL.md 必须禁止路由模块无意义 src 层')
444
+ assertContains(skill, /门面模式与统一入口/, 'SKILL.md 必须覆盖门面模式')
445
+ assertContains(skill, /职责目录下必须存在 `index\.ts`/, 'SKILL.md 必须覆盖职责目录统一入口')
446
+ assertContains(skill, /禁止深度导入/, 'SKILL.md 必须覆盖禁止深度导入')
447
+ assertContains(skill, /import \{ format \} from '@\/utils\/string\/format'/, 'SKILL.md 必须包含 Deep Import 反例')
448
+ assertContains(skill, /import \{ format \} from '@\/utils'/, 'SKILL.md 必须包含门面导入示例')
449
+ assertContains(skill, /同一职责目录内部允许通过相对路径引用私有实现文件/, 'SKILL.md 必须覆盖内部私有实现导入')
450
+ assertContains(skill, /export type \* from '\.\/xxx'/, 'SKILL.md 必须覆盖类型聚合')
451
+ assertContains(skill, /禁止用无差别 `export \*` 暴露内部实现/, 'SKILL.md 必须覆盖值导出约束')
452
+ assertContains(skill, /门面例外/, 'SKILL.md 必须覆盖门面例外')
453
+ assertContains(skill, /独立模块与组件目录骨架/, 'SKILL.md 必须覆盖独立模块骨架')
454
+ assertContains(skill, /公共契约层(根目录)/, 'SKILL.md 必须覆盖公共契约层')
455
+ assertContains(skill, /对外生产 API 只能通过唯一的根 `index\.ts` 暴露/, 'SKILL.md 必须覆盖根入口唯一出口')
456
+ assertContains(skill, /私有实现层 \(`src\/`\)/, 'SKILL.md 必须覆盖 src 私有实现层')
457
+ assertContains(skill, /所有具体逻辑、组件、样式代码必须收敛在 `src\/` 内部/, 'SKILL.md 必须覆盖 src 隔离边界')
458
+ assertContains(skill, /类型契约层/, 'SKILL.md 必须覆盖类型契约层')
459
+ assertContains(skill, /export type \* from '\.\/src\/types'/, 'SKILL.md 必须覆盖根类型门面导出')
460
+ assertContains(skill, /越界调用拦截/, 'SKILL.md 必须覆盖越界调用拦截')
461
+ assertContains(skill, /绝对禁止任何直接读取其 `src\/` 内部文件的行为/, 'SKILL.md 必须禁止外部读取 src')
462
+ assertContains(skill, /私有叶子例外/, 'SKILL.md 必须覆盖私有叶子例外')
463
+ assertContains(skill, /必须立即升级为独立目录骨架/, 'SKILL.md 必须覆盖叶子组件升级条件')
464
+ assertContains(skill, /强制测试交付要求/, 'SKILL.md 必须覆盖测试交付')
465
+ assertContains(skill, /不得留作后续任务/, 'SKILL.md 必须禁止测试延期')
466
+ assertContains(skill, /验证状态必须标记为 `MISSING`/, 'SKILL.md 必须覆盖缺失验证入口状态')
467
+ assertContains(skill, /同级的 `__test__\/` 目录/, 'SKILL.md 必须覆盖就近测试目录')
468
+ assertContains(skill, /项目根级 `__e2e__\/`/, 'SKILL.md 必须覆盖全局 E2E 目录')
469
+ assertContains(skill, /Vitest/, 'SKILL.md 必须覆盖 Vitest')
470
+ assertContains(skill, /Playwright/, 'SKILL.md 必须覆盖 Playwright')
471
+ assertContains(skill, /禁止占位断言/, 'SKILL.md 必须覆盖禁止占位断言')
472
+ assertContains(skill, /Props、Events\/Emits、Slots\/Children、Model、Expose\/Ref/, 'SKILL.md 必须覆盖实际契约测试边界')
473
+ assertContains(skill, /现代 Vue 语法强制红线/, 'SKILL.md 必须覆盖现代 Vue 语法红线')
474
+ assertContains(skill, /defineModel/, 'SKILL.md 必须覆盖 defineModel')
475
+ assertContains(skill, /emit\('update:xxx'\)/, 'SKILL.md 必须覆盖旧式 model 写法禁令')
476
+ assertContains(skill, /非 model 事件仍使用 `defineEmits`/, 'SKILL.md 必须保留非 model 事件语义')
477
+ assertContains(skill, /useTemplateRef/, 'SKILL.md 必须覆盖 useTemplateRef')
478
+ assertContains(skill, /defineProps/, 'SKILL.md 必须覆盖 defineProps')
479
+ assertContains(skill, /withDefaults/, 'SKILL.md 必须覆盖 withDefaults')
480
+ assertContains(skill, /交付状态/, 'SKILL.md 必须覆盖交付状态')
481
+ assertContains(skill, /按任务风险执行项目已有的 `lint`、`typecheck`、`test`、`build` 或浏览器验证/, 'SKILL.md 必须覆盖验证命令范围')
482
+ assertContains(skill, /FAIL > MISSING > NOT RUN > PASS/, 'SKILL.md 必须覆盖最终状态优先级')
483
+ printPass('frontend-code-standard self rules are valid')
484
+ }
485
+
486
+ function verifyHoist(args) {
487
+ const target = getOption(args, '--target')
488
+ const uses = getListAfter(args, '--uses', {
489
+ allowStableTwoUse: args.includes('--stable-two-use'),
490
+ })
491
+ const ancestor = nearestCommonAncestor(uses)
492
+
493
+ assertTargetInsideAncestor(target, ancestor, uses)
494
+
495
+ printPass('frontend hoist target stays within shared boundary', {
496
+ sharedBoundary: ancestor,
497
+ target: path.resolve(process.cwd(), target),
498
+ })
499
+ }
500
+
501
+ function main() {
502
+ const [command = 'self', ...args] = process.argv.slice(2)
503
+
504
+ if (command === '--help' || command === '-h')
505
+ return printHelp()
506
+
507
+ if (command === 'self')
508
+ return verifySelf()
509
+
510
+ if (command === 'leaf' || command === 'private-leaf')
511
+ return assertPrivateLeafComponent(getOption(args, '--root'))
512
+
513
+ if (command === 'component' || command === 'package' || command === 'project')
514
+ return assertComponentPackage(getOption(args, '--root'), { privatePackage: args.includes('--private') })
515
+
516
+ if (command === 'module')
517
+ return assertModule(getOption(args, '--root'))
518
+
519
+ if (command === 'utility' || command === 'tool-library')
520
+ return assertLibraryPackage(getOption(args, '--root'))
521
+
522
+ if (command === 'ui-library' || command === 'component-library')
523
+ return assertLibraryPackage(getOption(args, '--root'), { requireComponents: true })
524
+
525
+ if (command === 'hoist')
526
+ return verifyHoist(args)
527
+
528
+ throw new Error(`未知命令:${command},使用 --help 查看帮助`)
529
+ }
530
+
531
+ try {
532
+ main()
533
+ }
534
+ catch (error) {
535
+ console.error(`FAIL ${error instanceof Error ? error.message : String(error)}`)
536
+ process.exitCode = 1
537
+ }
@@ -0,0 +1,58 @@
1
+ ---
2
+ name: java-code-standard
3
+ description: 触发时机:当用户要求新建、编写、重构、拆分、优化、评审或校验 Java/Spring Boot 后端代码时触发。
4
+ ---
5
+
6
+ # Java & Spring Boot 工程架构与代码规范
7
+
8
+ 在执行 Java 后端代码生成、重构或评审任务时,必须在当前任务目标与改动范围内严格遵守以下物理边界与编码红线。本文件是唯一规则源,绝不为了兼容目标范围内的历史旧代码而保留过渡层或伪分层。
9
+
10
+ ## 一、核心架构与设计纪律
11
+
12
+ - **依赖倒置**:`domain` 位于最核心,完全独立。严禁 `domain` 依赖 `infrastructure`、`org.springframework`、`jakarta.persistence`、Servlet、Jackson 或任何技术细节。若现有项目将 JPA Entity、ORM 注解或持久化代理放入 `domain`,必须在当前任务范围内拆分领域模型与持久化实体。
13
+ - **高内聚与反垃圾桶**:类与文件必须保持职责内聚。辅助逻辑(Helper/Mapper)必须就近留在 feature 包内;绝对禁止设立无业务语义的全局 `utils` 目录。
14
+ - **不可变与时间契约**:对外暴露集合时强制使用不可变视图(如 `List.copyOf()` 或 `Collections.unmodifiableList()`)。全局禁用 `java.util.Date`、`Calendar` 与 `java.sql.Timestamp`,强制使用 `java.time` 包下的现代 API(如 `Instant`, `OffsetDateTime`)。
15
+ - **显式 DTO**:API 边界与用例交互必须使用显式 DTO(优先使用 `record`),严禁使用 `Map`、裸 `JSON` 或 `Object` 作为载体。
16
+
17
+ ## 二、Spring Boot 框架强制约束
18
+
19
+ - **依赖注入**:全局禁用 `@Autowired` 字段注入。所有依赖必须显式声明为 `final` 并通过构造器注入。
20
+ - **校验前置 (Fail-Fast)**:Controller 层的复杂 DTO、集合或入参,必须显式标注 `@Valid` 或 `@Validated`。嵌套 DTO 必须在父级字段标注以触发级联。进入 Application/Domain 层后,禁止重复编写基础防御逻辑(如空值、正则校验或 `.trim()`)。
21
+ - **事务红线 (Transaction Boundaries)**:
22
+ - **严禁自调用**:绝对禁止 `this.xxx()` 形式的内部事务方法调用,也禁止使用 `AopContext.currentProxy()` 绕过代理。拆分事务必须提取独立 Service 并通过注入调用。
23
+ - **读写分离标记**:涉及 Repository、EntityManager 或持久化上下文的查询类用例,必须显式标记 `@Transactional(readOnly = true)`,避免无意义的 Hibernate Dirty Checking。
24
+
25
+ ## 三、数据与持久化契约 (JPA/Hibernate)
26
+
27
+ - **禁用 OSIV**:必须显式配置 `spring.jpa.open-in-view: false`。懒加载必须收敛在 `@Transactional` 边界内,绝对禁止在 Controller 层或序列化阶段触发懒加载。
28
+ - **杜绝 N+1 查询**:严禁在循环体内执行 SQL 或触发懒加载代理。跨聚合查询强制使用 `@EntityGraph`、`JOIN FETCH` 或 DTO 投影(Projection)。
29
+ - **实体纯粹性**:JPA Entity 必须是普通 `class`。绝对禁止在 Entity 上使用 Lombok 的 `@Data`、`@EqualsAndHashCode`、`@ToString` 以防意外触发代理。必须基于稳定业务唯一键 (Business Key) 手写 `equals/hashCode`。
30
+ - **Repository 职责**:Repository 仅限持久化存取,严禁混入 HTTP 上下文、权限校验或跨域编排。
31
+
32
+ ## 四、强制验证与交付动作 (Mandatory Actions)
33
+
34
+ 任务完成后,必须自动执行以下操作,并严格按模板输出报告:
35
+
36
+ 1. **执行 Skill 自检脚本**:在 `skills/workflow/java-code-standard` 目录执行 `node scripts/verify-rules.mjs`。若当前上下文是目标 Java 项目且不存在该脚本,标记为 `MISSING`;不得用 Skill 自检脚本替代目标项目自身的 Maven/Gradle/lint/test。
37
+ 2. **执行项目质量检查**:优先运行当前项目已有的 Java 构建、测试或 lint 命令;缺少脚本、配置或依赖时标记为 `MISSING`,未执行时标记为 `NOT RUN`。
38
+ 3. **输出交付与评审报告**:严格使用以下 Markdown 格式输出结论,禁止任何套话:
39
+
40
+ ```markdown
41
+ ### 代码合规自校验报告
42
+ - **注入合规**:PASS / FAIL / MISSING / NOT RUN / N/A - 已彻底消除 `@Autowired` 字段注入。
43
+ - **校验合规**:PASS / FAIL / MISSING / NOT RUN / N/A - Request DTO/入口已全部覆盖 `@Valid` / `@Validated`。
44
+ - **事务合规**:PASS / FAIL / MISSING / NOT RUN / N/A - 不存在 `this.xxx()` 事务自调用,且只读方法已加 `readOnly = true`。
45
+ - **时间契约**:PASS / FAIL / MISSING / NOT RUN / N/A - 已全部使用 `java.time` API。
46
+ - **JPA 合规**:PASS / FAIL / MISSING / NOT RUN / N/A - Entity 未使用 `@Data`,且循环体内无查询逻辑。
47
+
48
+ ### 脚本执行结果 (Status: PASS / FAIL / MISSING / NOT RUN)
49
+ - `node scripts/verify-rules.mjs`:输出简述;无法执行时说明原因。
50
+ - 项目质量命令:输出简述;缺少命令时标记 `MISSING`。
51
+
52
+ ### 评审异常点
53
+ *(如无异常,填“无”)*
54
+ - **级别**:Critical / Major / Minor
55
+ - **文件与行号**:`xxx.java:41`
56
+ - **违规说明**:...
57
+ - **修复建议**:...
58
+ ```
@@ -0,0 +1,48 @@
1
+ import assert from 'node:assert/strict'
2
+ import { execFileSync, spawnSync } from 'node:child_process'
3
+ import path from 'node:path'
4
+ import process from 'node:process'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { it } from 'vitest'
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
+ const skillRoot = path.resolve(__dirname, '..')
10
+ const projectRoot = path.resolve(skillRoot, '..', '..', '..')
11
+ const scriptPath = path.join(skillRoot, 'scripts', 'verify-rules.mjs')
12
+
13
+ function runScript(...args: string[]) {
14
+ return execFileSync(process.execPath, [scriptPath, ...args], {
15
+ cwd: projectRoot,
16
+ encoding: 'utf8',
17
+ })
18
+ }
19
+
20
+ it('verify-rules 校验自检和领域提升扫描', () => {
21
+ assert.match(runScript(), /PASS java-code-standard self rules are valid/)
22
+ assert.match(
23
+ runScript(
24
+ 'hoist',
25
+ '--target',
26
+ 'src/main/java/com/example/order/shared',
27
+ '--uses',
28
+ 'src/main/java/com/example/order/create/CreateOrderService.java',
29
+ 'src/main/java/com/example/order/update/UpdateOrderService.java',
30
+ 'src/main/java/com/example/order/cancel/CancelOrderService.java',
31
+ ),
32
+ /PASS java hoist domain-boundary scan completed/,
33
+ )
34
+
35
+ const nestedHoistResult = spawnSync(process.execPath, [
36
+ scriptPath,
37
+ 'hoist',
38
+ '--target',
39
+ 'src/main/java/com/example/order/create/shared',
40
+ '--uses',
41
+ 'src/main/java/com/example/order/create/CreateOrderService.java',
42
+ 'src/main/java/com/example/order/update/UpdateOrderService.java',
43
+ 'src/main/java/com/example/order/cancel/CancelOrderService.java',
44
+ ], { cwd: projectRoot, encoding: 'utf8' })
45
+ assert.equal(nestedHoistResult.status, 1)
46
+ assert.match(nestedHoistResult.stderr, /FAIL \[HOIST_BOUNDARY_RISK\]/)
47
+ assert.doesNotMatch(nestedHoistResult.stdout, /PASS java hoist domain-boundary scan completed/)
48
+ })