feat: 初始化高校人事请假考勤技能 (process-leave-attendance-default)
All checks were successful
技能自动化发布 / release (push) Successful in 15s

基于 skill-template 脚手架开发,包含:
- 5 张业务表(教职工、请假政策、考勤日历、请假记录、假期余额)
- 请假核算引擎(扣薪计算、审批层级判定、年假抵扣)
- CLI 入口(import-employees / calculate-leave / render-html)
- HTML 审批与扣薪报告生成
- 示例数据与完整测试套件(295 tests passed)
This commit is contained in:
chenfuqiang
2026-09-07 16:59:24 +08:00
commit ee2a4d9e22
206 changed files with 29510 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-
"""skill-template 脚手架与 Git 防串库守护。"""
from __future__ import annotations
import json
import os
import tempfile
import unittest
from _support import get_skill_root
TEMPLATE_MARKER = ".openclaw-skill-template"
TEMPLATE_SLUG_KEBAB = "your-skill-slug"
TEMPLATE_SLUG_UNDERSCORE = "your_skill_slug"
SCAFFOLD_TEST_SLUG = "query-demo-records"
SCAFFOLD_TEST_SLUG_UNDERSCORE = "query_demo_records"
def _read_skill_slug() -> str:
constants_path = os.path.join(get_skill_root(), "scripts", "util", "constants.py")
with open(constants_path, encoding="utf-8") as f:
for line in f:
if line.strip().startswith("SKILL_SLUG"):
_, _, value = line.partition("=")
return value.strip().strip('"').strip("'")
return ""
class TestScaffoldGuard(unittest.TestCase):
def test_template_repo_has_marker(self) -> None:
slug = _read_skill_slug()
if slug != TEMPLATE_SLUG_KEBAB:
self.skipTest(f"当前 SKILL_SLUG={slug!r},非模板占位,跳过 template marker 断言")
marker = os.path.join(get_skill_root(), TEMPLATE_MARKER)
self.assertTrue(
os.path.isfile(marker),
msg=f"skill-template 源仓库根目录必须存在 {TEMPLATE_MARKER}",
)
def test_business_skill_must_not_keep_template_marker(self) -> None:
slug = _read_skill_slug()
if slug == TEMPLATE_SLUG_KEBAB:
self.skipTest("当前仍为模板占位 slug,由 test_template_repo_has_marker 守护")
marker = os.path.join(get_skill_root(), TEMPLATE_MARKER)
self.assertFalse(
os.path.isfile(marker),
msg=(
"业务技能不得保留 .openclaw-skill-template;"
"请使用 tools/scaffold_skill 或删除标记文件并检查是否误复制了模板 .git"
),
)
def test_development_md_documents_git_cleanup(self) -> None:
path = os.path.join(get_skill_root(), "development", "DEVELOPMENT.md")
with open(path, encoding="utf-8") as f:
text = f.read()
self.assertIn(".git", text)
self.assertTrue(
"scaffold_skill" in text or "scaffold" in text.lower(),
msg="DEVELOPMENT.md must document scaffold_skill",
)
self.assertTrue(
"remote" in text.lower() or "git remote" in text.lower(),
msg="DEVELOPMENT.md must mention git remote self-check",
)
def test_scaffold_replaces_slug_and_copies_actions_manifest(self) -> None:
slug = _read_skill_slug()
if slug != TEMPLATE_SLUG_KEBAB:
self.skipTest("仅在 skill-template 源仓库运行 scaffold 集成测试")
import subprocess
import sys
repo_root = get_skill_root()
scaffold_py = os.path.join(repo_root, "tools", "scaffold_skill.py")
with tempfile.TemporaryDirectory() as tmp:
dest = os.path.join(tmp, SCAFFOLD_TEST_SLUG)
proc = subprocess.run(
[
sys.executable,
scaffold_py,
"--slug",
SCAFFOLD_TEST_SLUG,
"--destination",
dest,
],
cwd=repo_root,
capture_output=True,
text=True,
)
self.assertEqual(proc.returncode, 0, msg=proc.stderr or proc.stdout)
actions_path = os.path.join(dest, "assets", "actions.json")
self.assertTrue(os.path.isfile(actions_path), msg="scaffold must copy assets/actions.json")
with open(actions_path, encoding="utf-8") as f:
manifest = json.load(f)
self.assertEqual(manifest.get("skill"), SCAFFOLD_TEST_SLUG)
manifest_raw = json.dumps(manifest)
self.assertNotIn(TEMPLATE_SLUG_KEBAB, manifest_raw)
self.assertNotIn(TEMPLATE_SLUG_UNDERSCORE, manifest_raw)
actions_md = os.path.join(dest, "references", "ACTIONS.md")
self.assertTrue(os.path.isfile(actions_md), msg="scaffold must copy references/ACTIONS.md")
schema_path = os.path.join(dest, "assets", "schemas", "skill-actions.schema.json")
self.assertTrue(os.path.isfile(schema_path))
marker = os.path.join(dest, TEMPLATE_MARKER)
self.assertFalse(os.path.isfile(marker))
self.assertFalse(os.path.isdir(os.path.join(dest, ".git")))
constants_path = os.path.join(dest, "scripts", "util", "constants.py")
with open(constants_path, encoding="utf-8") as f:
constants_text = f.read()
self.assertNotIn(TEMPLATE_SLUG_KEBAB, constants_text)
self.assertNotIn(TEMPLATE_SLUG_UNDERSCORE, constants_text)
self.assertIn(f'SKILL_SLUG = "{SCAFFOLD_TEST_SLUG}"', constants_text)
self.assertIn(
f'LOG_LOGGER_NAME = "openclaw.skill.{SCAFFOLD_TEST_SLUG_UNDERSCORE}"',
constants_text,
)
skill_md_path = os.path.join(dest, "SKILL.md")
with open(skill_md_path, encoding="utf-8") as f:
skill_md = f.read()
self.assertIn(f"slug: {SCAFFOLD_TEST_SLUG}", skill_md)
self.assertNotIn(TEMPLATE_SLUG_KEBAB, skill_md)
self.assertNotIn(TEMPLATE_SLUG_UNDERSCORE, skill_md)
def test_scaffold_ps1_matches_python_placeholder_rules(self) -> None:
slug = _read_skill_slug()
if slug != TEMPLATE_SLUG_KEBAB:
self.skipTest("仅在 skill-template 源仓库运行 scaffold PS1 文案测试")
ps1_path = os.path.join(get_skill_root(), "tools", "scaffold_skill.ps1")
with open(ps1_path, encoding="utf-8") as f:
text = f.read()
self.assertIn("your-skill-slug", text)
self.assertIn("your_skill_slug", text)
self.assertIn('.Replace("-", "_")', text)
if __name__ == "__main__":
unittest.main()