quality_check.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. #!/usr/bin/env python3
  2. """
  3. 自动检查生成的SKILL.md是否通过Phase 4质量标准。
  4. 对照通过标准表格逐项检查,输出通过/不通过和具体原因。
  5. 用法:
  6. python3 quality_check.py <SKILL.md路径>
  7. 示例:
  8. python3 quality_check.py .claude/skills/elon-musk-perspective/SKILL.md
  9. """
  10. import sys
  11. import re
  12. from pathlib import Path
  13. def check_mental_models(content: str) -> tuple[bool, str]:
  14. """检查心智模型数量(3-7个)"""
  15. # 匹配 ### 模型N: 或 ### N. 等模式
  16. models = re.findall(r'^###\s+(?:模型|Model|心智模型)\s*\d', content, re.MULTILINE)
  17. if not models:
  18. # fallback: 数「### 」开头的行在心智模型section中
  19. in_section = False
  20. count = 0
  21. for line in content.split('\n'):
  22. if re.match(r'^##\s+.*心智模型|Mental Model', line, re.IGNORECASE):
  23. in_section = True
  24. continue
  25. if in_section and re.match(r'^##\s+', line) and '心智模型' not in line:
  26. break
  27. if in_section and re.match(r'^###\s+', line):
  28. count += 1
  29. if count > 0:
  30. passed = 3 <= count <= 7
  31. return passed, f"{count}个心智模型 {'✅' if passed else '❌ (应为3-7个)'}"
  32. count = len(models)
  33. if count == 0:
  34. return False, "未检测到心智模型section"
  35. passed = 3 <= count <= 7
  36. return passed, f"{count}个心智模型 {'✅' if passed else '❌ (应为3-7个)'}"
  37. def check_limitations(content: str) -> tuple[bool, str]:
  38. """检查每个模型是否有局限性"""
  39. has_limitation = bool(re.search(r'局限|失效|不适用|盲区|limitation|blind spot', content, re.IGNORECASE))
  40. return has_limitation, "有局限性标注 ✅" if has_limitation else "❌ 未找到局限性描述"
  41. def check_expression_dna(content: str) -> tuple[bool, str]:
  42. """检查表达DNA辨识度"""
  43. dna_section = bool(re.search(r'表达DNA|Expression DNA|表达风格', content, re.IGNORECASE))
  44. if not dna_section:
  45. return False, "❌ 未找到表达DNA section"
  46. # 检查是否有具体的风格描述(句式、词汇等)
  47. style_markers = len(re.findall(r'句式|词汇|语气|幽默|节奏|确定性|引用|口头禅', content))
  48. passed = style_markers >= 3
  49. return passed, f"表达DNA特征: {style_markers}项 {'✅' if passed else '❌ (应≥3项)'}"
  50. def check_honest_boundary(content: str) -> tuple[bool, str]:
  51. """检查诚实边界(至少3条)"""
  52. # 找诚实边界section
  53. boundary_match = re.search(r'(?:##\s+.*诚实边界|## Honest Boundary)(.*?)(?=\n##\s|\Z)', content, re.DOTALL | re.IGNORECASE)
  54. if not boundary_match:
  55. return False, "❌ 未找到诚实边界section"
  56. boundary_text = boundary_match.group(1)
  57. # 计算列表项
  58. items = re.findall(r'^[-*]\s+', boundary_text, re.MULTILINE)
  59. count = len(items)
  60. passed = count >= 3
  61. return passed, f"诚实边界: {count}条 {'✅' if passed else '❌ (应≥3条)'}"
  62. def check_tensions(content: str) -> tuple[bool, str]:
  63. """检查内在张力(至少2对)"""
  64. tension_markers = len(re.findall(r'张力|矛盾|tension|paradox|一方面.*另一方面|既.*又', content, re.IGNORECASE))
  65. passed = tension_markers >= 2
  66. return passed, f"内在张力: {tension_markers}处 {'✅' if passed else '❌ (应≥2处)'}"
  67. def check_primary_sources(content: str) -> tuple[bool, str]:
  68. """检查一手来源占比"""
  69. # 找调研来源section
  70. source_section = re.search(r'(?:##\s+.*来源|## Source|## Reference)(.*?)(?=\n##\s|\Z)', content, re.DOTALL | re.IGNORECASE)
  71. if not source_section:
  72. return True, "未找到来源section(跳过检查)"
  73. source_text = source_section.group(1)
  74. primary = len(re.findall(r'一手|primary|本人著作|原始', source_text, re.IGNORECASE))
  75. secondary = len(re.findall(r'二手|secondary|转述|评论', source_text, re.IGNORECASE))
  76. total = primary + secondary
  77. if total == 0:
  78. return True, "未标记来源类型(跳过检查)"
  79. ratio = primary / total
  80. passed = ratio > 0.5
  81. return passed, f"一手来源占比: {primary}/{total} ({ratio:.0%}) {'✅' if passed else '❌ (应>50%)'}"
  82. def main():
  83. if len(sys.argv) < 2:
  84. print("用法: python3 quality_check.py <SKILL.md路径>")
  85. sys.exit(1)
  86. skill_path = Path(sys.argv[1])
  87. if not skill_path.exists():
  88. print(f"❌ 文件不存在: {skill_path}")
  89. sys.exit(1)
  90. content = skill_path.read_text(encoding='utf-8')
  91. checks = [
  92. ("心智模型数量", check_mental_models),
  93. ("模型局限性", check_limitations),
  94. ("表达DNA辨识度", check_expression_dna),
  95. ("诚实边界", check_honest_boundary),
  96. ("内在张力", check_tensions),
  97. ("一手来源占比", check_primary_sources),
  98. ]
  99. print(f"质量检查: {skill_path.name}")
  100. print("=" * 50)
  101. passed_count = 0
  102. total = len(checks)
  103. for name, check_fn in checks:
  104. passed, detail = check_fn(content)
  105. status = "✅ PASS" if passed else "❌ FAIL"
  106. print(f" {name:<12} {status} {detail}")
  107. if passed:
  108. passed_count += 1
  109. print("=" * 50)
  110. print(f"结果: {passed_count}/{total} 通过")
  111. if passed_count == total:
  112. print("🎉 全部通过,可以交付")
  113. elif passed_count >= total - 1:
  114. print("⚠️ 基本通过,建议修复不通过项后交付")
  115. else:
  116. print("❌ 多项不通过,建议回到Phase 2迭代")
  117. sys.exit(0 if passed_count == total else 1)
  118. if __name__ == '__main__':
  119. main()