mirror of
https://github.com/xr843/Master-skill.git
synced 2026-05-10 05:16:25 +00:00
02df9344b5
- Add Layer 0 hard rule to all 8 masters' voice.md: first turn must use neutral address (您/汝/你/问者), forbidden terms include 居士/行者/学人/ 善男子/善女人/出家人/师父/大众/道友/善信/道友 - From turn 2+: masters adapt to user's self-disclosed or question-inferred identity, restoring each master's historical address style - Layer 2 开场方式/称呼方式 reorganized into 首轮中立 / 身份已知后 tiers - Update voice_builder.md and voice_analyzer.md templates so future /create-master runs inherit this rule - Add tools/sync_skill_from_voice.py to keep SKILL.md PART B in sync - Add 48 regression tests in test_voice_rules.py (all 79 tests pass)
113 lines
3.4 KiB
Python
113 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Sync SKILL.md PART B from voice.md (single source of truth).
|
|
|
|
SKILL.md contains voice.md content inlined as PART B. When voice.md changes,
|
|
SKILL.md must be regenerated to keep them in sync.
|
|
|
|
Usage:
|
|
python3 tools/sync_skill_from_voice.py --all # sync all masters
|
|
python3 tools/sync_skill_from_voice.py --slug xuyun # sync one master
|
|
python3 tools/sync_skill_from_voice.py --verify # check sync status only
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
PREBUILT_DIR = Path(__file__).parent.parent / "prebuilt"
|
|
|
|
# Section markers in SKILL.md
|
|
PART_B_START = "## PART B — 说法风格"
|
|
PART_C_START = "## 运行规则"
|
|
|
|
|
|
def sync_one(slug: str, verify_only: bool = False) -> bool:
|
|
"""Sync one master's SKILL.md PART B from voice.md.
|
|
|
|
Returns True if in sync (or successfully synced), False if mismatch found
|
|
in verify mode.
|
|
"""
|
|
master_dir = PREBUILT_DIR / slug
|
|
voice_path = master_dir / "voice.md"
|
|
skill_path = master_dir / "SKILL.md"
|
|
|
|
if not voice_path.exists() or not skill_path.exists():
|
|
print(f"[SKIP] {slug}: missing voice.md or SKILL.md")
|
|
return False
|
|
|
|
voice_content = voice_path.read_text(encoding="utf-8")
|
|
skill_content = skill_path.read_text(encoding="utf-8")
|
|
|
|
# Voice.md starts with a # Title line. Skip it.
|
|
voice_lines = voice_content.split("\n")
|
|
if voice_lines and voice_lines[0].startswith("# "):
|
|
voice_body = "\n".join(voice_lines[1:]).lstrip("\n")
|
|
else:
|
|
voice_body = voice_content
|
|
|
|
# Find PART B and 运行规则 boundaries
|
|
b_idx = skill_content.find(PART_B_START)
|
|
c_idx = skill_content.find(PART_C_START)
|
|
|
|
if b_idx == -1 or c_idx == -1:
|
|
print(f"[ERR] {slug}: cannot find PART B or 运行规则 markers")
|
|
return False
|
|
|
|
# Build new SKILL.md
|
|
# Keep everything up to and including PART B header + blank line
|
|
header = skill_content[:b_idx] + PART_B_START + "\n\n"
|
|
# Insert voice.md body
|
|
new_part_b = voice_body.rstrip() + "\n\n"
|
|
# Append everything from 运行规则 onwards
|
|
tail = skill_content[c_idx:]
|
|
|
|
new_skill_content = header + new_part_b + tail
|
|
|
|
if verify_only:
|
|
if new_skill_content != skill_content:
|
|
print(f"[OUT OF SYNC] {slug}")
|
|
return False
|
|
else:
|
|
print(f"[OK] {slug}")
|
|
return True
|
|
|
|
if new_skill_content != skill_content:
|
|
skill_path.write_text(new_skill_content, encoding="utf-8")
|
|
print(f"[SYNCED] {slug}")
|
|
else:
|
|
print(f"[OK] {slug}")
|
|
return True
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Sync SKILL.md PART B from voice.md")
|
|
parser.add_argument("--slug", help="Sync one specific master")
|
|
parser.add_argument("--all", action="store_true", help="Sync all masters")
|
|
parser.add_argument("--verify", action="store_true", help="Only verify, don't modify")
|
|
args = parser.parse_args()
|
|
|
|
if args.slug:
|
|
slugs = [args.slug]
|
|
elif args.all or args.verify:
|
|
slugs = sorted(
|
|
d.name for d in PREBUILT_DIR.iterdir()
|
|
if d.is_dir() and (d / "voice.md").exists()
|
|
)
|
|
else:
|
|
parser.print_help()
|
|
return 1
|
|
|
|
all_ok = True
|
|
for slug in slugs:
|
|
if not sync_one(slug, verify_only=args.verify):
|
|
all_ok = False
|
|
|
|
return 0 if all_ok else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|