feat: install korean technical blog skill bundle

.agents/skills의 technical-document-author,
revising-korean-technical-prose, writing-natural-korean을
korean-technical-blog-skills-bundle-v1의 세 스킬로 교체한다.

- writing-korean-technical-blogs: 문제·제약·선택·구현·결과·한계 구조화
- reducing-ai-like-korean-writing: 상투성·추상화·반복 제거
- editing-korean-grammar-and-expression: 맞춤법·띄어쓰기·호응 검수

상류를 수정하지 않고 복사했다. diff -r 0건, MANIFEST.sha256 검증 통과,
번들 validate_skill.py 3/3 PASS.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-08-07 14:20:42 +09:00
co-authored by Claude Opus 5
parent 1099834617
commit 25644cc4d9
81 changed files with 5261 additions and 1484 deletions
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REQUIRED = [
ROOT / "SKILL.md",
ROOT / "references" / "decision-policy.md",
ROOT / "references" / "rule-catalog.md",
ROOT / "references" / "output-modes.md",
ROOT / "tests" / "cases.json",
ROOT / "tests" / "evaluation-rubric.md",
]
def fail(message: str) -> None:
print(f"FAIL: {message}")
raise SystemExit(1)
def parse_frontmatter(text: str) -> dict[str, str]:
match = re.match(r"^---\n(.*?)\n---\n", text, re.S)
if not match:
fail("SKILL.md must begin with YAML frontmatter")
data: dict[str, str] = {}
for line in match.group(1).splitlines():
if not line.strip() or line.lstrip().startswith("#"):
continue
if ":" not in line:
fail(f"invalid frontmatter line: {line!r}")
key, value = line.split(":", 1)
data[key.strip()] = value.strip().strip('"').strip("'")
return data
def main() -> None:
missing = [str(path.relative_to(ROOT)) for path in REQUIRED if not path.exists()]
if missing:
fail("missing required files: " + ", ".join(missing))
skill_text = (ROOT / "SKILL.md").read_text(encoding="utf-8")
frontmatter = parse_frontmatter(skill_text)
name = frontmatter.get("name", "")
description = frontmatter.get("description", "")
if name != ROOT.name:
fail(f"frontmatter name {name!r} must match directory {ROOT.name!r}")
if not re.fullmatch(r"[A-Za-z0-9-]+", name):
fail("name must contain only letters, numbers, and hyphens")
if not description.startswith("Use when "):
fail("description must start with 'Use when '")
if len((name + description).encode("utf-8")) > 1024:
fail("name + description frontmatter exceeds 1024 bytes")
if "cite" in skill_text or "turn" in frontmatter.get("description", ""):
fail("runtime-specific citation markers must not appear in SKILL.md")
cases = json.loads((ROOT / "tests" / "cases.json").read_text(encoding="utf-8"))
if not isinstance(cases, list) or not cases:
fail("tests/cases.json must be a non-empty array")
ids: set[str] = set()
allowed_actions = {"correct", "keep", "suggest", "review"}
required_keys = {"id", "category", "input", "expected_text", "expected_action", "rule_id", "explanation"}
for index, case in enumerate(cases):
missing_keys = required_keys - set(case)
if missing_keys:
fail(f"case #{index} missing keys: {sorted(missing_keys)}")
if case["id"] in ids:
fail(f"duplicate case id: {case['id']}")
ids.add(case["id"])
if case["expected_action"] not in allowed_actions:
fail(f"invalid expected_action in {case['id']}: {case['expected_action']}")
print(f"PASS: package structure valid; {len(cases)} test cases loaded")
if __name__ == "__main__":
main()