feat: Add OurDigital custom skills package (10 skills)

Complete implementation of OurDigital skills with dual-platform support
(Claude Desktop + Claude Code) following standardized structure.

Skills created:
- 01-ourdigital-brand-guide: Brand reference & style guidelines
- 02-ourdigital-blog: Korean blog drafts (blog.ourdigital.org)
- 03-ourdigital-journal: English essays (journal.ourdigital.org)
- 04-ourdigital-research: Research prompts & workflows
- 05-ourdigital-document: Notion-to-presentation pipeline
- 06-ourdigital-designer: Visual/image prompt generation
- 07-ourdigital-ad-manager: Ad copywriting & keyword research
- 08-ourdigital-trainer: Training materials & workshop planning
- 09-ourdigital-backoffice: Quotes, proposals, cost analysis
- 10-ourdigital-skill-creator: Meta skill for creating new skills

Features:
- YAML frontmatter with "ourdigital" or "our" prefix triggers
- Standardized directory structure (code/, desktop/, shared/, docs/)
- Shared environment setup (_ourdigital-shared/)
- Comprehensive reference documentation
- Cross-skill integration support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 16:50:17 +07:00
parent 7d20abe811
commit 0bc24d00b9
169 changed files with 9970 additions and 741 deletions

View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""
Validate an OurDigital skill structure and content.
Usage:
python validate_skill.py {skill-directory}
python validate_skill.py 02-ourdigital-blog
"""
import argparse
import re
from pathlib import Path
class SkillValidator:
def __init__(self, skill_path: Path):
self.skill_path = skill_path
self.errors = []
self.warnings = []
def validate(self) -> bool:
"""Run all validations."""
self._check_directory_structure()
self._check_skill_files()
self._check_frontmatter()
self._check_content_length()
self._check_ourdigital_triggers()
return len(self.errors) == 0
def _check_directory_structure(self):
"""Verify required directories exist."""
required_dirs = [
"code",
"desktop",
]
recommended_dirs = [
"shared",
"docs",
]
for d in required_dirs:
if not (self.skill_path / d).is_dir():
self.errors.append(f"Missing required directory: {d}/")
for d in recommended_dirs:
if not (self.skill_path / d).is_dir():
self.warnings.append(f"Missing recommended directory: {d}/")
def _check_skill_files(self):
"""Verify SKILL.md files exist."""
skill_files = [
"code/SKILL.md",
"desktop/SKILL.md",
]
for f in skill_files:
if not (self.skill_path / f).is_file():
self.errors.append(f"Missing required file: {f}")
def _check_frontmatter(self):
"""Verify YAML frontmatter in SKILL.md files."""
for env in ["code", "desktop"]:
skill_file = self.skill_path / env / "SKILL.md"
if not skill_file.is_file():
continue
content = skill_file.read_text()
# Check for YAML frontmatter
if not content.startswith("---"):
self.errors.append(f"{env}/SKILL.md: Missing YAML frontmatter")
continue
# Check required fields
required_fields = ["name", "description", "version", "author", "environment"]
for field in required_fields:
if f"{field}:" not in content.split("---")[1]:
self.warnings.append(f"{env}/SKILL.md: Missing frontmatter field '{field}'")
def _check_content_length(self):
"""Verify SKILL.md body is within word limit."""
for env in ["code", "desktop"]:
skill_file = self.skill_path / env / "SKILL.md"
if not skill_file.is_file():
continue
content = skill_file.read_text()
# Extract body (after frontmatter)
parts = content.split("---")
if len(parts) >= 3:
body = "---".join(parts[2:])
word_count = len(body.split())
if word_count < 200:
self.warnings.append(
f"{env}/SKILL.md: Body too short ({word_count} words, recommended 800-1200)"
)
elif word_count > 1500:
self.warnings.append(
f"{env}/SKILL.md: Body too long ({word_count} words, recommended 800-1200)"
)
def _check_ourdigital_triggers(self):
"""Verify 'ourdigital' keyword in triggers."""
for env in ["code", "desktop"]:
skill_file = self.skill_path / env / "SKILL.md"
if not skill_file.is_file():
continue
content = skill_file.read_text().lower()
# Check for ourdigital in description/triggers
if "ourdigital" not in content:
self.errors.append(
f"{env}/SKILL.md: Missing 'ourdigital' keyword in triggers"
)
def report(self):
"""Print validation report."""
print(f"\nValidation Report: {self.skill_path.name}")
print("=" * 50)
if self.errors:
print(f"\nERRORS ({len(self.errors)}):")
for e in self.errors:
print(f" [X] {e}")
if self.warnings:
print(f"\nWARNINGS ({len(self.warnings)}):")
for w in self.warnings:
print(f" [!] {w}")
if not self.errors and not self.warnings:
print("\n All checks passed!")
print("\n" + "=" * 50)
status = "PASS" if not self.errors else "FAIL"
print(f"Status: {status}")
return not self.errors
def main():
parser = argparse.ArgumentParser(description="Validate an OurDigital skill")
parser.add_argument("skill_dir", help="Skill directory name or path")
args = parser.parse_args()
# Handle relative or absolute path
skill_path = Path(args.skill_dir)
if not skill_path.is_absolute():
# Try relative to custom-skills directory
base_path = Path(__file__).parent.parent.parent.parent
skill_path = base_path / args.skill_dir
if not skill_path.is_dir():
print(f"Error: Skill directory not found: {skill_path}")
return 1
validator = SkillValidator(skill_path)
validator.validate()
success = validator.report()
return 0 if success else 1
if __name__ == "__main__":
exit(main())