CPN 한국어 자습서 · 러닝패스 1 / 4 — Agent Skills
2장
Introduction to agent skills · Creating your first skill
1장에서 스킬이 무엇인지 살펴봤습니다. 이번 장에서는 스킬을 직접 하나 만들어 봅니다. 폴더를 만들고, SKILL.md 파일에 규칙을 적고, Claude가 그 스킬을 어떻게 찾아 쓰는지까지 손으로 따라 합니다. 중간에 명령어가 나오지만 한 줄씩 무슨 뜻인지 풀어 드리니, 그대로 따라오기만 하면 됩니다.
이 장에서 배우는 것What you'll learn
약 20분개인 스킬을 폴더 + SKILL.md로 직접 만들기
좋은 description으로 ‘제때 발동’ 시키기
Claude가 스킬을 찾는 과정 — 스캔 → 매칭 → 확인 → 로드
이름이 겹칠 때 우선순위 (엔터프라이즈 > 개인 > 프로젝트 > 플러그인)
이 자습서를 보는 법
영어 영상을 먼저 본 뒤, 여기서 한국어로 따라 읽고 손으로 익히는 교재입니다. 점선 친 단어는 올리거나 탭하면 뜻이 나오고, 아래쪽엔 스킬이 작동하는 모습을 직접 눌러 보는 시뮬레이터가 있습니다. 읽은 만큼 ‘완료’를 누르면 위 진도 바가 찹니다.
~ 로 줄여 씁니다.이번에 만들 스킬은 ‘PR 설명을 자동으로 써 주는 스킬’입니다. 한 번 만들어 두면, 브랜치에서 작업한 뒤 “PR 설명 써 줘”라고만 해도 Claude가 늘 같은 양식으로 정리해 줍니다. 무엇을 했는지(What), 왜 했는지(Why), 무엇이 바뀌었는지(Changes) 순서로요. 매번 형식을 다시 알려줄 필요가 없습니다.
이 스킬은 어떤 프로젝트에서든 쓰고 싶으니 ‘개인 스킬내 컴퓨터 전체를 따라다니는 스킬. 홈 디렉터리의 ~/.claude/skills에 둡니다.’로 만듭니다. 그래서 내 홈 디렉터리내 계정의 맨 윗 폴더. 명령어에서 ~ 로 표시합니다. 안에 자리하게 됩니다.
먼저 영상을 보세요. 영어가 어렵다면 아래 한국어를 같이 읽으면 됩니다. 자동 번역과 달리, 낯선 용어를 풀어서 옮겼습니다.
영상 대본 — 영어 영상을 보며 같이 읽으세요 (타임스탬프 기준).
skills 폴더 안에 ‘스킬 이름과 똑같은 이름의 폴더’를 하나 만든다는 점입니다.name(이름)은 스킬을 가리키는 표지이고, description(설명)은 Claude에게 ‘언제 이 스킬을 쓸지’를 알려줍니다.--- 아래에 적는 모든 것이, Claude가 따를 실제 지시(본문)입니다..claude 폴더에 있습니다. 가장 낮은 건 플러그인으로, 온라인에서 받아 둔 플러그인이 여기 해당합니다.review(리뷰) 대신 front-end PR review나 security review처럼요. 스킬을 고치려면 그 SKILL.md 파일을 편집하면 됩니다. 없애려면 그 폴더를 삭제하면 됩니다.SKILL.md 파일(메타데이터와 지시가 담긴)을 넣은 폴더를 하나 만드는 것’입니다.SKILL.md를 편집하고, 적용하려면 Claude Code를 재시작하세요.스킬을 만드는 건 생각보다 단순합니다. 폴더 하나, 파일 하나, 그리고 재시작. 순서대로 해 봅시다.
skills 폴더 안에, 스킬 이름과 똑같은 이름의 폴더를 만듭니다. 개인 스킬이니 홈 디렉터리(~) 아래에 둡니다.SKILL.md 파일을 만들고, 이름·설명·지시를 적습니다.폴더 하나, 파일 하나, 재시작 — 스킬 만들기는 이게 전부입니다.
① 폴더 만들기 — 터미널에 한 줄이면 됩니다.
# 개인 스킬이라 홈 디렉터리(~)의 .claude/skills 안에 만듭니다 ~ % mkdir -p ~/.claude/skills/pr-description
만들고 나면 이런 구조가 됩니다.
~/ └─ .claude/ └─ skills/ └─ pr-description/ └─ SKILL.md
② 이 폴더 안에 SKILL.md를 만들고 아래처럼 적습니다. (원문은 영어입니다. 복사해서 그대로 써 볼 수 있어요.)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
--- name: pr-description description: Writes pull request descriptions. Use when creating a PR, writing a PR, or when the user asks to summarize changes for a pull request. --- When writing a PR description: 1. Run `git diff main...HEAD` to see all changes on this branch 2. Write a description following this format: ## What One sentence explaining what this PR does. ## Why Brief context on why this change is needed ## Changes - Bullet points of specific changes made - Group related changes together - Mention any files deleted or renamed
이 파일도 1장과 똑같이 두 부분으로 나뉩니다. --- 사이의 ‘라벨’과, 그 아래 ‘본문’입니다. 다만 이번엔 라벨을 세 갈래로 더 또렷이 봐 둡시다.
name — 표지. 스킬을 가리키는 이름 역할만 합니다.description — 매칭 기준. Claude가 들어온 요청과 견주는 바로 그 문장입니다. 여기를 잘 써야 제때 발동합니다.--- 아래 본문 — 실제 지시. 발동했을 때 Claude가 따를 규칙입니다. 위 예시에선 PR 설명 양식(무엇을·왜·무엇이 바뀌었는지)을 적어 뒀습니다.③ 마지막으로 Claude Code를 재시작합니다. Claude Code는 켤 때 스킬을 한 번 읽어들이기 때문에, 새로 만들었으면 다시 켜야 인식합니다. 재시작하면, 쓸 수 있는 스킬 목록에 방금 만든 것이 보입니다.
재시작하면 새 스킬이 목록에 뜹니다.
# 변경을 적용하려면 Claude Code를 재시작합니다. # 다시 켜면, 쓸 수 있는 스킬 목록에 새 스킬이 보입니다. pr-description Writes pull request descriptions. Use when creating a PR… accessibility-audit Audits UI for accessibility issues… codebase-onboarding Explains the codebase for new contributors… performance-check Reviews code for performance problems…
이제 만든 스킬을 시험해 봅시다. 브랜치에서 코드를 좀 고쳤다고 치고, 특별한 명령 없이 평범하게 “PR 설명 써 줘”라고만 합니다. 아래 [실행]을 눌러 보세요. 1장과 달리, Claude가 곧장 실행하지 않고 한 번 확인을 거치는 모습이 보일 겁니다.
발동 시뮬레이터 — [실행]하면 매칭 → 확인 → 로드 흐름이 보입니다.
name: pr-description
Writes pull request descriptions. Use when creating a PR…
pr-description 스킬을 불러올까요?
1장과 달리, Claude는 본문을 읽기 전에 한 번 확인합니다 — 지금 어떤 자료를 쓰는지 늘 알 수 있게요. 발동 뒤엔 매번 같은 양식(What·Why·Changes)으로 정리됩니다.
여기서 1장보다 한 걸음 더 나아간 두 가지가 보였습니다. 첫째, Claude가 바로 실행하지 않고 “이 스킬을 불러올까요?”라고 한 번 확인했습니다. 이 확인 단계 덕분에, 지금 Claude가 어떤 자료를 참고하는지 늘 알 수 있습니다. 둘째, 일단 발동하면 매번 똑같은 양식(무엇을·왜·무엇이 바뀌었는지)으로 정리됩니다. 형식을 다시 알려줄 필요가 없죠.
만들 때 잘 써 둔 설명(description)이 ‘제때 발동’을 좌우합니다. 요청의 의도와 설명의 의도가 겹쳐야 Claude가 그 스킬을 고릅니다.
순서대로 따라 해 보세요.
SKILL.md를 만들고 위 예시 내용을 붙여 넣습니다.스킬이 등록돼 있으면, 따로 지정하지 않아도 Claude가 확인을 거쳐 알아서 불러옵니다.
스킬은 PC(Claude Code) 전용이라 모바일 앱에서는 발동하지 않습니다. 위 시뮬레이터로 발동 흐름을 익혀 두고, PC에서 직접 확인해 보세요.
방금 “PR 설명 써 줘” 한마디에 어떻게 맞는 스킬이 딱 불려 왔을까요? Claude Code가 안에서 거치는 과정은 이렇습니다.
~/.claude/skills), 프로젝트(저장소의 .claude/skills), 플러그인 — 이 네 군데에서 스킬을 찾습니다.평소엔 ‘이름·설명’만 띄워 두고, 요청과 맞을 때만 본문을 펼칩니다. 그래서 스킬이 아무리 많아도 기억 공간을 미리 잡아먹지 않습니다.
저장소를 클론했더니 내 개인 스킬과 이름이 같은 스킬이 들어 있다면? 이때는 정해진 우선순위가 충돌을 정리합니다. 번호가 높은(=위에 있는) 쪽이 이깁니다.
~/.claude/skills). 지금 우리가 만든 자리입니다..claude/skills.그래서 회사에 엔터프라이즈 ‘코드 리뷰’ 스킬이 있고 내가 개인 ‘코드 리뷰’ 스킬을 만들면, 엔터프라이즈가 우선합니다. 조직 표준을 흔들지 않으면서, 구성원은 각자 ‘다른 이름의 스킬’로 맞춤 설정을 더할 수 있는 구조죠.
code-review라는 같은 이름의 스킬이 여러 곳에 있다면? 어디에 있는지 켜고 꺼 보세요. 가장 높은 곳이 발동합니다.
엔터프라이즈
관리자 표준
개인
~/.claude/skills
프로젝트
저장소의 .claude/skills
플러그인
받아 끼운 것
엔터프라이즈 > 개인 > 프로젝트 > 플러그인 — 위가 아래를 이깁니다. 충돌이 싫으면 이름을 구체적으로 지으세요(frontend-review 등).
엔터프라이즈 > 개인 > 프로젝트 > 플러그인. 위가 아래를 이깁니다.
충돌을 피하는 가장 쉬운 방법은 이름을 구체적으로 짓는 것입니다. 그냥 review 대신 frontend-review, backend-review처럼요. 이름이 다르면 충돌 자체가 없습니다.
한 번 만든 스킬도 얼마든지 손볼 수 있습니다.
SKILL.md 파일을 열어 내용을 바꿉니다.무엇을 바꾸든 ‘재시작’을 잊지 마세요. Claude Code는 켤 때 스킬을 한 번 읽어들이니, 재시작해야 변경이 반영됩니다.
SKILL.md(이름·설명·지시가 든)를 넣은 ‘폴더’를 하나 만든다.~/.claude/skills에 둔다. 만든 뒤엔 Claude Code를 재시작해야 인식한다.frontend-review 등).정답을 먼저 떠올려 본 뒤 골라 보세요. 맞히면 설명이 나옵니다.
Q1스킬을 새로 만든 뒤, Claude Code가 그것을 인식하게 하려면?
맞아요. Claude Code는 켤 때 스킬을 한 번 읽어들입니다. 그래서 만들거나 고친 뒤에는 재시작해야 반영됩니다.
Q2이름이 같은 스킬이 개인과 엔터프라이즈에 모두 있으면, 어느 쪽이 우선인가요?
엔터프라이즈가 가장 높습니다. 엔터프라이즈 > 개인 > 프로젝트 > 플러그인 순이에요. 조직 표준을 강제하기 위해서죠.
Q3Claude가 요청에 맞는 스킬을 찾을 때, 시작 단계에서 주로 보는 것은?
시작할 때는 이름과 설명만 읽어 둡니다. 요청과 의도가 겹치는 스킬을 찾고, 확인을 거친 뒤에야 본문 전체를 읽어요. 그래서 컨텍스트 윈도우가 가볍게 유지됩니다.
description을 한 줄로 어떻게 적을까요?SKILL.md 맨 위 라벨의 이름·설명·도구 항목이 agentskills.io 공개 표준에 맞는지 점검하고, 규칙을 벗어난 곳을 항목별로 짚어 봅니다.
// SKILL.md 검사기 — SKILL.md 맨 위 라벨(name·description·allowed-tools·model)이
// agentskills.io 공개 표준 규칙에 맞는지 점검한다. 규칙은 3장에서 배운 것 그대로다.
// (a) 맨 위 라벨 블록을 잘라 읽는다. 여는 --- 와 닫는 --- 는 각자 제 줄에 있어야 한다.
// 한 줄로 붕괴하면(---name: ... model: ... ---) 라벨을 읽지 못한다.
function parseFrontmatter(src) {
const lines = src.replace(/\r\n/g, "\n").split("\n");
const first = lines[0].trim();
if (first !== "---") {
if (first.indexOf("---") === 0) {
return { ok: false, reason: "여는 --- 뒤에 내용이 붙어 라벨이 한 줄로 붕괴했습니다." };
}
return { ok: false, reason: "맨 위 라벨이 --- 로 시작하지 않습니다." };
}
let end = -1;
for (let i = 1; i < lines.length; i++) {
if (lines[i].trim() === "---") { end = i; break; }
}
if (end === -1) {
return { ok: false, reason: "닫는 --- 가 제 줄에 없습니다." };
}
const fm = {};
for (let i = 1; i < end; i++) {
const c = lines[i].indexOf(":");
if (c === -1) continue;
fm[lines[i].slice(0, c).trim()] = lines[i].slice(c + 1).trim();
}
return { ok: true, fm: fm, body: lines.slice(end + 1).join("\n").trim() };
}
// (b) 라벨과 본문을 규칙대로 점검한다.
function validate(parsed) {
const problems = [];
if (!parsed.ok) { problems.push(parsed.reason); return problems; }
const fm = parsed.fm;
// name — 필수 · 소문자·숫자·하이픈만 · 최대 64자
if (!fm.name) {
problems.push("name 이 없습니다(필수).");
} else {
if (!/^[a-z0-9-]+$/.test(fm.name)) {
problems.push("name 은 소문자·숫자·하이픈만 됩니다: " + fm.name);
}
if (fm.name.length > 64) {
problems.push("name 이 64자를 넘습니다(" + fm.name.length + "자).");
}
}
// description — 필수 · 최대 1,024자 · 매칭에 가장 중요한 항목
if (!fm.description) {
problems.push("description 이 없습니다(필수 · 매칭에 가장 중요).");
} else if (fm.description.length > 1024) {
problems.push("description 이 1,024자를 넘습니다(" + fm.description.length + "자).");
}
// allowed-tools — 있으면 쉼표로 나눈 목록이어야 한다
const at = fm["allowed-tools"];
if (at && at.indexOf(",") === -1 && at.split(/\s+/).length > 1) {
problems.push("allowed-tools 는 쉼표로 나눈 목록이어야 합니다.");
}
// 라벨 아래 본문(지침) 존재
if (!parsed.body) {
problems.push("라벨 아래 본문(지침)이 없습니다.");
}
return problems;
}
// ── 점검할 SKILL.md 세 벌(전부 3장 예시에서 가져옴) ──
const GOOD = [
"---",
"name: codebase-onboarding",
"description: Helps new developers understand how the system works.",
"allowed-tools: Read, Grep, Glob, Bash",
"model: sonnet",
"---",
"",
"# Codebase Guide",
].join("\n");
// 여닫는 --- 가 제 줄을 잃고 한 줄로 붕괴한 라벨
const COLLAPSED =
"---name: codebase-onboardingdescription: Helps new developers " +
"understand how the system works.allowed-tools: Read, Grep, Glob, Bashmodel: sonnet---";
// 이름에 대문자·공백이 섞이고 description 이 빠진 라벨
const BAD = [
"---",
"name: PR Review",
"model: sonnet",
"---",
"",
"When writing a PR description, run git diff first.",
].join("\n");
[
["통과 · codebase-onboarding", GOOD],
["위반A · 한 줄로 붕괴한 라벨", COLLAPSED],
["위반B · 이름 규칙 위반 + 설명 누락", BAD],
].forEach(function (pair) {
const problems = validate(parseFrontmatter(pair[1]));
console.log(pair[0] + " → " + (problems.length ? problems.join(" / ") : "표준과 일치"));
});
다음 장에서는 스킬을 더 정교하게 다듬는 법을 봅니다. 메타데이터 항목을 더 붙이고, 쓸 수 있는 도구를 제한하거나(allowed-tools), 큰 스킬을 여러 파일로 나눠 필요한 부분만 불러오는(점진적 공개) 방법을 다룹니다.