create-dwy 0.19.1 → 0.20.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 +13 -0
- package/package.json +1 -1
- package/src/cli-help.js +28 -2
- package/src/sync-all.js +95 -583
- package/src/sync-codex.js +14 -7
- package/src/sync-opencode.js +8 -22
- package/src/sync.js +60 -149
- package/src/tab-multiselect.js +15 -53
- package/templates/ai-tools/CHANGELOG.md +195 -0
- package/templates/ai-tools/CLAUDE.md +39 -58
- package/templates/ai-tools/VERSION +1 -1
- package/templates/ai-tools/hooks/Git/pre-git-commit-ai-signature-check.sh +5 -4
- package/templates/ai-tools/rules/Android/dwy-android-core.md +147 -13
- package/templates/ai-tools/rules/Android/dwy-android-layering.md +142 -0
- package/templates/ai-tools/rules/Android/dwy-android-mvp.md +55 -0
- package/templates/ai-tools/rules/Android/dwy-android-support-only.md +2 -0
- package/templates/ai-tools/rules/Docker/dwy-docker.md +23 -1
- package/templates/ai-tools/rules/Flutter/dwy-flutter-core.md +2 -0
- package/templates/ai-tools/rules/Flutter/dwy-flutter-testing.md +1 -1
- package/templates/ai-tools/rules/HarmonyOS/dwy-arkts-ui-build.md +12 -8
- package/templates/ai-tools/rules/Python/dwy-python-backend.md +4 -43
- package/templates/ai-tools/rules/Python/dwy-python-layering.md +169 -0
- package/templates/ai-tools/rules/Vue/dwy-vue-core.md +11 -49
- package/templates/ai-tools/rules/Vue/dwy-vue-layering.md +153 -0
- package/templates/ai-tools/rules/iOS/dwy-apple-layering.md +133 -0
- package/templates/ai-tools/rules/iOS/dwy-swift-core.md +2 -0
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-code-craft.md +83 -0
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-dependency-freshness.md +27 -66
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-git-commit.md +8 -219
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-oss-commercial-license.md +27 -123
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-prod-branch.md +31 -0
- package/templates/ai-tools/rules//346/225/260/346/215/256/345/272/223/dwy-postgres.md +17 -10
- package/templates/ai-tools/rules//346/225/260/346/215/256/345/272/223/dwy-redis.md +6 -3
- package/templates/ai-tools/skills/Docker/dwy-deploy-first/SKILL.md +14 -14
- package/templates/ai-tools/skills/Docker/dwy-deploy-first/references/chapter-01-mirrors.md +2 -2
- package/templates/ai-tools/skills/Docker/dwy-docker/SKILL.md +21 -13
- package/templates/ai-tools/skills/Docker/dwy-docker/references/ask-templates.md +13 -12
- package/templates/ai-tools/skills/Docker/dwy-docker/references/templates.md +3 -1
- package/templates/ai-tools/skills/Docker/dwy-docker/references/version-rules.md +10 -6
- package/templates/ai-tools/skills/Docker/dwy-docker/scripts/query_dockerhub.py +21 -6
- package/templates/ai-tools/skills/Docker/dwy-docker/scripts/scan_images.sh +2 -2
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/SKILL.md +44 -30
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/references/dockerfile-snippets.md +2 -0
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/references/mirror-providers.md +8 -8
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/scripts/apply_mirrors.py +14 -1
- package/templates/ai-tools/skills/Docker/dwy-mirror-source/scripts/check_mirrors.py +4 -0
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/SKILL.md +8 -6
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/digest/python-sdk.md +3 -3
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/digest/stream-engine.md +2 -2
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/anti-patterns.md +3 -1
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/change-checklist.md +4 -4
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/lethal-violations.md +5 -5
- package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/review-rules/perf-baselines.md +5 -5
- package/templates/ai-tools/skills/Python/dwy-eapi/SKILL.md +2 -2
- package/templates/ai-tools/skills/Python/dwy-eapi/v0/breaking.md +3 -0
- package/templates/ai-tools/skills/Python/dwy-eapi/v0/email-canonical.md +28 -3
- package/templates/ai-tools/skills/Python/dwy-eapi/v0/index.md +2 -2
- package/templates/ai-tools/skills/Python/dwy-eapi/v0/providers-email.md +12 -2
- package/templates/ai-tools/skills/Vue/dwy-eui/references/eui-design-guide.md +1 -1
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-architecture/SKILL.md +11 -14
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-product-launcher/references/state-and-contract.md +1 -1
- package/templates/ai-tools/skills//344/272/247/345/223/2010/345/210/2601/dwy-prototype/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/SKILL.md +32 -15
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/references/electron-release-workflow-template.yml +3 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-github-action-publish/references/release-workflow-template.yml +8 -5
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/SKILL.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/assets/appstore-submission.yaml +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/aso.md +2 -0
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-ios-app-store-release/references/in-app-purchase-review.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +4 -3
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +45 -27
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/git-tag.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +1 -1
- package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +8 -0
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/SKILL.md +7 -7
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/references/checks-runtime.md +63 -59
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_base.sh +10 -1
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_capacity.sh +21 -23
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_db.sh +9 -1
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_docker.sh +20 -24
- package/templates/ai-tools/skills//345/256/211/345/205/250/dwy-deploy-audit/scripts/check_logs.sh +3 -3
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/SKILL.md +4 -2
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/references/api.md +1 -1
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/dwy-doubao-tts/scripts/synthesize.py +1 -0
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/SKILL.md +7 -5
- package/templates/ai-tools/skills//350/207/252/345/252/222/344/275/223/media-platform-packaging/references/platform-copy-examples.md +5 -3
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-dependency-choice/SKILL.md +140 -0
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-git-commit/SKILL.md +113 -0
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-shared/SKILL.md +16 -13
- package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-whiteboard-prototype/SKILL.md +1 -1
- package/src/sync-packs.js +0 -223
- package/src/sync-scene.js +0 -178
- package/templates/ai-tools/rules//345/274/200/345/217/221/346/265/201/347/250/213/dwy-tdd-development.md +0 -115
package/src/sync-packs.js
DELETED
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 按包勾选的包目录:技术栈包 + 场景包。
|
|
3
|
-
* 展开结果写入现有 skills/rules/hooks 选择,不改平台 sync 的按名增删。
|
|
4
|
-
* 条目按扫描结果的 category / name 匹配,模板里不存在的名字直接跳过。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** 技术栈包(含通用)。Tab 顺序:通用第一,Android、Vue 随后,其余保持原相对顺序。 */
|
|
8
|
-
export const STACK_PACKS = [
|
|
9
|
-
{
|
|
10
|
-
id: 'common',
|
|
11
|
-
label: '通用',
|
|
12
|
-
description: '开发流程 rules、Git hooks、跨栈 skill(dwy-shared / semver / 流程文档)',
|
|
13
|
-
ruleCategories: ['开发流程'],
|
|
14
|
-
hookCategories: ['Git'],
|
|
15
|
-
skillCategories: ['通用'],
|
|
16
|
-
skillNames: ['dwy-semver'],
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: 'android',
|
|
20
|
-
label: 'Android',
|
|
21
|
-
description: 'Android rules',
|
|
22
|
-
ruleCategories: ['Android'],
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: 'vue',
|
|
26
|
-
label: 'Vue',
|
|
27
|
-
description: 'Vue rules + dwy-eui / dwy-ekit / 全栈脚手架',
|
|
28
|
-
ruleCategories: ['Vue'],
|
|
29
|
-
skillCategories: ['Vue'],
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
id: 'python',
|
|
33
|
-
label: 'Python',
|
|
34
|
-
description: 'Python rules + dwy-eapi / 全栈脚手架',
|
|
35
|
-
ruleCategories: ['Python'],
|
|
36
|
-
skillCategories: ['Python'],
|
|
37
|
-
skillNames: ['dwy-fullstack-scaffold'],
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: 'ios',
|
|
41
|
-
label: 'iOS',
|
|
42
|
-
description: 'iOS / Swift rules。上架 skill 在场景包「发布发版」',
|
|
43
|
-
ruleCategories: ['iOS'],
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: 'flutter',
|
|
47
|
-
label: 'Flutter',
|
|
48
|
-
description: 'Flutter rules',
|
|
49
|
-
ruleCategories: ['Flutter'],
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
id: 'harmony',
|
|
53
|
-
label: 'HarmonyOS',
|
|
54
|
-
description: 'HarmonyOS / ArkTS rules',
|
|
55
|
-
ruleCategories: ['HarmonyOS'],
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
id: 'docker',
|
|
59
|
-
label: 'Docker / 运维',
|
|
60
|
-
description: 'Docker rule + 镜像/部署 skill',
|
|
61
|
-
ruleCategories: ['Docker'],
|
|
62
|
-
skillCategories: ['Docker'],
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
id: 'database',
|
|
66
|
-
label: '数据库',
|
|
67
|
-
description: 'Postgres / Redis / 迁移 rules。DolphinDB skill 在场景包',
|
|
68
|
-
ruleCategories: ['数据库'],
|
|
69
|
-
},
|
|
70
|
-
]
|
|
71
|
-
|
|
72
|
-
/** 场景包。和栈包同一屏 Tab,默认不预勾。 */
|
|
73
|
-
export const SCENE_PACKS = [
|
|
74
|
-
{
|
|
75
|
-
id: 'product-0to1',
|
|
76
|
-
label: '产品0到1',
|
|
77
|
-
description: '立项到上线的阶段 skill(launcher / PRD / 架构 / TDD…)',
|
|
78
|
-
skillCategories: ['产品0到1'],
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: 'media',
|
|
82
|
-
label: '自媒体',
|
|
83
|
-
description: '单位圆自媒体流水线 + 豆包配音',
|
|
84
|
-
skillCategories: ['自媒体'],
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
id: 'release',
|
|
88
|
-
label: '发布发版',
|
|
89
|
-
description: 'dwy-publish / App Store / SDK 检查 / semver / GitHub Action',
|
|
90
|
-
skillCategories: ['发布发版'],
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
id: 'security',
|
|
94
|
-
label: '安全',
|
|
95
|
-
description: '部署巡检与白帽渗透',
|
|
96
|
-
skillCategories: ['安全'],
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
id: 'article',
|
|
100
|
-
label: '内容创作',
|
|
101
|
-
description: '技术文章写作 skill',
|
|
102
|
-
skillCategories: ['内容创作'],
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
id: 'dolphindb',
|
|
106
|
-
label: 'DolphinDB',
|
|
107
|
-
description: 'DolphinDB 开发与审查 skill(体量大,不跟数据库栈绑定)',
|
|
108
|
-
skillCategories: ['DolphinDB'],
|
|
109
|
-
},
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
const STACK_IDS = new Set(STACK_PACKS.map(pack => pack.id))
|
|
113
|
-
const SCENE_IDS = new Set(SCENE_PACKS.map(pack => pack.id))
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* 规范化 sync-state 里的 packs。未知 id 丢弃,避免旧状态撑爆交互默认勾选。
|
|
117
|
-
*
|
|
118
|
-
* @param {{ stacks?: string[], scenes?: string[] } | null | undefined} raw
|
|
119
|
-
*/
|
|
120
|
-
export function normalizePacks(raw) {
|
|
121
|
-
return {
|
|
122
|
-
stacks: [...new Set((raw?.stacks || []).filter(id => STACK_IDS.has(id)))],
|
|
123
|
-
scenes: [...new Set((raw?.scenes || []).filter(id => SCENE_IDS.has(id)))],
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 判断扫描项是否属于某个包。
|
|
129
|
-
* 约束:只看本类型对应的 category / 显式名字,避免 skill 目录名误伤 rules。
|
|
130
|
-
*
|
|
131
|
-
* @param {{ name: string, category?: string }} item
|
|
132
|
-
* @param {object} pack
|
|
133
|
-
* @param {'skills' | 'rules' | 'hooks'} type
|
|
134
|
-
*/
|
|
135
|
-
export function itemInPack(item, pack, type) {
|
|
136
|
-
if (type === 'skills') {
|
|
137
|
-
if ((pack.skillNames || []).includes(item.name)) return true
|
|
138
|
-
return (pack.skillCategories || []).includes(item.category)
|
|
139
|
-
}
|
|
140
|
-
if (type === 'rules') return (pack.ruleCategories || []).includes(item.category)
|
|
141
|
-
return (pack.hookCategories || []).includes(item.category)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* 按名字去重合并,保持扫描原序。
|
|
146
|
-
*
|
|
147
|
-
* @param {Array<{ name: string }>} items
|
|
148
|
-
*/
|
|
149
|
-
function uniqueByName(items) {
|
|
150
|
-
const seen = new Set()
|
|
151
|
-
const result = []
|
|
152
|
-
for (const item of items) {
|
|
153
|
-
if (seen.has(item.name)) continue
|
|
154
|
-
seen.add(item.name)
|
|
155
|
-
result.push(item)
|
|
156
|
-
}
|
|
157
|
-
return result
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* 把已选栈 + 场景包展开成 sync 用的条目。
|
|
162
|
-
* skillsOnly 时 rules/hooks 必须空数组,调用方不得据此删除项目已有项。
|
|
163
|
-
*
|
|
164
|
-
* @param {{ skills: object[], rules: object[], commands?: object[], hooks: object[] }} scans
|
|
165
|
-
* @param {{ stacks?: string[], scenes?: string[], skillsOnly?: boolean }} selection
|
|
166
|
-
*/
|
|
167
|
-
/**
|
|
168
|
-
* 列出某包在当前扫描结果里的子条目,带 type,供界面展示「包下面有哪些」。
|
|
169
|
-
*
|
|
170
|
-
* @param {object} pack
|
|
171
|
-
* @param {{ skills?: object[], rules?: object[], hooks?: object[] }} scans
|
|
172
|
-
* @param {{ skillsOnly?: boolean }} [opts]
|
|
173
|
-
*/
|
|
174
|
-
export function listPackItems(pack, scans, { skillsOnly = false } = {}) {
|
|
175
|
-
const types = skillsOnly ? ['skills'] : ['skills', 'rules', 'hooks']
|
|
176
|
-
const items = []
|
|
177
|
-
for (const type of types) {
|
|
178
|
-
for (const item of scans[type] || []) {
|
|
179
|
-
if (itemInPack(item, pack, type)) items.push({ ...item, type })
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return items
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* 条目属于已选包时,用包的中文名做分组前缀。
|
|
187
|
-
*
|
|
188
|
-
* @param {{ name: string, category?: string }} item
|
|
189
|
-
* @param {'skills' | 'rules' | 'hooks'} type
|
|
190
|
-
* @param {string[]} packIds
|
|
191
|
-
*/
|
|
192
|
-
export function packLabelForItem(item, type, packIds) {
|
|
193
|
-
const packs = [...STACK_PACKS, ...SCENE_PACKS]
|
|
194
|
-
const hit = packs.find(pack => packIds.includes(pack.id) && itemInPack(item, pack, type))
|
|
195
|
-
return hit?.label || item.category || '其他'
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function expandPackSelection(scans, { stacks = [], scenes = [], skillsOnly = false } = {}) {
|
|
199
|
-
const packById = new Map([...STACK_PACKS, ...SCENE_PACKS].map(pack => [pack.id, pack]))
|
|
200
|
-
const selectedPacks = [...stacks, ...scenes]
|
|
201
|
-
.map(id => packById.get(id))
|
|
202
|
-
.filter(Boolean)
|
|
203
|
-
|
|
204
|
-
const pick = (type) => uniqueByName(
|
|
205
|
-
selectedPacks.flatMap(pack => (scans[type] || []).filter(item => itemInPack(item, pack, type))),
|
|
206
|
-
)
|
|
207
|
-
|
|
208
|
-
if (skillsOnly) {
|
|
209
|
-
return {
|
|
210
|
-
skills: pick('skills'),
|
|
211
|
-
rules: [],
|
|
212
|
-
commands: [],
|
|
213
|
-
hooks: [],
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
return {
|
|
218
|
-
skills: pick('skills'),
|
|
219
|
-
rules: pick('rules'),
|
|
220
|
-
commands: [],
|
|
221
|
-
hooks: pick('hooks'),
|
|
222
|
-
}
|
|
223
|
-
}
|
package/src/sync-scene.js
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 按包勾选交互:技术栈包 → 场景包。
|
|
3
|
-
* 每包一个 Tab,Tab 下列 checkbox;←/→ 切包,Space 勾子项,a 全选本包。
|
|
4
|
-
* 由 dwy / dwy sync 在「按场景和技术栈」模式下调用。
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { isCancel } from '@clack/prompts'
|
|
8
|
-
import { tabMultiselect } from './tab-multiselect.js'
|
|
9
|
-
import {
|
|
10
|
-
SCENE_PACKS,
|
|
11
|
-
STACK_PACKS,
|
|
12
|
-
listPackItems,
|
|
13
|
-
normalizePacks,
|
|
14
|
-
} from './sync-packs.js'
|
|
15
|
-
import { chalk } from './utils.js'
|
|
16
|
-
|
|
17
|
-
/** 子 value 三段分隔:packId / type / name。packId 不含冒号。 */
|
|
18
|
-
const PACK_CHILD_RE = /^([^:]+):(skills|rules|hooks):(.+)$/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 子条目在 Tab 多选里的稳定 value。
|
|
22
|
-
* 带 packId:同一 skill 可同时出现在 Vue 与 Python,取消一边不能误伤另一边。
|
|
23
|
-
*
|
|
24
|
-
* @param {string} packId
|
|
25
|
-
* @param {'skills' | 'rules' | 'hooks'} type
|
|
26
|
-
* @param {string} name
|
|
27
|
-
*/
|
|
28
|
-
export function packChildKey(packId, type, name) {
|
|
29
|
-
return `${packId}:${type}:${name}`
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* 解析 packChildKey。对不上的丢掉,避免脏 initialValues 撑进结果。
|
|
34
|
-
*
|
|
35
|
-
* @param {unknown} key
|
|
36
|
-
* @returns {{ packId: string, type: 'skills' | 'rules' | 'hooks', name: string } | null}
|
|
37
|
-
*/
|
|
38
|
-
export function parsePackChildKey(key) {
|
|
39
|
-
const match = String(key).match(PACK_CHILD_RE)
|
|
40
|
-
if (!match) return null
|
|
41
|
-
return { packId: match[1], type: match[2], name: match[3] }
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* 每个包一个 Tab。空包跳过,避免 Tab 下面没有可选项。
|
|
46
|
-
*
|
|
47
|
-
* @param {object[]} packs
|
|
48
|
-
* @param {object} scans
|
|
49
|
-
* @param {{ skillsOnly?: boolean, group?: string }} [opts]
|
|
50
|
-
* @returns {Array<{ id: string, label: string, group?: string, options: Array<{ value: string, label: string, type: string, description?: string }> }>}
|
|
51
|
-
*/
|
|
52
|
-
export function packTabs(packs, scans, { skillsOnly = false, group } = {}) {
|
|
53
|
-
const tabs = []
|
|
54
|
-
for (const pack of packs) {
|
|
55
|
-
const items = listPackItems(pack, scans, { skillsOnly })
|
|
56
|
-
if (items.length === 0) continue
|
|
57
|
-
tabs.push({
|
|
58
|
-
id: pack.id,
|
|
59
|
-
label: pack.label,
|
|
60
|
-
...(group ? { group } : {}),
|
|
61
|
-
options: items.map(item => ({
|
|
62
|
-
value: packChildKey(pack.id, item.type, item.name),
|
|
63
|
-
label: item.name,
|
|
64
|
-
type: item.type,
|
|
65
|
-
description: item.description || pack.description,
|
|
66
|
-
})),
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
return tabs
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* 已选包 → 其下全部子 value,给 tabMultiselect initialValues。
|
|
74
|
-
* 打开时该包 Tab 显示为全勾,用户可再取消。
|
|
75
|
-
*
|
|
76
|
-
* @param {object[]} packs
|
|
77
|
-
* @param {object} scans
|
|
78
|
-
* @param {string[]} selectedPackIds
|
|
79
|
-
* @param {{ skillsOnly?: boolean }} [opts]
|
|
80
|
-
*/
|
|
81
|
-
export function initialChildValues(packs, scans, selectedPackIds, { skillsOnly = false } = {}) {
|
|
82
|
-
const selected = new Set(selectedPackIds)
|
|
83
|
-
const values = []
|
|
84
|
-
for (const pack of packs) {
|
|
85
|
-
if (!selected.has(pack.id)) continue
|
|
86
|
-
for (const item of listPackItems(pack, scans, { skillsOnly })) {
|
|
87
|
-
values.push(packChildKey(pack.id, item.type, item.name))
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
return values
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 把勾上的子 value 还原成条目 + 所属包。
|
|
95
|
-
* 只认扫描结果里真实存在的名字,防止 key 伪造。
|
|
96
|
-
*
|
|
97
|
-
* @param {string[]} keys
|
|
98
|
-
* @param {object} scans
|
|
99
|
-
* @param {{ skillsOnly?: boolean }} [opts]
|
|
100
|
-
*/
|
|
101
|
-
export function itemsFromChildKeys(keys, scans, { skillsOnly = false } = {}) {
|
|
102
|
-
const parsed = keys.map(parsePackChildKey).filter(Boolean)
|
|
103
|
-
const packIds = [...new Set(parsed.map(item => item.packId))]
|
|
104
|
-
const names = { skills: new Set(), rules: new Set(), hooks: new Set() }
|
|
105
|
-
for (const item of parsed) names[item.type].add(item.name)
|
|
106
|
-
const pick = (type) => (scans[type] || []).filter(item => names[type].has(item.name))
|
|
107
|
-
if (skillsOnly) {
|
|
108
|
-
return { packIds, skills: pick('skills'), rules: [], commands: [], hooks: [] }
|
|
109
|
-
}
|
|
110
|
-
return {
|
|
111
|
-
packIds,
|
|
112
|
-
skills: pick('skills'),
|
|
113
|
-
rules: pick('rules'),
|
|
114
|
-
commands: [],
|
|
115
|
-
hooks: pick('hooks'),
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 打印本次按包勾选的包名与条数。
|
|
121
|
-
*
|
|
122
|
-
* @param {string[]} stacks
|
|
123
|
-
* @param {string[]} scenes
|
|
124
|
-
* @param {{ skills: object[], rules: object[], hooks: object[] }} selected
|
|
125
|
-
*/
|
|
126
|
-
function printPackSummary(stacks, scenes, selected) {
|
|
127
|
-
const stackLabels = STACK_PACKS.filter(pack => stacks.includes(pack.id)).map(pack => pack.label)
|
|
128
|
-
const sceneLabels = SCENE_PACKS.filter(pack => scenes.includes(pack.id)).map(pack => pack.label)
|
|
129
|
-
const packText = [...stackLabels, ...sceneLabels].join(' + ') || '(未选包)'
|
|
130
|
-
console.log(chalk.gray('\n包展开:'))
|
|
131
|
-
console.log(chalk.gray(` ${packText}`))
|
|
132
|
-
console.log(chalk.gray(` Skills ${selected.skills.length} Rules ${selected.rules.length} Hooks ${selected.hooks.length}`))
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* 按包选择条目。技术栈 Tab 在左、场景 Tab 在右,同一屏勾选。
|
|
137
|
-
* 返回 { skills, rules, commands, hooks, packs } 或 null(取消)。
|
|
138
|
-
*
|
|
139
|
-
* @param {object} scans
|
|
140
|
-
* @param {{ packs?: { stacks?: string[], scenes?: string[] } }} syncState
|
|
141
|
-
* @param {{ skillsOnly?: boolean }} [opts]
|
|
142
|
-
*/
|
|
143
|
-
export async function interactiveSelectByPacks(scans, syncState, { skillsOnly = false } = {}) {
|
|
144
|
-
const previous = normalizePacks(syncState?.packs)
|
|
145
|
-
const packOpts = { skillsOnly }
|
|
146
|
-
const defaultStacks = previous.stacks.length > 0 ? previous.stacks : ['common']
|
|
147
|
-
const tabs = [
|
|
148
|
-
...packTabs(STACK_PACKS, scans, { ...packOpts, group: 'stack' }),
|
|
149
|
-
...packTabs(SCENE_PACKS, scans, { ...packOpts, group: 'scene' }),
|
|
150
|
-
]
|
|
151
|
-
const keys = await tabMultiselect({
|
|
152
|
-
message: '选择技术栈和场景包',
|
|
153
|
-
tabs,
|
|
154
|
-
initialValues: [
|
|
155
|
-
...initialChildValues(STACK_PACKS, scans, defaultStacks, packOpts),
|
|
156
|
-
...initialChildValues(SCENE_PACKS, scans, previous.scenes, packOpts),
|
|
157
|
-
],
|
|
158
|
-
required: true,
|
|
159
|
-
maxItems: 12,
|
|
160
|
-
})
|
|
161
|
-
if (isCancel(keys)) return null
|
|
162
|
-
|
|
163
|
-
const stacks = STACK_PACKS.map(pack => pack.id).filter(id =>
|
|
164
|
-
keys.some(key => parsePackChildKey(key)?.packId === id),
|
|
165
|
-
)
|
|
166
|
-
const scenes = SCENE_PACKS.map(pack => pack.id).filter(id =>
|
|
167
|
-
keys.some(key => parsePackChildKey(key)?.packId === id),
|
|
168
|
-
)
|
|
169
|
-
const selected = itemsFromChildKeys(keys, scans, packOpts)
|
|
170
|
-
printPackSummary(stacks, scenes, selected)
|
|
171
|
-
return {
|
|
172
|
-
skills: selected.skills,
|
|
173
|
-
rules: selected.rules,
|
|
174
|
-
commands: selected.commands,
|
|
175
|
-
hooks: selected.hooks,
|
|
176
|
-
packs: { stacks, scenes },
|
|
177
|
-
}
|
|
178
|
-
}
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: TDD 开发流程(新功能/bug 修复/重构的测试先行与回归策略)与测试-源码分隔(测试工程独立 tests/ 目录、镜像源码结构、各栈目录约定)
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# TDD 开发规则
|
|
6
|
-
|
|
7
|
-
所有代码变更必须遵循测试驱动开发流程。测试不是事后补的,是开发的起点。
|
|
8
|
-
|
|
9
|
-
## 开发流程
|
|
10
|
-
|
|
11
|
-
### 新功能
|
|
12
|
-
|
|
13
|
-
1. 先写测试 — 定义预期行为,测试此时应失败
|
|
14
|
-
2. 再写实现 — 用最简代码让测试通过
|
|
15
|
-
3. 重构优化 — 在测试保护下重构,确保始终通过
|
|
16
|
-
|
|
17
|
-
### Bug 修复
|
|
18
|
-
|
|
19
|
-
1. 先写复现测试 — 写一个能暴露 bug 的测试用例,确认它失败
|
|
20
|
-
2. 再修代码 — 修复 bug,确认测试通过
|
|
21
|
-
3. 跑回归 — 确认没有引入新问题
|
|
22
|
-
|
|
23
|
-
### 重构
|
|
24
|
-
|
|
25
|
-
1. 先跑现有测试 — 确认当前全部通过
|
|
26
|
-
2. 再重构 — 修改代码
|
|
27
|
-
3. 再跑测试 — 确认行为没变
|
|
28
|
-
|
|
29
|
-
## 测试与源码分隔(硬约束)
|
|
30
|
-
|
|
31
|
-
测试工程必须与源码分隔,不混在一起。
|
|
32
|
-
|
|
33
|
-
### 核心规则
|
|
34
|
-
|
|
35
|
-
- 测试代码**禁止**与源码同目录混放;测试集中在项目根 / 包根的独立 `tests/` 目录。
|
|
36
|
-
- `tests/` **镜像源码结构**,测试文件与被测源码一一对应、可反查:
|
|
37
|
-
- `src/foo/bar.ts` → `tests/foo/bar.test.ts`
|
|
38
|
-
- `src/services/user.py` → `tests/test_user.py`
|
|
39
|
-
- 源码目录(`src/`)保持纯净,**只放产品代码**;打包 / 发布产物天然不含测试,CI 用一个目录通配即可圈定全部测试。
|
|
40
|
-
- 测试用的 fixture / mock 数据 / 测试工具,放 `tests/` 内(如 `tests/fixtures/`、`tests/conftest.py`),不散落进源码目录。
|
|
41
|
-
|
|
42
|
-
### 为什么(约束的根因)
|
|
43
|
-
|
|
44
|
-
测试混入源码目录会引发一连串问题,分隔后一次性收口:
|
|
45
|
-
|
|
46
|
-
- **被打包进发布产物**:测试随源码一起发到 npm / PyPI,污染产物、撑大体积。
|
|
47
|
-
- **source map / 内部细节泄露**:测试常含内部实现假设、mock 的真实数据,泄露到产物有安全风险。
|
|
48
|
-
- **覆盖率 / 构建统计污染**:测试文件被计入源码统计,覆盖率、行数失真。
|
|
49
|
-
- **CI 难圈定范围**:源码与测试交织,CI 无法用一条目录规则排除测试。
|
|
50
|
-
- **AI / 工具难定位**:找不到「源码↔测试」对应关系,反复把测试误写进 `src/`。
|
|
51
|
-
|
|
52
|
-
### 各栈目录与命令速查表
|
|
53
|
-
|
|
54
|
-
| 栈 | 测试目录 | 命名约定 | 运行命令 |
|
|
55
|
-
|----|---------|---------|---------|
|
|
56
|
-
| Vue / Vitest | `tests/` 镜像 `src/` | `*.test.ts` | `pnpm vitest run [路径]` |
|
|
57
|
-
| Python / pytest | `tests/` | `test_*.py` | `pytest tests/ -v` / `uv run pytest` |
|
|
58
|
-
| Android | `src/test/`(单元)+ `src/androidTest/`(仪器) | `*Test.kt` | `./gradlew test` / `./gradlew connectedAndroidTest` |
|
|
59
|
-
| Node / CLI | `tests/` 镜像 `src/` | `*.test.ts` | 对应 runner(vitest / jest) |
|
|
60
|
-
|
|
61
|
-
注:Android 的 `src/test` / `src/androidTest` 是 Gradle 生态**强约定**的独立测试源集(test source set),与 `src/main` 源码完全分隔,符合本规范"测试与源码分隔"理念,按其生态默认即可,**属合规分隔**,不视为"测试写进 src"。
|
|
62
|
-
|
|
63
|
-
**通用准则(表中未列的栈)**:遵循「独立测试目录 + 镜像源码结构 + 该生态主流 runner 约定」三条原则自行判断,不硬套上表布局。
|
|
64
|
-
|
|
65
|
-
## 回归测试
|
|
66
|
-
|
|
67
|
-
### 开发中:只跑受影响的测试
|
|
68
|
-
|
|
69
|
-
每次代码改动后,只运行受影响的测试文件,提高开发效率:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
# 后端:只跑改动涉及的测试文件
|
|
73
|
-
uv run pytest tests/test_factor.py -v # 改了 factor 相关代码
|
|
74
|
-
uv run pytest tests/test_tick.py tests/test_upload.py -v # 改了多个模块
|
|
75
|
-
|
|
76
|
-
# 前端:只跑对应目录
|
|
77
|
-
pnpm vitest run tests/stores/ # 改了 store
|
|
78
|
-
pnpm vitest run tests/utils/format.test.ts # 改了单个工具函数
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
判断"受影响的测试"的规则:
|
|
82
|
-
- 改了 `services/xxx.py` → 跑 `tests/test_xxx.py`
|
|
83
|
-
- 改了 `routers/xxx.py` → 跑 `tests/test_xxx.py`
|
|
84
|
-
- 改了 `schemas/` 或 `models/` → 跑引用了它们的测试
|
|
85
|
-
- 改了 `conftest.py` 或公共依赖 → 全量回归
|
|
86
|
-
- 不确定影响范围 → 全量回归
|
|
87
|
-
|
|
88
|
-
### 提交前:跑受影响的测试即可
|
|
89
|
-
|
|
90
|
-
提交前确保受影响的测试通过即可,不要求全量回归。全量测试留给 CI 或手动触发。
|
|
91
|
-
|
|
92
|
-
## TEST_CASES.md
|
|
93
|
-
|
|
94
|
-
每个包/项目维护一份 `TEST_CASES.md`,记录所有测试用例的清单。**`TEST_CASES.md` 跟测试目录走,不放项目根**:
|
|
95
|
-
|
|
96
|
-
- 有 `tests/` → 放 `tests/TEST_CASES.md`
|
|
97
|
-
- 测试源集分散(如 Android `src/test/`)→ 放对应测试根(`src/test/TEST_CASES.md`),与该模块测试同处
|
|
98
|
-
- 理念:测试清单是测试工程的一部分,归测试目录,与「测试与源码分隔」一致
|
|
99
|
-
|
|
100
|
-
内容要求:
|
|
101
|
-
|
|
102
|
-
- 人类可读,方便审查和手动编辑
|
|
103
|
-
- 新增/修改/删除功能时必须同步更新
|
|
104
|
-
- 内容与实际测试文件保持一致
|
|
105
|
-
- 作为版本回测基准
|
|
106
|
-
|
|
107
|
-
## 禁止事项
|
|
108
|
-
|
|
109
|
-
- **禁止**不写测试就提交功能代码
|
|
110
|
-
- **禁止**写完代码再补测试(先测试后代码)
|
|
111
|
-
- **禁止**测试不通过就提交
|
|
112
|
-
- **禁止** TEST_CASES.md 与实际测试不同步
|
|
113
|
-
- **禁止**测试文件写进 `src/` 源码目录(Android `src/test`、`src/androidTest` 例外,是 Gradle 标准测试源集)
|
|
114
|
-
- **禁止**测试目录结构与源码脱节(找不到「源码↔测试」对应关系)
|
|
115
|
-
- **禁止**把测试 fixture / mock 数据散落进源码目录
|