@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.
Files changed (123) hide show
  1. package/README.md +7 -7
  2. package/package.json +1 -1
  3. package/scripts/build-binaries.sh +1 -1
  4. package/scripts/e2e-test.sh +75 -3
  5. package/skills/SKILL.md +18 -1
  6. package/skills/soke-ai-coach-director/CHANGELOG.md +203 -0
  7. package/skills/soke-ai-coach-director/README.md +123 -0
  8. package/skills/soke-ai-coach-director/SKILL.md +221 -0
  9. package/skills/soke-ai-coach-director/coaching/knowledge-packager/knowledge-packager.md +91 -0
  10. package/skills/soke-ai-coach-director/coaching/prompt-engineer/parts/part2-roles.md +97 -0
  11. package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-engineer/parts/part3-flow.md +3 -3
  12. package/skills/soke-ai-coach-director/coaching/prompt-engineer/prompt-engineer.md +76 -0
  13. package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-optimizer/prompt-optimizer.md +77 -58
  14. package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/workbuddy.md +2 -3
  15. package/skills/soke-ai-coach-director/platform/interaction.md +103 -0
  16. package/skills/{ai-coach-director → soke-ai-coach-director}/platform/publish-gate.md +3 -3
  17. package/skills/{ai-coach-director → soke-ai-coach-director}/platform/resource-finalizer.md +10 -35
  18. package/skills/{ai-coach-director → soke-ai-coach-director}/platform/soke-ai-training/soke-ai-training.md +33 -3
  19. package/skills/soke-ai-coach-director/platform/sync-engine.md +39 -0
  20. package/skills/{ai-coach-director → soke-ai-coach-director}/references/change-log-partwise-refactor.md +1 -1
  21. package/skills/soke-ai-coach-director/references/env-check.md +146 -0
  22. package/skills/soke-ai-coach-director/references/exec-common.md +126 -0
  23. package/skills/soke-ai-coach-director/references/exec-handbook.md +621 -0
  24. package/skills/soke-ai-coach-director/references/exec-part1.md +86 -0
  25. package/skills/soke-ai-coach-director/references/exec-part2.md +145 -0
  26. package/skills/soke-ai-coach-director/references/exec-part3.md +91 -0
  27. package/skills/soke-ai-coach-director/references/exec-part4.md +187 -0
  28. package/skills/soke-ai-coach-director/references/gate-check-matrix.md +46 -0
  29. package/skills/soke-ai-coach-director/references/micro-tune-labels.md +30 -0
  30. package/skills/soke-ai-coach-director/references/platform-api-pitfalls.md +49 -0
  31. package/skills/soke-ai-coach-director/references/role-resource-matching.md +45 -0
  32. package/skills/{ai-coach-director → soke-ai-coach-director}/references/self-improvement-examples.md +2 -0
  33. package/skills/soke-ai-coach-director/references/self-learning-engine.md +489 -0
  34. package/skills/soke-ai-coach-director/references/subs/part1/SOP.md +17 -0
  35. package/skills/soke-ai-coach-director/references/subs/part2/SOP.md +34 -0
  36. package/skills/soke-ai-coach-director/references/subs/part3/SOP.md +27 -0
  37. package/skills/soke-ai-coach-director/references/subs/part4/SOP.md +31 -0
  38. package/skills/soke-ai-coach-director/references/subs/publish/SOP.md +81 -0
  39. package/skills/soke-ai-coach-director/references/subs/tune/SOP.md +92 -0
  40. package/skills/soke-ai-coach-director/references/verified-cli-cheatsheet.md +56 -0
  41. package/skills/{ai-coach-director → soke-ai-coach-director}/scripts/activator.sh +0 -0
  42. package/skills/{ai-coach-director → soke-ai-coach-director}/scripts/error-detector.sh +0 -0
  43. package/skills/{ai-coach-director → soke-ai-coach-director}/scripts/extract-skill.sh +0 -0
  44. package/skills/soke-ai-coach-director/scripts/gate_check.py +112 -0
  45. package/skills/soke-ai-coach-director/scripts/validate_operations.py +403 -0
  46. package/skills/soke-certificate/SKILL.md +717 -0
  47. package/skills/soke-course/README.md +6 -2
  48. package/skills/soke-course/SKILL.md +3 -3
  49. package/skills/soke-course/references/course-list-courses.md +49 -84
  50. package/skills/soke-course/references/intent-cases.md +2 -2
  51. package/skills/soke-course/references/sop-full-workflow.md +4 -2
  52. package/skills/soke-course/references/subs/publish/SOP.md +21 -3
  53. package/skills/soke-course/references/subs/query/SOP.md +9 -7
  54. package/skills/soke-course/references/subs/settings/SOP.md +2 -1
  55. package/skills/soke-exam-question-pool/SKILL.md +2 -2
  56. package/skills/soke-task/SKILL.md +32 -10
  57. package/skills/soke-training-demand/SKILL.md +2 -2
  58. package/skills/ai-coach-director/SKILL.md +0 -1563
  59. package/skills/ai-coach-director/coaching/prompt-engineer/parts/part2-roles.md +0 -317
  60. package/skills/ai-coach-director/coaching/prompt-engineer/prompt-engineer.md +0 -244
  61. package/skills/ai-coach-director/platform/interaction.md +0 -315
  62. package/skills/ai-coach-director/platform/sync-engine.md +0 -1101
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. 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
  70. 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
  71. 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
  72. 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
  73. 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
  74. 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
  75. 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
  76. 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
  77. package/skills/ai-coach-director/references/env-check.md +0 -167
  78. package/skills/ai-coach-director/references/platform-api-pitfalls.md +0 -232
  79. package/skills/ai-coach-director/references/role-resource-matching.md +0 -349
  80. package/skills/ai-coach-director/references/verified-cli-cheatsheet.md +0 -203
  81. /package/skills/{ai-coach-director → soke-ai-coach-director}/assets/ERRORS-TEMPLATE.md +0 -0
  82. /package/skills/{ai-coach-director → soke-ai-coach-director}/assets/FEATURE_REQUESTS-TEMPLATE.md +0 -0
  83. /package/skills/{ai-coach-director → soke-ai-coach-director}/assets/LEARNINGS-TEMPLATE.md +0 -0
  84. /package/skills/{ai-coach-director → soke-ai-coach-director}/assets/SKILL-TEMPLATE.md +0 -0
  85. /package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-engineer/parts/part1-basic.md +0 -0
  86. /package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-engineer/parts/part4-scoring.md +0 -0
  87. /package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/prompt-engineer/parts/part5-constraints.md +0 -0
  88. /package/skills/{ai-coach-director → soke-ai-coach-director}/coaching/role-profiler/role-profiler.md +0 -0
  89. /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/README.md +0 -0
  90. /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/qclaw.md +0 -0
  91. /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/wukong.md +0 -0
  92. /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/adapters/zework.md +0 -0
  93. /package/skills/{ai-coach-director → soke-ai-coach-director}/platform/api-fallback.md +0 -0
  94. /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
  95. /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
  96. /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
  97. /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
  98. /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
  99. /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
  100. /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
  101. /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
  102. /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
  103. /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
  104. /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
  105. /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
  106. /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
  107. /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
  108. /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
  109. /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
  110. /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
  111. /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
  112. /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
  113. /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
  114. /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
  115. /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
  116. /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
  117. /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
  118. /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
  119. /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
  120. /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
  121. /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
  122. /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
  123. /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())