@sokeai/cli 1.0.76 → 1.0.77
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/README.md +7 -7
- package/package.json +1 -1
- package/scripts/build-binaries.sh +1 -1
- package/scripts/e2e-test.sh +75 -3
- package/skills/SKILL.md +18 -1
- package/skills/soke-ai-coach-director/CHANGELOG.md +203 -0
- package/skills/soke-ai-coach-director/README.md +123 -0
- package/skills/soke-ai-coach-director/SKILL.md +221 -0
- package/skills/soke-ai-coach-director/coaching/knowledge-packager/knowledge-packager.md +91 -0
- package/skills/soke-ai-coach-director/coaching/prompt-engineer/parts/part2-roles.md +97 -0
- package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-engineer/parts/part3-flow.md +3 -3
- package/skills/soke-ai-coach-director/coaching/prompt-engineer/prompt-engineer.md +76 -0
- package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-optimizer/prompt-optimizer.md +77 -58
- package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/workbuddy.md +2 -3
- package/skills/soke-ai-coach-director/platform/interaction.md +103 -0
- package/skills/{ai-coach-director → soke-ai-coach-director}/platform/publish-gate.md +3 -3
- package/skills/{ai-coach-director → soke-ai-coach-director}/platform/resource-finalizer.md +10 -35
- package/skills/{ai-coach-director → soke-ai-coach-director}/platform/soke-ai-training/soke-ai-training.md +33 -3
- package/skills/soke-ai-coach-director/platform/sync-engine.md +39 -0
- package/skills/{ai-coach-director → soke-ai-coach-director}/references/change-log-partwise-refactor.md +1 -1
- package/skills/soke-ai-coach-director/references/env-check.md +146 -0
- package/skills/soke-ai-coach-director/references/exec-common.md +126 -0
- package/skills/soke-ai-coach-director/references/exec-handbook.md +621 -0
- package/skills/soke-ai-coach-director/references/exec-part1.md +86 -0
- package/skills/soke-ai-coach-director/references/exec-part2.md +145 -0
- package/skills/soke-ai-coach-director/references/exec-part3.md +91 -0
- package/skills/soke-ai-coach-director/references/exec-part4.md +187 -0
- package/skills/soke-ai-coach-director/references/gate-check-matrix.md +46 -0
- package/skills/soke-ai-coach-director/references/micro-tune-labels.md +30 -0
- package/skills/soke-ai-coach-director/references/platform-api-pitfalls.md +49 -0
- package/skills/soke-ai-coach-director/references/role-resource-matching.md +45 -0
- package/skills/{ai-coach-director → soke-ai-coach-director}/references/self-improvement-examples.md +2 -0
- package/skills/soke-ai-coach-director/references/self-learning-engine.md +489 -0
- package/skills/soke-ai-coach-director/references/subs/part1/SOP.md +17 -0
- package/skills/soke-ai-coach-director/references/subs/part2/SOP.md +34 -0
- package/skills/soke-ai-coach-director/references/subs/part3/SOP.md +27 -0
- package/skills/soke-ai-coach-director/references/subs/part4/SOP.md +31 -0
- package/skills/soke-ai-coach-director/references/subs/publish/SOP.md +81 -0
- package/skills/soke-ai-coach-director/references/subs/tune/SOP.md +92 -0
- package/skills/soke-ai-coach-director/references/verified-cli-cheatsheet.md +56 -0
- package/skills/{ai-coach-director → soke-ai-coach-director}/scripts/activator.sh +0 -0
- package/skills/{ai-coach-director → soke-ai-coach-director}/scripts/error-detector.sh +0 -0
- package/skills/{ai-coach-director → soke-ai-coach-director}/scripts/extract-skill.sh +0 -0
- package/skills/soke-ai-coach-director/scripts/gate_check.py +112 -0
- package/skills/soke-ai-coach-director/scripts/validate_operations.py +403 -0
- package/skills/soke-certificate/SKILL.md +717 -0
- package/skills/soke-course/README.md +6 -2
- package/skills/soke-course/SKILL.md +3 -3
- package/skills/soke-course/references/course-list-courses.md +49 -84
- package/skills/soke-course/references/intent-cases.md +2 -2
- package/skills/soke-course/references/sop-full-workflow.md +4 -2
- package/skills/soke-course/references/subs/publish/SOP.md +21 -3
- package/skills/soke-course/references/subs/query/SOP.md +9 -7
- package/skills/soke-course/references/subs/settings/SOP.md +2 -1
- package/skills/soke-exam-question-pool/SKILL.md +2 -2
- package/skills/soke-task/SKILL.md +32 -10
- package/skills/soke-training-demand/SKILL.md +2 -2
- package/skills/ai-coach-director/SKILL.md +0 -1563
- package/skills/ai-coach-director/coaching/prompt-engineer/parts/part2-roles.md +0 -317
- package/skills/ai-coach-director/coaching/prompt-engineer/prompt-engineer.md +0 -244
- package/skills/ai-coach-director/platform/interaction.md +0 -315
- package/skills/ai-coach-director/platform/sync-engine.md +0 -1101
- package/skills/ai-coach-director/references/cases/create//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/224/265/350/257/235/347/272/246/350/256/277//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/224/265/350/257/235/347/272/246/350/256/277_/346/240/207/345/207/206/347/237/245/350/257/206/345/272/223.md +0 -199
- package/skills/ai-coach-director/references/cases/create//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/224/265/350/257/235/347/272/246/350/256/277//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/224/265/350/257/235/347/272/246/350/256/277/347/237/245/350/257/206/345/272/223.md +0 -388
- package/skills/ai-coach-director/references/cases/create//346/237/220/345/233/275/351/231/205/346/243/200/346/265/213/346/234/272/346/236/204/tuv-knowledge-base.md +0 -125
- package/skills/ai-coach-director/references/cases/create//346/237/220/345/233/275/351/231/205/346/243/200/346/265/213/346/234/272/346/236/204/tuv-p03-first-call-scenario-prompt.md +0 -428
- package/skills/ai-coach-director/references/cases/create//346/237/220/345/233/275/351/231/205/346/243/200/346/265/213/346/234/272/346/236/204//346/237/220/345/233/275/351/231/205/346/243/200/346/265/213/346/234/272/346/236/204P03/351/231/252/347/273/203/346/211/247/350/241/214/346/226/271/346/241/210_20260610.md +0 -225
- package/skills/ai-coach-director/references/cases/create//346/237/220/345/234/260/346/226/271/351/223/266/350/241/214//346/237/220/345/234/260/346/226/271/351/223/266/350/241/214/345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/224/265/350/257/235/347/272/246/350/256/277AI/351/231/252/347/273/203/346/265/213/350/257/225/347/224/250/344/276/213/345/220/210/351/233/206.md +0 -525
- package/skills/ai-coach-director/references/cases/create//347/211/251/346/265/201/350/241/214/344/270/232//347/211/251/346/265/201/350/241/214/344/270/232-/345/210/235/346/254/241/346/213/234/350/256/277/351/246/231/347/262/276/351/246/231/346/226/231/345/256/242/346/210/267.md +0 -146
- package/skills/ai-coach-director/references/cases/create//351/205/222/345/272/227/351/244/220/351/245/256//351/205/222/345/272/227/351/244/220/351/245/256AI/351/231/252/347/273/203_/346/240/207/346/235/2061_/346/226/260/345/223/201/346/216/250/351/224/200_20260701.md +0 -230
- package/skills/ai-coach-director/references/cases/create//351/205/222/345/272/227/351/244/220/351/245/256//351/205/222/345/272/227/351/244/220/351/245/256AI/351/231/252/347/273/203_/346/240/207/346/235/2062_/345/202/254/350/217/234/345/256/211/346/212/232_20260701.md +0 -257
- package/skills/ai-coach-director/references/cases/create//351/205/222/345/272/227/351/244/220/351/245/256//351/205/222/345/272/227/351/244/220/351/245/256AI/351/231/252/347/273/203_/346/240/207/346/235/2063_/345/267/256/350/257/204/345/233/236/350/256/277_20260701.md +0 -259
- package/skills/ai-coach-director/references/cases/create//351/205/222/345/272/227/351/244/220/351/245/256//351/205/222/345/272/227/351/244/220/351/245/256AI/351/231/252/347/273/203/345/234/272/346/231/257/346/226/271/346/241/210_20260701.md +0 -452
- package/skills/ai-coach-director/references/cases/create//351/231/214/346/213/234/344/277/241/346/201/257/346/224/266/351/233/206//351/231/214/346/213/234/344/277/241/346/201/257/346/224/266/351/233/206_/345/256/242/346/210/267/347/241/256/350/256/244/345/217/215/351/246/210/350/241/250.md +0 -133
- package/skills/ai-coach-director/references/cases/create//351/231/214/346/213/234/344/277/241/346/201/257/346/224/266/351/233/206//351/231/214/346/213/234/344/277/241/346/201/257/346/224/266/351/233/206_/351/231/252/347/273/203/346/217/220/347/244/272/350/257/215_v1.md +0 -333
- package/skills/ai-coach-director/references/cases/create//351/231/214/346/213/234/344/277/241/346/201/257/346/224/266/351/233/206//351/231/214/346/213/234/344/277/241/346/201/257/346/224/266/351/233/206_/351/231/252/347/273/203/346/217/220/347/244/272/350/257/215_v2_Part1-Part5.md +0 -649
- package/skills/ai-coach-director/references/env-check.md +0 -167
- package/skills/ai-coach-director/references/platform-api-pitfalls.md +0 -232
- package/skills/ai-coach-director/references/role-resource-matching.md +0 -349
- package/skills/ai-coach-director/references/verified-cli-cheatsheet.md +0 -203
- /package/skills/{ai-coach-director → soke-ai-coach-director}/assets/ERRORS-TEMPLATE.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/assets/FEATURE_REQUESTS-TEMPLATE.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/assets/LEARNINGS-TEMPLATE.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/assets/SKILL-TEMPLATE.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-engineer/parts/part1-basic.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-engineer/parts/part4-scoring.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-engineer/parts/part5-constraints.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/role-profiler/role-profiler.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/README.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/qclaw.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/wukong.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/zework.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/api-fallback.md +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/345/256/242/346/210/267/350/247/222/350/211/262/346/250/241/346/235/277/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203Bad Case/346/240/267/346/234/254/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/345/210/233/345/273/272/351/227/256/351/242/230/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/345/234/272/346/231/257/345/210/206/347/261/273/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/345/256/242/346/210/267/346/235/220/346/226/231/346/270/205/345/215/225/344/270/216/350/256/277/350/260/210/351/242/230/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/345/276/256/350/260/203/351/227/256/351/242/230/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/346/212/200/346/234/257/350/276/271/347/225/214/344/270/216/346/216/222/351/232/234/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/346/217/220/347/244/272/350/257/215/346/250/241/345/235/227/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/346/225/260/346/215/256/346/214/207/346/240/207/344/270/216/347/234/213/346/235/277/345/217/243/345/276/204/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/346/265/213/350/257/225/347/224/250/344/276/213/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/350/257/204/345/210/206/346/240/207/345/207/206/347/273/204/344/273/266/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/231/252/347/273/203/351/241/271/347/233/256/346/241/210/344/276/213/345/244/215/347/233/230/345/272/223.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/AI/351/241/276/345/256/242/350/247/222/350/211/262/346/250/241/346/213/237_GoodCase_BadCase_20260701.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/create//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/224/265/350/257/235/347/272/246/350/256/277//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/345/256/242/346/210/267/346/235/220/346/226/231/346/261/207/346/200/273_2026-05-19_1341.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/create//346/226/260/350/203/275/346/272/220/350/275/246/344/270/273/350/264/267//346/226/260/350/203/275/346/272/220/350/275/246/344/270/273/350/264/267/347/237/245/350/257/206/345/272/223/347/224/237/346/210/220_20260615.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/create//346/226/260/350/203/275/346/272/220/350/275/246/344/270/273/350/264/267//346/226/260/350/203/275/346/272/220/350/275/246/344/270/273/350/264/267/351/231/252/347/273/203/346/217/220/347/244/272/350/257/215/347/224/237/346/210/220_20260615.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/create//346/237/220/345/233/275/351/231/205/346/243/200/346/265/213/346/234/272/346/236/204//346/237/220/345/233/275/351/231/205/346/243/200/346/265/213/346/234/272/346/236/204/351/231/252/347/273/203/346/211/247/350/241/214/346/226/271/346/241/210/344/272/244/344/273/230/350/256/260/345/275/225_20260610.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/create//351/231/214/346/213/234/344/277/241/346/201/257/346/224/266/351/233/206//351/231/214/346/213/234/344/277/241/346/201/257/346/224/266/351/233/206_/346/217/220/347/244/272/350/257/215/346/220/255/345/273/272_20260622.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//344/274/201/344/270/232/345/256/266/347/216/213/346/200/273/347/263/273/345/210/227//344/274/201/344/270/232/345/256/266/347/216/213/346/200/273/346/217/220/347/244/272/350/257/215_/344/274/230/345/214/226/347/211/210_V1.1_20260616.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//344/274/201/344/270/232/345/256/266/347/216/213/346/200/273/347/263/273/345/210/227//344/274/201/344/270/232/345/256/266/347/216/213/346/200/273/346/217/220/347/244/272/350/257/215_/344/274/230/345/214/226/347/211/210_V1.2_20260616.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//344/274/201/344/270/232/345/256/266/347/216/213/346/200/273/347/263/273/345/210/227//344/274/201/344/270/232/345/256/266/347/216/213/346/200/273/346/217/220/347/244/272/350/257/215_/345/216/213/347/274/251/347/211/210_V2.0_20260616.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//344/274/201/344/270/232/345/256/266/347/216/213/346/200/273/347/263/273/345/210/227//344/274/201/344/270/232/345/256/266/347/216/213/346/200/273/346/217/220/347/244/272/350/257/215/344/274/230/345/214/226_20260616.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/263/273/345/210/227//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277AI/351/231/252/347/273/203/347/263/273/347/273/237_/346/265/213/350/257/225/345/217/215/351/246/210/346/261/207/346/200/273_2026-06-01.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/263/273/345/210/227//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277KYC/344/277/241/344/273/273/345/200/274/347/246/201/344/273/244_2026-06-09_0932.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/263/273/345/210/227//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277KYC/346/217/220/347/244/272/350/257/215_/344/274/230/345/214/226/347/211/210_v2_20260608.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/263/273/345/210/227//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/346/217/220/347/244/272/350/257/215_/344/274/230/345/214/226/347/211/210_v1.1.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/263/273/345/210/227//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/346/217/220/347/244/272/350/257/215_/344/274/230/345/214/226/347/211/210_v1.2.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases/micro-tune//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/347/263/273/345/210/227//345/206/234/346/210/267/345/274/240/350/200/201/346/235/277/346/217/220/347/244/272/350/257/215/344/274/230/345/214/226_V2_/350/257/212/346/226/255/344/272/244/344/273/230_20260609.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases//345/233/233/345/244/247/345/234/272/346/231/257_GoodCase_BadCase_20260701.md" +0 -0
- /package/skills/{ai-coach-director → soke-ai-coach-director}/references/cases//346/241/210/344/276/213/345/272/223/350/241/245/345/205/205_2026-06-24.md" +0 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
soke-ai-coach-director · 执行手册验证器
|
|
4
|
+
Usage:
|
|
5
|
+
python3 scripts/validate_operations.py <part> <operations.json>
|
|
6
|
+
python3 scripts/validate_operations.py part1 ./part1-create.json
|
|
7
|
+
python3 scripts/validate_operations.py part2 ./part2-operations.json
|
|
8
|
+
python3 scripts/validate_operations.py part3 ./part3-operations.json
|
|
9
|
+
python3 scripts/validate_operations.py part4 ./part4-operations.json
|
|
10
|
+
python3 scripts/validate_operations.py part5 ./part5-operations.json
|
|
11
|
+
|
|
12
|
+
退出码:
|
|
13
|
+
0 = 全部通过
|
|
14
|
+
1 = 有错误(详情打印到 stderr)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
import os
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ---------- Part1: step1-create.json / part1-create.json ----------
|
|
24
|
+
|
|
25
|
+
def validate_part1(data: dict) -> list[str]:
|
|
26
|
+
"""Part1 不是 operations 数组,而是单个 JSON 对象。"""
|
|
27
|
+
errors = []
|
|
28
|
+
|
|
29
|
+
# name
|
|
30
|
+
name = data.get("name")
|
|
31
|
+
if not isinstance(name, str) or not (1 <= len(name) <= 100):
|
|
32
|
+
errors.append(f"name: 必须是 1-100 字符的字符串,当前 type={type(name).__name__} value={name!r}")
|
|
33
|
+
|
|
34
|
+
# description
|
|
35
|
+
desc = data.get("description")
|
|
36
|
+
if not isinstance(desc, str) or len(desc) < 20:
|
|
37
|
+
errors.append(f"description: 必须是 ≥20 字符的字符串,当前 len={len(desc) if isinstance(desc, str) else 0}")
|
|
38
|
+
|
|
39
|
+
# tags
|
|
40
|
+
tags = data.get("tags")
|
|
41
|
+
if not isinstance(tags, list):
|
|
42
|
+
errors.append(f"tags: 必须是数组,当前 type={type(tags).__name__}")
|
|
43
|
+
elif not (3 <= len(tags) <= 5):
|
|
44
|
+
errors.append(f"tags: 必须是 3-5 个元素,当前 {len(tags)}")
|
|
45
|
+
elif not all(isinstance(t, str) and t for t in tags):
|
|
46
|
+
errors.append("tags: 所有元素必须是非空字符串")
|
|
47
|
+
|
|
48
|
+
# key_points: 必须是字符串(最高频错误)
|
|
49
|
+
kp = data.get("key_points")
|
|
50
|
+
if not isinstance(kp, str) or not kp:
|
|
51
|
+
errors.append(f"key_points: 必须是非空字符串(不能是数组),当前 type={type(kp).__name__}")
|
|
52
|
+
# 回查: 如果是数组,提示
|
|
53
|
+
if isinstance(kp, list):
|
|
54
|
+
errors.append("key_points: ❌ 被写成数组,平台会接受但字段为空。改为分号拼接的单字符串")
|
|
55
|
+
|
|
56
|
+
# scene_lang
|
|
57
|
+
if data.get("scene_lang") != "简体中文":
|
|
58
|
+
errors.append(f"scene_lang: 必须固定为 '简体中文',当前 {data.get('scene_lang')!r}")
|
|
59
|
+
|
|
60
|
+
# scenario_cover 或 cover_url(CLI 接受两者)
|
|
61
|
+
cover = data.get("scenario_cover") or data.get("cover_url")
|
|
62
|
+
if cover != "https://newsokeeditorcdn.soke.cn/public/ai/cover/lingshou_daogou.png":
|
|
63
|
+
errors.append(f"scenario_cover/cover_url: 必须使用固定默认图 URL,当前 {cover!r}")
|
|
64
|
+
|
|
65
|
+
return errors
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ---------- Part2: operations 数组(update_basic + replace_ai_roles) ----------
|
|
69
|
+
|
|
70
|
+
def validate_part2(operations: list) -> list[str]:
|
|
71
|
+
errors = []
|
|
72
|
+
|
|
73
|
+
# 操作 1: update_basic (OS)
|
|
74
|
+
os_ops = [o for o in operations if o.get("type") == "update_basic"]
|
|
75
|
+
if not os_ops:
|
|
76
|
+
errors.append("❌ 缺操作 1: update_basic(OS 整体设定)—— 会导致平台无 OS,角色行为退化")
|
|
77
|
+
else:
|
|
78
|
+
for op in os_ops:
|
|
79
|
+
os_text = op.get("data", {}).get("ai_roles_overall_description")
|
|
80
|
+
if not isinstance(os_text, str):
|
|
81
|
+
errors.append(f"ai_roles_overall_description: 必须是字符串,当前 type={type(os_text).__name__}")
|
|
82
|
+
elif len(os_text) < 500:
|
|
83
|
+
errors.append(f"ai_roles_overall_description: 必须 ≥500 字符(当前 {len(os_text)}),A1-A7 全文不摘要")
|
|
84
|
+
|
|
85
|
+
# 操作 2: replace_ai_roles
|
|
86
|
+
role_ops = [o for o in operations if o.get("type") == "replace_ai_roles"]
|
|
87
|
+
if not role_ops:
|
|
88
|
+
errors.append("❌ 缺操作 2: replace_ai_roles(角色数据)—— 会导致平台有 OS 无角色")
|
|
89
|
+
else:
|
|
90
|
+
for ri, op in enumerate(role_ops):
|
|
91
|
+
# v2.26: 检测 ai_roles_overall_description 是否被错误放入 replace_ai_roles.data
|
|
92
|
+
wrong_os = op.get("data", {}).get("ai_roles_overall_description")
|
|
93
|
+
if wrong_os is not None:
|
|
94
|
+
errors.append(
|
|
95
|
+
"❌ ai_roles_overall_description 被错误放入 replace_ai_roles.data!"
|
|
96
|
+
"必须移到 update_basic.data 中,否则平台静默丢弃、API 不报错。"
|
|
97
|
+
)
|
|
98
|
+
roles = op.get("data", {}).get("roles")
|
|
99
|
+
if not isinstance(roles, list) or not roles:
|
|
100
|
+
errors.append(f"replace_ai_roles.data.roles: 必须是非空数组")
|
|
101
|
+
continue
|
|
102
|
+
for i, r in enumerate(roles):
|
|
103
|
+
# 外壳字段必须在 role_data 外层(source_type 允许同时存在于两处)
|
|
104
|
+
for shell_field in ["customization_level", "is_custom_role", "customized_fields"]:
|
|
105
|
+
if shell_field not in r:
|
|
106
|
+
errors.append(f"roles[{i}]: 缺外壳字段 {shell_field}(必须在 role_data 外层)")
|
|
107
|
+
# source_type 是特殊情况:顶层必须有,role_data 里可以有也可以没有
|
|
108
|
+
if "source_type" not in r:
|
|
109
|
+
errors.append(f"roles[{i}]: 缺外壳字段 source_type(必须在 role_data 外层)")
|
|
110
|
+
|
|
111
|
+
rd = r.get("role_data", {})
|
|
112
|
+
for k in ["name", "description", "communication_style", "background",
|
|
113
|
+
"personality_type", "personality", "avatar", "voice_id"]:
|
|
114
|
+
if not rd.get(k):
|
|
115
|
+
errors.append(f"roles[{i}].role_data.{k}: 空/缺失")
|
|
116
|
+
|
|
117
|
+
# knowledge_level 必须是数组
|
|
118
|
+
kl = rd.get("knowledge_level")
|
|
119
|
+
if not isinstance(kl, list):
|
|
120
|
+
errors.append(f"roles[{i}].role_data.knowledge_level: 必须是数组(如 ['小白']),当前 type={type(kl).__name__}")
|
|
121
|
+
|
|
122
|
+
# tags 必须是数组 2-4 个
|
|
123
|
+
tags = rd.get("tags")
|
|
124
|
+
if not isinstance(tags, list) or not (2 <= len(tags) <= 4):
|
|
125
|
+
errors.append(f"roles[{i}].role_data.tags: 必须是 2-4 个元素的数组,当前 {tags!r}")
|
|
126
|
+
|
|
127
|
+
# avatar URL 必须在平台头像库中(v2.24.2 新增)
|
|
128
|
+
avatar_url = rd.get("avatar", "")
|
|
129
|
+
if avatar_url and "/public/ai/avatar/" in avatar_url:
|
|
130
|
+
errors.append(f"roles[{i}].role_data.avatar: ❌ URL 路径错误!正确路径是 `/public/ai/roles_avatars/` 不是 `/public/ai/avatar/`(长龙航空事故)")
|
|
131
|
+
elif avatar_url and "/public/ai/roles_avatars/" not in avatar_url and "/public/ai/cover/" not in avatar_url:
|
|
132
|
+
print(f"⚠️ roles[{i}].role_data.avatar: URL 不在平台头像库路径下(/public/ai/roles_avatars/ 或 /public/ai/cover/)", file=sys.stderr)
|
|
133
|
+
to = rd.get("taboos_objections")
|
|
134
|
+
# taboos_objections 必须是数组
|
|
135
|
+
to = rd.get("taboos_objections")
|
|
136
|
+
if not isinstance(to, list):
|
|
137
|
+
errors.append(f"roles[{i}].role_data.taboos_objections: 必须是数组")
|
|
138
|
+
|
|
139
|
+
# background 四段完整性检查(中英文任一全套即通过)
|
|
140
|
+
bg = rd.get("background", "") or ""
|
|
141
|
+
cn_segs = ["我是谁", "我的困扰", "我的过往经历", "我今天的心态"]
|
|
142
|
+
en_segs = ["Who I am", "My concern", "My past experience", "My mindset today"]
|
|
143
|
+
cn_missing = [s for s in cn_segs if s not in bg]
|
|
144
|
+
en_missing = [s for s in en_segs if s not in bg]
|
|
145
|
+
if cn_missing and en_missing:
|
|
146
|
+
for s in cn_missing:
|
|
147
|
+
errors.append(f"roles[{i}].role_data.background: 缺四段画像段落 '{s}'")
|
|
148
|
+
|
|
149
|
+
return errors
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ---------- Part3: operations 数组(update_basic + add_conversation_step) ----------
|
|
153
|
+
|
|
154
|
+
def validate_part3(operations: list) -> list[str]:
|
|
155
|
+
errors = []
|
|
156
|
+
|
|
157
|
+
# 开场语
|
|
158
|
+
open_ops = [o for o in operations if o.get("type") == "update_basic"]
|
|
159
|
+
if not open_ops:
|
|
160
|
+
errors.append("缺 update_basic 操作(开场语)")
|
|
161
|
+
else:
|
|
162
|
+
d = open_ops[0].get("data", {})
|
|
163
|
+
od = d.get("opening_dialogue")
|
|
164
|
+
if not isinstance(od, str) or len(od) < 20:
|
|
165
|
+
errors.append(f"opening_dialogue: 必须 ≥20 字符,当前 len={len(od) if isinstance(od, str) else 0}")
|
|
166
|
+
odt = d.get("opening_dialogue_type")
|
|
167
|
+
if odt != 1:
|
|
168
|
+
errors.append(f"opening_dialogue_type: 必须是数字 1(不是字符串),当前 {odt!r}")
|
|
169
|
+
|
|
170
|
+
# 对话环节
|
|
171
|
+
steps = [o for o in operations if o.get("type") == "add_conversation_step"]
|
|
172
|
+
if not (3 <= len(steps) <= 7):
|
|
173
|
+
errors.append(f"对话环节数: 必须 3-7 个,当前 {len(steps)}")
|
|
174
|
+
|
|
175
|
+
for i, s in enumerate(steps):
|
|
176
|
+
d = s.get("data", {})
|
|
177
|
+
# sort_order 平台自动生成(创建时不传,更新时才传)- 不强制
|
|
178
|
+
for k in ["step_name", "student_objective", "ai_task", "max_rounds"]:
|
|
179
|
+
if d.get(k) in (None, "", []):
|
|
180
|
+
errors.append(f"step[{i}].data.{k}: 空/缺失")
|
|
181
|
+
|
|
182
|
+
# sort_order 如果传了,必须是字符串(平台创建时不传也可以)
|
|
183
|
+
so = d.get("sort_order")
|
|
184
|
+
if so is not None and not isinstance(so, str):
|
|
185
|
+
errors.append(f"step[{i}].sort_order: 必须是字符串(如 '1'),当前 type={type(so).__name__}")
|
|
186
|
+
|
|
187
|
+
# max_rounds 必须是整数 3-10
|
|
188
|
+
mr = d.get("max_rounds")
|
|
189
|
+
if not isinstance(mr, int) or not (3 <= mr <= 10):
|
|
190
|
+
errors.append(f"step[{i}].max_rounds: 必须是 3-10 的整数,当前 {mr!r}")
|
|
191
|
+
|
|
192
|
+
# ai_task 应含条件逻辑(中英文任一)和退出条件
|
|
193
|
+
at = d.get("ai_task", "") or ""
|
|
194
|
+
has_if_logic = any(kw in at for kw in ["IF", "如果", "进入条件", "行为分支"]) or "条件" in at
|
|
195
|
+
has_exit = any(kw in at for kw in ["退出", "结束", "退出条件"])
|
|
196
|
+
if not has_if_logic:
|
|
197
|
+
errors.append(f"step[{i}].ai_task: 缺条件逻辑(IF/进入条件/行为分支/如果)")
|
|
198
|
+
if not has_exit:
|
|
199
|
+
errors.append(f"step[{i}].ai_task: 缺退出条件")
|
|
200
|
+
|
|
201
|
+
# 原子性检查:开场语 + 环节应在同一批 operations
|
|
202
|
+
if open_ops and steps:
|
|
203
|
+
# 这里只能检查是否在同一数组,不能检查是否同一次 apply(由 SKILL.md 约束)
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
return errors
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
# ---------- Part4: operations 数组(replace_scoring_criteria_config 双层包装) ----------
|
|
210
|
+
|
|
211
|
+
def validate_part4(operations: list) -> list[str]:
|
|
212
|
+
errors = []
|
|
213
|
+
|
|
214
|
+
p4_ops = [o for o in operations if o.get("type") == "replace_scoring_criteria_config"]
|
|
215
|
+
if not p4_ops:
|
|
216
|
+
errors.append("缺 replace_scoring_criteria_config 操作")
|
|
217
|
+
return errors
|
|
218
|
+
|
|
219
|
+
for ri, op in enumerate(p4_ops):
|
|
220
|
+
config = op.get("data", {}).get("config")
|
|
221
|
+
if not isinstance(config, dict):
|
|
222
|
+
errors.append(f"op[{ri}].data.config: 必须是对象")
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
# v2.26 硬阻断: shell 字段必须在 config 顶层,不在 criteria_data 内
|
|
226
|
+
cd = config.get("criteria_data") or {}
|
|
227
|
+
SHELL_FIELDS = ["scenario_criteria_id", "customization_level", "is_custom_criteria", "customized_fields"]
|
|
228
|
+
for k in SHELL_FIELDS:
|
|
229
|
+
if k not in config:
|
|
230
|
+
errors.append(f"❌ op[{ri}].data.config.{k}: 缺失(v2.26 硬阻断:shell 字段必须在 config 顶层,否则 UI 不显示评分标准)")
|
|
231
|
+
if k in cd:
|
|
232
|
+
errors.append(
|
|
233
|
+
f"❌ op[{ri}]: shell 字段 {k} 被错误放入 criteria_data 内层!"
|
|
234
|
+
f"必须移到 data.config 顶层。错放会导致 UI 显示「请选择或创建评分标准」空状态,API 不报错。"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# 第 2 层 criteria_data 包装(必须存在)
|
|
238
|
+
if not isinstance(cd, dict) or not cd:
|
|
239
|
+
errors.append(f"op[{ri}].data.config.criteria_data: ❌ 缺第 2 层 criteria_data 包装(平台会接受但存储错误)")
|
|
240
|
+
continue
|
|
241
|
+
|
|
242
|
+
# 必填字段(实际影响平台存储和 UI 展示)
|
|
243
|
+
# name/description/dimensions 是 API 硬必填
|
|
244
|
+
for k in ["name", "description", "dimensions"]:
|
|
245
|
+
if cd.get(k) in (None, "", []):
|
|
246
|
+
errors.append(f"op[{ri}].data.config.criteria_data.{k}: 空/缺失(必填)")
|
|
247
|
+
# v2.26: criteria_data 内业务字段硬阻断
|
|
248
|
+
if cd.get("total_weight") not in (100, None):
|
|
249
|
+
errors.append(f"op[{ri}].criteria_data.total_weight: 必须 100,当前 {cd.get('total_weight')!r}")
|
|
250
|
+
for k in ["tags", "source_type", "status"]:
|
|
251
|
+
if k not in cd:
|
|
252
|
+
errors.append(f"op[{ri}].criteria_data.{k}: ❌ 缺失(v2.26 硬阻断)")
|
|
253
|
+
elif k == "tags" and (not isinstance(cd.get(k), list) or len(cd.get(k)) == 0):
|
|
254
|
+
errors.append(f"op[{ri}].criteria_data.tags: 必须是非空数组")
|
|
255
|
+
elif k == "source_type" and cd.get(k) not in ("enterprise", "custom"):
|
|
256
|
+
errors.append(f"op[{ri}].criteria_data.source_type: 必须 'enterprise',当前 {cd.get(k)!r}")
|
|
257
|
+
elif k == "status" and cd.get(k) != 1:
|
|
258
|
+
errors.append(f"op[{ri}].criteria_data.status: 必须 1,当前 {cd.get(k)!r}")
|
|
259
|
+
|
|
260
|
+
if cd.get("source_type") not in ("enterprise", "custom"):
|
|
261
|
+
errors.append(f"op[{ri}].criteria_data.source_type: 应为 'enterprise' 或 'custom',当前 {cd.get('source_type')!r}")
|
|
262
|
+
|
|
263
|
+
# total_weight 是警告不是错误(中文版不加也成功)
|
|
264
|
+
if "total_weight" in cd and cd.get("total_weight") != 100:
|
|
265
|
+
print(f"⚠️ op[{ri}].criteria_data.total_weight: 建议 100,当前 {cd.get('total_weight')!r}", file=sys.stderr)
|
|
266
|
+
|
|
267
|
+
dims = cd.get("dimensions", [])
|
|
268
|
+
if not isinstance(dims, list) or len(dims) < 2:
|
|
269
|
+
errors.append(f"op[{ri}].criteria_data.dimensions: 必须 ≥2 个维度")
|
|
270
|
+
else:
|
|
271
|
+
weight_sum = sum(d.get("weight", 0) for d in dims)
|
|
272
|
+
if weight_sum != 100:
|
|
273
|
+
errors.append(f"op[{ri}].dimensions weight sum: 必须 100,当前 {weight_sum}")
|
|
274
|
+
|
|
275
|
+
for i, d in enumerate(dims):
|
|
276
|
+
# 硬必填(API + UI)
|
|
277
|
+
for k in ["id", "code", "name", "weight", "scoring_rule", "criteria", "sort_order"]:
|
|
278
|
+
if d.get(k) in (None, "", []):
|
|
279
|
+
errors.append(f"op[{ri}].dimensions[{i}].{k}: 空/缺失(必填)")
|
|
280
|
+
# description 建议字段
|
|
281
|
+
if "description" not in d or d.get("description") in (None, "", []):
|
|
282
|
+
print(f"⚠️ op[{ri}].dimensions[{i}].description: 建议字段", file=sys.stderr)
|
|
283
|
+
|
|
284
|
+
# sort_order 必须是数字
|
|
285
|
+
so = d.get("sort_order")
|
|
286
|
+
if not isinstance(so, int):
|
|
287
|
+
errors.append(f"op[{ri}].dimensions[{i}].sort_order: 必须是数字(不是字符串),当前 type={type(so).__name__}")
|
|
288
|
+
|
|
289
|
+
# criteria 至少 2 个
|
|
290
|
+
crit = d.get("criteria", [])
|
|
291
|
+
if not isinstance(crit, list) or len(crit) < 2:
|
|
292
|
+
errors.append(f"op[{ri}].dimensions[{i}].criteria: 至少 2 个评分点")
|
|
293
|
+
|
|
294
|
+
# code 必须是英文
|
|
295
|
+
code = d.get("code", "")
|
|
296
|
+
if code and not code.isascii():
|
|
297
|
+
errors.append(f"op[{ri}].dimensions[{i}].code: 必须是英文,当前 {code!r}")
|
|
298
|
+
|
|
299
|
+
return errors
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# ---------- Part5: operations 数组(update_basic 6 项固定配置) ----------
|
|
303
|
+
|
|
304
|
+
def validate_part5(operations: list) -> list[str]:
|
|
305
|
+
errors = []
|
|
306
|
+
|
|
307
|
+
ops = [o for o in operations if o.get("type") == "update_basic"]
|
|
308
|
+
if not ops:
|
|
309
|
+
errors.append("缺 update_basic 操作")
|
|
310
|
+
return errors
|
|
311
|
+
|
|
312
|
+
d = ops[0].get("data", {})
|
|
313
|
+
# Part5 固定配置(以平台实际接受值为准)
|
|
314
|
+
# min/max_practice_duration 平台允许 0 或正整数(0=不限)
|
|
315
|
+
# max_practice_attempts=10, passing_score=60, completion_rule=all_steps 是平台默认值
|
|
316
|
+
checks = {
|
|
317
|
+
"max_practice_attempts": (lambda v: isinstance(v, int) and v > 0, "正整数"),
|
|
318
|
+
"passing_score": (lambda v: isinstance(v, int) and 0 < v <= 100, "1-100 整数"),
|
|
319
|
+
"completion_rule": (lambda v: v == "all_steps", "'all_steps'"),
|
|
320
|
+
"pass_difficulty": (lambda v: isinstance(v, int) and 1 <= v <= 3, "1-3 整数"),
|
|
321
|
+
}
|
|
322
|
+
for k, (check, hint) in checks.items():
|
|
323
|
+
actual = d.get(k)
|
|
324
|
+
if actual is None:
|
|
325
|
+
errors.append(f"{k}: 缺失(Part5 必填)")
|
|
326
|
+
elif not check(actual):
|
|
327
|
+
errors.append(f"{k}: 必须为 {hint},当前 {actual!r}")
|
|
328
|
+
# ai_assistance_enabled 应为 True
|
|
329
|
+
if d.get("ai_assistance_enabled") is not True:
|
|
330
|
+
errors.append(f"ai_assistance_enabled: 必须为 True,当前 {d.get('ai_assistance_enabled')!r}")
|
|
331
|
+
# 禁止字段(已在 Part1/Part3 同步过)
|
|
332
|
+
for forbidden in ["opening_dialogue", "opening_dialogue_type", "key_points", "name", "description", "tags", "scenario_cover"]:
|
|
333
|
+
if forbidden in d:
|
|
334
|
+
errors.append(f"{forbidden}: ❌ Part5 不应同步此字段(已在 Part1 同步)")
|
|
335
|
+
|
|
336
|
+
return errors
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
# ---------- 入口 ----------
|
|
340
|
+
|
|
341
|
+
def load_json(path: str) -> Any:
|
|
342
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
343
|
+
return json.load(f)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def main() -> int:
|
|
347
|
+
if len(sys.argv) != 3:
|
|
348
|
+
print(__doc__, file=sys.stderr)
|
|
349
|
+
return 2
|
|
350
|
+
|
|
351
|
+
part = sys.argv[1].lower().strip()
|
|
352
|
+
path = sys.argv[2]
|
|
353
|
+
|
|
354
|
+
if not os.path.isfile(path):
|
|
355
|
+
print(f"❌ 文件不存在: {path}", file=sys.stderr)
|
|
356
|
+
return 2
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
data = load_json(path)
|
|
360
|
+
except json.JSONDecodeError as e:
|
|
361
|
+
print(f"❌ JSON 解析失败: {e}", file=sys.stderr)
|
|
362
|
+
return 2
|
|
363
|
+
|
|
364
|
+
validators = {
|
|
365
|
+
"part1": (validate_part1, "dict"),
|
|
366
|
+
"part2": (validate_part2, "list"),
|
|
367
|
+
"part3": (validate_part3, "list"),
|
|
368
|
+
"part4": (validate_part4, "list"),
|
|
369
|
+
"part5": (validate_part5, "list"),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if part not in validators:
|
|
373
|
+
print(f"❌ 未知 part: {part},支持: {list(validators.keys())}", file=sys.stderr)
|
|
374
|
+
return 2
|
|
375
|
+
|
|
376
|
+
func, expected_type = validators[part]
|
|
377
|
+
if expected_type == "dict" and not isinstance(data, dict):
|
|
378
|
+
print(f"❌ {part} 期望 JSON 对象,实际 {type(data).__name__}", file=sys.stderr)
|
|
379
|
+
return 1
|
|
380
|
+
# 支持两种格式:
|
|
381
|
+
# 1. 纯 operations 数组 [...]
|
|
382
|
+
# 2. CLI request-file 对象 {operations: [...], idempotency_key, ...}
|
|
383
|
+
if expected_type == "list":
|
|
384
|
+
if isinstance(data, dict) and "operations" in data:
|
|
385
|
+
data = data["operations"]
|
|
386
|
+
if not isinstance(data, list):
|
|
387
|
+
print(f"❌ {part} 期望 JSON 数组或 {{operations:[...]}} 对象,实际 {type(data).__name__}", file=sys.stderr)
|
|
388
|
+
return 1
|
|
389
|
+
|
|
390
|
+
errors = func(data)
|
|
391
|
+
|
|
392
|
+
if not errors:
|
|
393
|
+
print(f"✅ {part} 验证通过({len(errors)} 个错误)")
|
|
394
|
+
return 0
|
|
395
|
+
else:
|
|
396
|
+
print(f"❌ {part} 验证失败({len(errors)} 个错误):", file=sys.stderr)
|
|
397
|
+
for e in errors:
|
|
398
|
+
print(f" - {e}", file=sys.stderr)
|
|
399
|
+
return 1
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
if __name__ == "__main__":
|
|
403
|
+
sys.exit(main())
|