feat: add skill writer for teacher directory management

This commit is contained in:
xianren
2026-04-04 17:32:39 +08:00
parent 8db581ca06
commit 1f02953d46
+165
View File
@@ -0,0 +1,165 @@
"""
Skill Writer — creates and updates teacher skill directories.
Adapted from colleague-skill's skill_writer.py for Buddhist teacher context.
"""
import json
import os
import shutil
from datetime import datetime
from typing import Optional
try:
from pypinyin import lazy_pinyin, Style
HAS_PYPINYIN = True
except ImportError:
HAS_PYPINYIN = False
SKILL_MD_TEMPLATE = """---
name: teacher_{slug}
description: 依据{name}{tradition}{school})的教学风格与教义体系
user-invocable: true
---
# {name}
{disclaimer}
---
## PART A — 教义体系
{teaching_content}
## PART B — 说法风格
{voice_content}
## 运行规则
1. 收到提问后,先依据 voice.md Layer 0 硬规则检查
2. 依据 voice.md Layer 1-3 确定回答的风格和方式
3. 依据 teaching.md 检索相关教义内容
4. 以该法师的风格组织回答
5. 必须附经文出处,格式:【《经名》卷N】→ https://fojin.app/texts/{text_id}
6. 遇到超出范围的问题,坦诚说明并建议查阅相关传承
"""
DISCLAIMER = "本内容依据历史佛教文献生成,仅供参考学习。如需正式修行指导,请亲近善知识。所有回答均附经文出处,可通过 FoJin (fojin.app) 查阅原文。"
def slugify(name: str) -> str:
"""Convert teacher name to URL-safe slug."""
if HAS_PYPINYIN:
pinyin_list = lazy_pinyin(name, style=Style.NORMAL)
slug = "-".join(pinyin_list)
else:
slug = name.lower().replace(" ", "-")
slug = "".join(c for c in slug if c.isalnum() or c == "-")
slug = slug.strip("-")
return slug
def create_teacher(
base_dir: str,
name: str,
tradition: str,
school: str,
era: str,
languages: list,
teaching_content: str,
voice_content: str,
fojin_entity_id: Optional[str] = None,
sources: Optional[list] = None,
) -> str:
"""Create a new teacher skill directory."""
slug = slugify(name)
teacher_dir = os.path.join(base_dir, slug)
os.makedirs(teacher_dir, exist_ok=True)
os.makedirs(os.path.join(teacher_dir, "versions"), exist_ok=True)
with open(os.path.join(teacher_dir, "teaching.md"), "w", encoding="utf-8") as f:
f.write(teaching_content)
with open(os.path.join(teacher_dir, "voice.md"), "w", encoding="utf-8") as f:
f.write(voice_content)
skill_content = SKILL_MD_TEMPLATE.format(
slug=slug, name=name, tradition=tradition, school=school,
disclaimer=DISCLAIMER, teaching_content=teaching_content,
voice_content=voice_content,
)
with open(os.path.join(teacher_dir, "SKILL.md"), "w", encoding="utf-8") as f:
f.write(skill_content)
meta = {
"name": name, "slug": slug, "tradition": tradition, "school": school,
"era": era, "languages": languages, "fojin_entity_id": fojin_entity_id,
"sources": sources or [], "version": "1.0.0",
"created_at": datetime.now().strftime("%Y-%m-%d"),
"updated_at": datetime.now().strftime("%Y-%m-%d"),
"disclaimer": DISCLAIMER,
}
with open(os.path.join(teacher_dir, "meta.json"), "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
return teacher_dir
def update_teacher(teacher_dir: str, teaching_patch: Optional[str] = None, voice_patch: Optional[str] = None) -> str:
"""Update an existing teacher skill with new content. Archives current version before updating."""
meta_path = os.path.join(teacher_dir, "meta.json")
with open(meta_path, "r", encoding="utf-8") as f:
meta = json.load(f)
version = meta.get("version", "1.0.0")
version_dir = os.path.join(teacher_dir, "versions", f"v{version}")
os.makedirs(version_dir, exist_ok=True)
for fname in ["SKILL.md", "teaching.md", "voice.md", "meta.json"]:
src = os.path.join(teacher_dir, fname)
if os.path.exists(src):
shutil.copy2(src, version_dir)
if teaching_patch:
with open(os.path.join(teacher_dir, "teaching.md"), "a", encoding="utf-8") as f:
f.write("\n\n" + teaching_patch)
if voice_patch:
with open(os.path.join(teacher_dir, "voice.md"), "a", encoding="utf-8") as f:
f.write("\n\n" + voice_patch)
parts = version.split(".")
parts[1] = str(int(parts[1]) + 1)
new_version = ".".join(parts)
meta["version"] = new_version
meta["updated_at"] = datetime.now().strftime("%Y-%m-%d")
with open(meta_path, "w", encoding="utf-8") as f:
json.dump(meta, f, ensure_ascii=False, indent=2)
teaching_content = open(os.path.join(teacher_dir, "teaching.md"), encoding="utf-8").read()
voice_content = open(os.path.join(teacher_dir, "voice.md"), encoding="utf-8").read()
skill_content = SKILL_MD_TEMPLATE.format(
slug=meta["slug"], name=meta["name"], tradition=meta["tradition"],
school=meta["school"], disclaimer=DISCLAIMER,
teaching_content=teaching_content, voice_content=voice_content,
)
with open(os.path.join(teacher_dir, "SKILL.md"), "w", encoding="utf-8") as f:
f.write(skill_content)
return new_version
def list_teachers(base_dir: str) -> list:
"""List all teacher skills in a directory."""
teachers = []
if not os.path.exists(base_dir):
return teachers
for entry in sorted(os.listdir(base_dir)):
meta_path = os.path.join(base_dir, entry, "meta.json")
if os.path.isfile(meta_path):
with open(meta_path, "r", encoding="utf-8") as f:
meta = json.load(f)
teachers.append(meta)
return teachers