Skip to content

Commit da2a08e

Browse files
authored
feat: Add automated tests for Skills (#17)
* feat(scripts): add skill validation script Add validate_skills.sh to verify SKILL.md files: - Check YAML frontmatter presence and format - Validate name field (lowercase, numbers, hyphens, max 64 chars) - Validate description field (non-empty, max 1024 chars) - Check file line count (warn if > 500 lines) - Verify reference directory existence - Optional YAML syntax validation with PyYAML * feat(ci): add GitHub Actions workflow for skill validation Add validate-skills.yml workflow: - Runs on push/PR to skills directories - Validates SKILL.md files using validate_skills.sh - Additional YAML syntax verification with PyYAML - Checks description length (max 1024 chars) - Checks name format (lowercase, numbers, hyphens, max 64 chars) * feat(hooks): add pre-commit hook for skill validation Add pre-commit hook and installation script: - hooks/pre-commit: Validates SKILL.md files before commit - hooks/install-hooks.sh: Installs hooks to .git/hooks - Only runs validation when SKILL.md files are changed * docs: add skill validation documentation Update README.md and README.ko.md: - Add validate_skills.sh script documentation - Add pre-commit hook section - Update structure section with hooks and .github directories
1 parent dca800c commit da2a08e

6 files changed

Lines changed: 664 additions & 2 deletions

File tree

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
name: Validate Skills
2+
3+
on:
4+
push:
5+
paths:
6+
- 'project/.claude/skills/**'
7+
- 'plugin/skills/**'
8+
- 'scripts/validate_skills.sh'
9+
pull_request:
10+
paths:
11+
- 'project/.claude/skills/**'
12+
- 'plugin/skills/**'
13+
- 'scripts/validate_skills.sh'
14+
15+
jobs:
16+
validate:
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Checkout repository
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: '3.x'
26+
27+
- name: Install PyYAML
28+
run: pip install pyyaml
29+
30+
- name: Validate SKILL.md files
31+
run: ./scripts/validate_skills.sh
32+
33+
- name: Check YAML syntax (additional verification)
34+
run: |
35+
for skill in $(find . -name "SKILL.md" -path "*/skills/*"); do
36+
echo "Checking $skill..."
37+
python3 << PYEOF
38+
import yaml
39+
import sys
40+
41+
with open('$skill', 'r') as f:
42+
content = f.read()
43+
44+
lines = content.split('\n')
45+
if lines[0] != '---':
46+
print(f"Error: {sys.argv[0]} - No YAML frontmatter")
47+
sys.exit(1)
48+
49+
end_idx = -1
50+
for i, line in enumerate(lines[1:], 1):
51+
if line == '---':
52+
end_idx = i
53+
break
54+
55+
if end_idx == -1:
56+
print(f"Error: No closing frontmatter delimiter")
57+
sys.exit(1)
58+
59+
frontmatter = '\n'.join(lines[1:end_idx])
60+
try:
61+
data = yaml.safe_load(frontmatter)
62+
if not data.get('name'):
63+
print(f"Error: Missing 'name' field")
64+
sys.exit(1)
65+
if not data.get('description'):
66+
print(f"Error: Missing 'description' field")
67+
sys.exit(1)
68+
print(f"Valid: {data.get('name')}")
69+
except yaml.YAMLError as e:
70+
print(f"YAML Error: {e}")
71+
sys.exit(1)
72+
PYEOF
73+
done
74+
echo "All SKILL.md files are valid!"
75+
76+
- name: Check description length
77+
run: |
78+
failed=0
79+
for skill in $(find . -name "SKILL.md" -path "*/skills/*"); do
80+
python3 << PYEOF
81+
import sys
82+
83+
with open('$skill', 'r') as f:
84+
content = f.read()
85+
86+
lines = content.split('\n')
87+
for line in lines[1:]:
88+
if line == '---':
89+
break
90+
if line.startswith('description:'):
91+
desc = line[12:].strip()
92+
if len(desc) > 1024:
93+
print(f"Error: $skill description exceeds 1024 characters ({len(desc)})")
94+
sys.exit(1)
95+
break
96+
PYEOF
97+
if [ $? -ne 0 ]; then
98+
failed=1
99+
fi
100+
done
101+
if [ $failed -eq 1 ]; then
102+
echo "Some SKILL.md files have description length issues"
103+
exit 1
104+
fi
105+
echo "All descriptions are within limits!"
106+
107+
- name: Check name format
108+
run: |
109+
failed=0
110+
for skill in $(find . -name "SKILL.md" -path "*/skills/*"); do
111+
python3 << PYEOF
112+
import sys
113+
import re
114+
115+
with open('$skill', 'r') as f:
116+
content = f.read()
117+
118+
lines = content.split('\n')
119+
for line in lines[1:]:
120+
if line == '---':
121+
break
122+
if line.startswith('name:'):
123+
name = line[5:].strip()
124+
if not re.match(r'^[a-z0-9-]+$', name):
125+
print(f"Error: $skill name '{name}' contains invalid characters")
126+
sys.exit(1)
127+
if len(name) > 64:
128+
print(f"Error: $skill name exceeds 64 characters ({len(name)})")
129+
sys.exit(1)
130+
break
131+
PYEOF
132+
if [ $? -ne 0 ]; then
133+
failed=1
134+
fi
135+
done
136+
if [ $failed -eq 1 ]; then
137+
echo "Some SKILL.md files have name format issues"
138+
exit 1
139+
fi
140+
echo "All names are valid!"

README.ko.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,16 @@ claude_config_backup/
134134
│ ├── install.sh # 새 시스템에 설치
135135
│ ├── backup.sh # 현재 설정 백업
136136
│ ├── sync.sh # 설정 동기화
137-
│ └── verify.sh # 백업 무결성 검증
137+
│ ├── verify.sh # 백업 무결성 검증
138+
│ └── validate_skills.sh # SKILL.md 파일 검증
139+
140+
├── hooks/ # Git hooks
141+
│ ├── pre-commit # 커밋 전 스킬 검증
142+
│ └── install-hooks.sh # Hook 설치 스크립트
143+
144+
├── .github/
145+
│ └── workflows/
146+
│ └── validate-skills.yml # CI 스킬 검증
138147
139148
├── plugin/ # Claude Code Plugin (Beta)
140149
│ ├── .claude-plugin/
@@ -348,6 +357,48 @@ cd ~/claude_config_backup
348357

349358
---
350359

360+
### 5. validate_skills.sh
361+
362+
**목적:** SKILL.md 파일의 형식 준수 여부 검증
363+
364+
**기능:**
365+
- YAML frontmatter 검증
366+
- name 필드 확인 (소문자, 숫자, 하이픈, 최대 64자)
367+
- description 필드 확인 (비어있지 않음, 최대 1024자)
368+
- 파일 라인 수 확인 (500줄 초과 시 경고)
369+
- reference 디렉토리 존재 확인
370+
- 선택적 PyYAML 구문 검증
371+
372+
**사용법:**
373+
```bash
374+
./scripts/validate_skills.sh
375+
```
376+
377+
**검증 규칙:**
378+
| 필드 | 규칙 |
379+
|------|------|
380+
| Frontmatter | `---`로 시작하고 끝나야 함 |
381+
| name | `[a-z0-9-]+`, 최대 64자 |
382+
| description | 비어있지 않음, 최대 1024자 |
383+
| 파일 길이 | 500줄 초과 시 경고 |
384+
385+
---
386+
387+
## Pre-commit Hook
388+
389+
SKILL.md 파일을 커밋 전 자동으로 검증하려면 pre-commit hook을 설치하세요:
390+
391+
```bash
392+
./hooks/install-hooks.sh
393+
```
394+
395+
Hook이 수행하는 작업:
396+
- SKILL.md 파일 변경 감지
397+
- `validate_skills.sh` 자동 실행
398+
- 유효하지 않은 SKILL.md 파일이 있으면 커밋 차단
399+
400+
---
401+
351402
## 사용 시나리오
352403

353404
### 시나리오 A: 회사 + 집 컴퓨터 동기화

README.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,16 @@ claude_config_backup/
135135
│ ├── install.sh # Install to new system
136136
│ ├── backup.sh # Backup current settings
137137
│ ├── sync.sh # Sync settings
138-
│ └── verify.sh # Verify backup integrity
138+
│ ├── verify.sh # Verify backup integrity
139+
│ └── validate_skills.sh # Validate SKILL.md files
140+
141+
├── hooks/ # Git hooks
142+
│ ├── pre-commit # Pre-commit skill validation
143+
│ └── install-hooks.sh # Hook installation script
144+
145+
├── .github/
146+
│ └── workflows/
147+
│ └── validate-skills.yml # CI skill validation
139148
140149
├── plugin/ # Claude Code Plugin (Beta)
141150
│ ├── .claude-plugin/
@@ -381,6 +390,48 @@ cd ~/claude_config_backup
381390

382391
---
383392

393+
### 5. validate_skills.sh
394+
395+
**Purpose:** Validate SKILL.md files for format compliance
396+
397+
**Features:**
398+
- YAML frontmatter validation
399+
- Name field check (lowercase, numbers, hyphens, max 64 chars)
400+
- Description field check (non-empty, max 1024 chars)
401+
- File line count check (warning if > 500 lines)
402+
- Reference directory existence check
403+
- Optional PyYAML syntax validation
404+
405+
**Usage:**
406+
```bash
407+
./scripts/validate_skills.sh
408+
```
409+
410+
**Validation Rules:**
411+
| Field | Rule |
412+
|-------|------|
413+
| Frontmatter | Must start and end with `---` |
414+
| name | `[a-z0-9-]+`, max 64 characters |
415+
| description | Non-empty, max 1024 characters |
416+
| File length | Warning if > 500 lines |
417+
418+
---
419+
420+
## Pre-commit Hook
421+
422+
Install the pre-commit hook to automatically validate SKILL.md files before commit:
423+
424+
```bash
425+
./hooks/install-hooks.sh
426+
```
427+
428+
The hook will:
429+
- Detect changes to SKILL.md files
430+
- Run `validate_skills.sh` automatically
431+
- Block commits with invalid SKILL.md files
432+
433+
---
434+
384435
## Use Cases
385436

386437
### Use Case A: Sync Work + Home Computers

hooks/install-hooks.sh

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
#!/bin/bash
2+
3+
# Git Hooks Installation Script
4+
# =============================
5+
# Git hooks를 설치하는 스크립트
6+
7+
set -e
8+
9+
# 색상 정의
10+
RED='\033[0;31m'
11+
GREEN='\033[0;32m'
12+
YELLOW='\033[1;33m'
13+
BLUE='\033[0;34m'
14+
NC='\033[0m'
15+
16+
# 함수 정의
17+
info() { echo -e "${BLUE}ℹ️ $1${NC}"; }
18+
success() { echo -e "${GREEN}$1${NC}"; }
19+
warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
20+
error() { echo -e "${RED}$1${NC}"; }
21+
22+
# 스크립트 디렉토리
23+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
24+
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
25+
GIT_HOOKS_DIR="$REPO_ROOT/.git/hooks"
26+
27+
echo -e "${BLUE}"
28+
cat << 'EOF'
29+
╔═══════════════════════════════════════════════════════════════╗
30+
║ ║
31+
║ Git Hooks Installation Script ║
32+
║ ║
33+
╚═══════════════════════════════════════════════════════════════╝
34+
EOF
35+
echo -e "${NC}"
36+
37+
# Git 저장소 확인
38+
if [ ! -d "$REPO_ROOT/.git" ]; then
39+
error "Git 저장소가 아닙니다."
40+
exit 1
41+
fi
42+
43+
# hooks 디렉토리 확인
44+
if [ ! -d "$GIT_HOOKS_DIR" ]; then
45+
mkdir -p "$GIT_HOOKS_DIR"
46+
success "Git hooks 디렉토리 생성: $GIT_HOOKS_DIR"
47+
fi
48+
49+
# pre-commit hook 설치
50+
info "pre-commit hook 설치 중..."
51+
52+
if [ -f "$GIT_HOOKS_DIR/pre-commit" ]; then
53+
warning "기존 pre-commit hook이 존재합니다."
54+
read -p "덮어쓰시겠습니까? (y/n): " -n 1 -r
55+
echo
56+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
57+
info "설치를 건너뜁니다."
58+
exit 0
59+
fi
60+
fi
61+
62+
cp "$SCRIPT_DIR/pre-commit" "$GIT_HOOKS_DIR/pre-commit"
63+
chmod +x "$GIT_HOOKS_DIR/pre-commit"
64+
65+
success "pre-commit hook 설치 완료!"
66+
67+
echo ""
68+
info "설치된 hooks:"
69+
ls -la "$GIT_HOOKS_DIR" | grep -v ".sample"
70+
71+
echo ""
72+
success "Git hooks 설치가 완료되었습니다."
73+
info "SKILL.md 파일을 커밋할 때 자동으로 검증이 실행됩니다."

hooks/pre-commit

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/bin/bash
2+
3+
# Pre-commit hook for SKILL.md validation
4+
# ========================================
5+
# SKILL.md 파일이 변경될 때 자동으로 검증을 실행합니다.
6+
7+
# 색상 정의
8+
RED='\033[0;31m'
9+
GREEN='\033[0;32m'
10+
YELLOW='\033[1;33m'
11+
NC='\033[0m'
12+
13+
# 스크립트 디렉토리 찾기
14+
SCRIPT_DIR="$(git rev-parse --show-toplevel)"
15+
16+
# 변경된 SKILL.md 파일 확인
17+
CHANGED_SKILLS=$(git diff --cached --name-only | grep "SKILL.md")
18+
19+
if [ -z "$CHANGED_SKILLS" ]; then
20+
# SKILL.md 파일이 변경되지 않음 - 검증 건너뜀
21+
exit 0
22+
fi
23+
24+
echo -e "${YELLOW}SKILL.md 파일 변경 감지 - 검증 실행 중...${NC}"
25+
echo ""
26+
27+
# 검증 스크립트 존재 확인
28+
if [ ! -x "$SCRIPT_DIR/scripts/validate_skills.sh" ]; then
29+
echo -e "${RED}오류: validate_skills.sh를 찾을 수 없거나 실행 권한이 없습니다.${NC}"
30+
echo "경로: $SCRIPT_DIR/scripts/validate_skills.sh"
31+
exit 1
32+
fi
33+
34+
# 검증 실행
35+
cd "$SCRIPT_DIR" && ./scripts/validate_skills.sh
36+
37+
if [ $? -ne 0 ]; then
38+
echo ""
39+
echo -e "${RED}SKILL.md 검증 실패 - 커밋이 중단되었습니다.${NC}"
40+
echo ""
41+
echo "문제를 수정한 후 다시 커밋하세요."
42+
exit 1
43+
fi
44+
45+
echo ""
46+
echo -e "${GREEN}SKILL.md 검증 통과!${NC}"
47+
exit 0

0 commit comments

Comments
 (0)