당사(주식회사 JoinClass)에서는 Claude Code에 회사 운영의 거의 모든 것을 맡기고 있습니다. launchd job이 17개 있으며, Zenn, Qiita, note로의 기사 공개도 매일 자동으로 진행하고 있습니다.
프로젝트의 .claude/skills/
에 있는 스킬은 현재 13개입니다. 개수 자체는 많지 않습니다. 그럼에도 불구하고 다음과 같은 오작동이 발생했습니다.
- “Qiita용으로 써줘”라고 요청했는데도
write-zenn
의 채점 기준(90점 이상)으로 작성됨 - “Kindle로 해줘”라고 요청하면,
publish-kindle / convert-to-epub / format-kindle-epub 중 어느 것이 작동할지 매번 달라짐
원인은 모델의 지능이 아니었습니다. 스킬의 이름(name)과 description 설계였습니다. Claude는 스킬 본문 전체를 처음부터 읽지는 않습니다. 먼저 name과 description만 보고 어떤 것을 사용할지 결정합니다. 즉, description은 모델에게 있어 라우팅 테이블입니다.
이 글에서는 당사에서 실제로 사용하고 있는 SKILL.md 작성 방법과 이를 기계적으로 체크하는 lint 스크립트를 소개합니다.
제가 겪었던 문제들을 분류해 보면 다음 세 가지 패턴으로 나눌 수 있었습니다.
| 패턴 | 예시 | 발생하는 현상 |
|---|
| 동사가 같음 | write-note / write-qiita / write-zenn | |
| 모두 “기사를 작성하는” 것이라 매체명이 대화에 나오지 않으면 구별할 수 없음 | | |
| 공정이 겹침 | convert-to-epub / format-kindle-epub / publish-kindle | |
| “EPUB”, “Kindle”라는 단어가 세 description 모두에 들어가 있음 | | |
| description이 추상적임 | “~을 최적화한다”, “~을 지원한다” | 어떤 요청에도 해당될 것 같아 보여서 우선순위가 엉망이 됨 |
가장 효과적이었던 해결책은 두 번째 방안이었습니다. “나는 어디부터 어디까지를 담당하는지”를 description에 명시하도록 했습니다.
당사의 스킬명은 모두 동사-대상의 kebab-case로 통일했습니다.
.claude/skills/
├── write-note/ # 기사 작성 (매체별)
├── write-qiita/
...
규칙은 단 3가지입니다.
디렉토리명과 frontmatter의 일치. 이것이 어긋나면, 슬래시 명령어 이름과 자동 선택 대상이 별개의 것으로 보이게 됩니다. name을 일치시켜야 합니다 -
접두사(Prefix) 사용. 접두사가 같다면, 뒷부분(매체명 등)이 가장 중요한 식별자가 됩니다. write- / publish- / generate- 와 같은 접두사는 “같은 종류의 작업”에만 사용해야 합니다 -
뒷부분에는 사용자가 실제로 입으로 내뱉는 단어를 사용. “Qiita에 써줘”라고 들을 거라면 qiita를 사용합니다. tech-blog-jp와 같은 사내 용어는 사용하지 않습니다.
당사의 write-qiita의 frontmatter(실물)는 다음과 같습니다.
---
name: write-qiita
description: Qiita 기사를 작성한다. LGTM 최대화 스코어링 기준(100점 만점)에 기반하여, 80점 이상의 기사만 출력. 사용법 /write-qiita "테마"
...
이것을 분해하면 다음 네 가지 요소로 구성되어 있습니다.
[대상] + [동사]. [다른 것과 다른 점 = 판단 기준이나 출력 조건]. 사용법 /<name> "<인수>"
- 대상을 앞에 배치: “Qiita 기사를”이 맨 앞에 오기 때문에, 매체명으로 매칭하기가 쉬워집니다 -
다른 것과의 차이를 한 문장으로 작성: write-zenn은 “90점 이상”, write-qiita는 “80점 이상”. 채점 기준이 다르다는 것이 description에 나와 있으면, 착각했을 때 사람도 바로 알아챌 수 있습니다. -
: 인수가 없는 호출이나 잘못된 호출을 줄여줍니다. 사용법으로 인수 형태를 보여줍니다
공정이 겹치는 스킬은 **“입력 → 출력”과 “담당 범위를 벗어나는 것”**을 추가로 작성합니다. 예를 들어 Kindle 관련 세 가지는 다음과 같이 정리했습니다.
convert-to-epub: 마크다운(Markdown) → EPUB 변환만 담당
description: Zenn 책의 마크다운을 Kindle용 고품질 EPUB으로 변환합니다. 사용법 /convert-to-epub "책 슬러그"
...
핵심은 사용자의 모호한 요청("Kindle로 만들어줘")을 받을 진입(Entry) 스킬을 하나 정해두는 것입니다. 이 진입점 description에는 →를 사용하여 공정 흐름을 작성하고, 나머지 두 스킬은 인수를 "슬러그", "경로"와 같이 구체적으로 지정하여 부품처럼 사용되는 스킬임을 명확히 합니다.
- description에 여러 줄의 YAML(": 파서에 따라 개행 처리 방식이 달라져, 첫 번째 줄만 유효한 경우가 있었습니다. 한 줄로 작성합니다
|
- 너무 길지 않게 유지하기: 모든 스킬의 description은 매번 컨텍스트에 포함됩니다. 저희는 200자를 상한선으로 설정했습니다.
- CLAUDE.md에 같은 설명을 쓰지 않기: CLAUDE.md가 1,000줄을 초과했을 때, 규칙들끼리 모순되어 AI가 지시와 반대되는 움직임을 보인 적이 있습니다. 스킬 설명은 SKILL.md에만 작성하고, CLAUDE.md에는 작성하지 않도록 합니다.
규칙은 사람이 계속 지키기 어렵기 때문에, 스크립트로 검사합니다.
#!/usr/bin/env bash
set -uo pipefail
...
실행합니다.
chmod +x scripts/lint-skills.sh
./scripts/lint-skills.sh .claude/skills
저희의 13개 스킬에서는 다음과 같은 출력이 나옵니다.
INFO 접두사 write가 3개 → 경계를 description에 명시해야 함
NG(오류)가 하나라도 있으면 종료 코드가 1이 됩니다. 따라서, pre-commit이나 Claude Code의 Hooks("PostToolUse")에 그대로 포함할 수 있습니다.
{
"hooks": {
"PostToolUse": [
...
exit 2로 끝나면, stderr의 내용이 Claude에게 반환됩니다. AI가 스스로 SKILL.md를 만들거나 수정했을 때도, 명명 규칙 위반을 그 자리에서 바로잡게 할 수 있습니다.
- 문자 수를 셀 때는
wc -m을 사용하려면 멀티바이트(multi-byte) 대응 로케일이 필요합니다. LANG=C 환경에서는 바이트 수가 되며, 일본어 description 전체가 모두 "너무 김"으로 판정됩니다. CI에서는 LANG=ja_JP.UTF-8 (또는 C.UTF-8)를 지정해 주세요.
- frontmatter의 값을 따옴표로 감싼 경우("
description: "...""), sub에서 따옴표도 제거해 주세요.
lint는 형식만 검사할 수 있습니다. 실제로 올바른 스킬이 선택되는지는 모호한 요청 문구를 던져 확인하는 것밖에 없습니다. 저는 다음 절차로 확인하고 있습니다.
- 사용자가 실제로 할 법한 요청을 스킬별로 3가지씩 작성합니다 (예: "Kindle로 만들어줘", "EPUB 만들게", "책 조판을 정리해줘").
- 새로운 세션에서 하나씩 던지며, 어떤 스킬이 호출되었는지 기록합니다.
- 빗나갔다면,
실패한 쪽이 아니라 성공한 쪽의 description을 좁힙니다
세 번째가 중요합니다. 잘못 선택된 스킬은 대부분 description의 범위가 너무 넓기 때문이었습니다.
- Claude는 스킬을
name과 description으로 선택합니다. description은 라우팅 테이블(routing table)처럼 설계해야 합니다. - 이름은 동사-대상 형태입니다. 접두사는 네임스페이스로 사용하고, 뒤에는 사용자가 입에 담는 단어를 넣습니다. - description은 "대상 + 동사 / 다른 것과의 차이점 / 사용법"의 3가지로 구성합니다. 공정이 중복되는 스킬은 진입점을 하나로 정합니다.
- lint와 Hooks를 통해 형식을 검사하고, 선택 정확도는 실제 요청 문구로 확인합니다.
저희 회사는 이전에 10개의 사업을 동시에 운영하다가 9개 사업이 매출 제로가 된 적이 있습니다. '무엇이든 할 수 있다'는 상태는 '아무것도 선택할 수 없다'는 상태와 같았습니다. 스킬도 마찬가지입니다. 개수를 늘리는 것보다 경계를 명확히 하는 것이 성과로 이어집니다.
스킬 설계(점진적 공개(progressive disclosure), 보조 스크립트 번들링, 플러그인 형태 배포)를 총정리하여 설명한 책을 쓰고 있습니다.