byteforce

CPN 한국어 자습서 · 러닝패스 1 / 4 — Agent Skills

2장

첫 스킬 만들기

Introduction to agent skills · Creating your first skill

1장에서 스킬이 무엇인지 살펴봤습니다. 이번 장에서는 스킬을 직접 하나 만들어 봅니다. 폴더를 만들고, SKILL.md 파일에 규칙을 적고, Claude가 그 스킬을 어떻게 찾아 쓰는지까지 손으로 따라 합니다. 중간에 명령어가 나오지만 한 줄씩 무슨 뜻인지 풀어 드리니, 그대로 따라오기만 하면 됩니다.

이 장에서 배우는 것What you'll learn

약 20분
1

개인 스킬을 폴더 + SKILL.md로 직접 만들기

2

좋은 description으로 ‘제때 발동’ 시키기

3

Claude가 스킬을 찾는 과정 — 스캔 → 매칭 → 확인 → 로드

4

이름이 겹칠 때 우선순위 (엔터프라이즈 > 개인 > 프로젝트 > 플러그인)

이 자습서를 보는 법

영어 영상을 먼저 본 뒤, 여기서 한국어로 따라 읽고 손으로 익히는 교재입니다. 점선 친 단어는 올리거나 탭하면 뜻이 나오고, 아래쪽엔 스킬이 작동하는 모습을 직접 눌러 보는 시뮬레이터가 있습니다. 읽은 만큼 ‘완료’를 누르면 위 진도 바가 찹니다.

실습 환경

먼저, 이 장에 나오는 낯선 단어
홈 디렉터리 (Home directory)
내 컴퓨터에서 내 계정의 ‘맨 윗 폴더’입니다. 명령어에서는 물결표 ~ 로 줄여 씁니다.
메타데이터 (Metadata)
데이터에 붙는 ‘꼬리표’ 정보입니다. 여기서는 스킬의 이름과 설명을 가리킵니다.
diff (디프 · 변경 사항)
코드에서 바뀐 부분만 콕 집어 비교해 보여주는 것입니다.
엔터프라이즈 (Enterprise)
회사·조직 단위를 뜻합니다. 관리자가 구성원 전체에 적용하는 설정이 여기 해당합니다.
플러그인 (Plugin)
남이 만들어 둔 기능을 가져와 끼워 쓰는 확장 꾸러미입니다.

이 장에서 만들 것

What we'll build

이번에 만들 스킬은 ‘PR 설명을 자동으로 써 주는 스킬’입니다. 한 번 만들어 두면, 브랜치에서 작업한 뒤 “PR 설명 써 줘”라고만 해도 Claude가 늘 같은 양식으로 정리해 줍니다. 무엇을 했는지(What), 왜 했는지(Why), 무엇이 바뀌었는지(Changes) 순서로요. 매번 형식을 다시 알려줄 필요가 없습니다.

이 스킬은 어떤 프로젝트에서든 쓰고 싶으니 ‘개인 스킬내 컴퓨터 전체를 따라다니는 스킬. 홈 디렉터리의 ~/.claude/skills에 둡니다.’로 만듭니다. 그래서 내 홈 디렉터리내 계정의 맨 윗 폴더. 명령어에서 ~ 로 표시합니다. 안에 자리하게 됩니다.

영상 내용, 한국어로

Video walkthrough

먼저 영상을 보세요. 영어가 어렵다면 아래 한국어를 같이 읽으면 됩니다. 자동 번역과 달리, 낯선 용어를 풀어서 옮겼습니다.

영상 · Creating your first skill약 4분 · 영어코스에서 영상 보기 →

영상 대본 — 영어 영상을 보며 같이 읽으세요 (타임스탬프 기준).

0:03스킬을 하나 만들어 봅시다. 이 스킬은 Claude에게 ‘우리가 원하는 방식’을 가르칩니다 — 예컨대 코드를 그림(다이어그램)과 비유로 풀어 설명하는 방식처럼요.
0:11그런 다음, Claude가 그 스킬을 쓸 때 안에서 무슨 일이 벌어지는지 들여다봅니다.
0:18먼저 스킬을 담을 폴더를 만듭니다. 이번엔 개인 스킬을 만들 거라, 여러 프로젝트에 두루 따라오도록 홈 디렉터리에 둡니다.
0:26핵심은, skills 폴더 안에 ‘스킬 이름과 똑같은 이름의 폴더’를 하나 만든다는 점입니다.
0:33이제 스킬을 작성합니다.
0:38name(이름)은 스킬을 가리키는 표지이고, description(설명)은 Claude에게 ‘언제 이 스킬을 쓸지’를 알려줍니다.
0:43바로 이 설명이 매칭 기준입니다. 그리고 두 번째 --- 아래에 적는 모든 것이, Claude가 따를 실제 지시(본문)입니다.
0:52Claude Code는 시작할 때 스킬을 읽어들입니다. 그러니 세션을 재시작하세요. 그런 다음 스킬이 잘 등록됐는지 확인합니다.
1:00목록에서 방금 만든 PR 설명 스킬이 보일 겁니다.
1:04이제 시험해 봅니다. 브랜치에서 무언가를 고친 뒤 “내 변경 사항으로 PR 설명을 써 줘”라고 해 보세요. 그러면 Claude가 ‘PR 설명 스킬을 쓰는 중’이라고 알려줍니다.
1:17이어서 변경 사항(diff코드에서 바뀐 부분만 비교해 보여주는 것. ‘변경 사항’.)을 확인하고, 당신이 정해 둔 양식대로 설명을 써 줍니다. 매번 똑같은 형식으로요.
1:24Claude Code는 시작할 때 네 곳을 살펴 스킬을 찾습니다. 엔터프라이즈 경로, 내 개인 Claude 스킬, 프로젝트의 Claude 스킬, 그리고 설치된 플러그인입니다. 이때 각 스킬의 이름과 설명만 읽고, 전체 내용은 읽지 않습니다. 이 점이 뒤에서 중요해집니다. 요청을 보내면, Claude는 그 요청을 스킬들의 설명과 견줍니다.
1:43예를 들어 “이 함수가 뭘 하는지 설명해 줘”라는 요청은 ‘코드를 그림으로 설명한다’고 적힌 스킬과 맞아떨어집니다. 의도(하려는 바)가 겹치기 때문이죠. 그러면 Claude는 그 스킬을 불러올지 확인을 요청합니다.
1:51이 확인 단계 덕분에, 당신은 Claude가 지금 어떤 자료를 쓰고 있는지 늘 알 수 있습니다. 확인하고 나면, Claude가 파일 전체를 읽고 그 지시를 따릅니다.
2:00이제 Git 저장소를 복제(클론)했는데 스킬 이름이 겹친다고 해 봅시다. 어느 쪽이 이길까요?
2:07우선순위는 이렇습니다. 가장 높은 건 엔터프라이즈로, 관리자 설정에 들어 있습니다. 두 번째는 개인 스킬로, 지금 우리가 하는 것처럼 내 홈 디렉터리 설정에 있습니다.
2:15세 번째는 프로젝트로, 저장소 안의 .claude 폴더에 있습니다. 가장 낮은 건 플러그인으로, 온라인에서 받아 둔 플러그인이 여기 해당합니다.
2:31이렇게 하면 조직은 표준을 강제하면서도, 구성원은 ‘다른 이름의 스킬’로 각자 맞춤 설정을 할 수 있습니다.
2:40만약 회사에 엔터프라이즈 코드 리뷰 스킬이 있는데 당신이 개인 코드 리뷰 스킬을 만들면, 회사(엔터프라이즈) 쪽이 우선합니다.
2:47충돌을 피하려면 이름을 구체적으로 지으세요. 그냥 review(리뷰) 대신 front-end PR reviewsecurity review처럼요. 스킬을 고치려면 그 SKILL.md 파일을 편집하면 됩니다. 없애려면 그 폴더를 삭제하면 됩니다.
3:00변경한 뒤에는 Claude Code를 재시작해야 적용됩니다.
3:06정리하면, 스킬을 만든다는 건 ‘SKILL.md 파일(메타데이터와 지시가 담긴)을 넣은 폴더를 하나 만드는 것’입니다.
3:13Claude는 시작할 때 스킬의 이름과 설명을 읽어들이고, 들어오는 요청을 그 설명들과 견주며, 전체 내용을 불러오기 전에 확인을 요청합니다.
3:25이름이 겹칠 때는 우선순위 규칙이 처리합니다.
3:27엔터프라이즈가 개인을, 개인이 프로젝트를, 프로젝트가 플러그인을 이깁니다.
3:32스킬을 고치려면 SKILL.md를 편집하고, 적용하려면 Claude Code를 재시작하세요.

스킬 만들기 — 세 단계

Create a skill

스킬을 만드는 건 생각보다 단순합니다. 폴더 하나, 파일 하나, 그리고 재시작. 순서대로 해 봅시다.

만들어 보기 · 단계를 눌러 진행

폴더 하나, 파일 하나, 재시작 — 스킬 만들기는 이게 전부입니다.

① 폴더 만들기 — 터미널에 한 줄이면 됩니다.

터미널 — 스킬 폴더 만들기
# 개인 스킬이라 홈 디렉터리(~)의 .claude/skills 안에 만듭니다
~ % mkdir -p ~/.claude/skills/pr-description

만들고 나면 이런 구조가 됩니다.

~/
└─ .claude/
   └─ skills/
      └─ pr-description/
         └─ SKILL.md

② 이 폴더 안에 SKILL.md를 만들고 아래처럼 적습니다. (원문은 영어입니다. 복사해서 그대로 써 볼 수 있어요.)

SKILL.md — skills
~ › .claude › skills › pr-description › 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장과 똑같이 두 부분으로 나뉩니다. --- 사이의 ‘라벨’과, 그 아래 ‘본문’입니다. 다만 이번엔 라벨을 세 갈래로 더 또렷이 봐 둡시다.

③ 마지막으로 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…

스킬이 발동하는 모습

See it activate

이제 만든 스킬을 시험해 봅시다. 브랜치에서 코드를 좀 고쳤다고 치고, 특별한 명령 없이 평범하게 “PR 설명 써 줘”라고만 합니다. 아래 [실행]을 눌러 보세요. 1장과 달리, Claude가 곧장 실행하지 않고 한 번 확인을 거치는 모습이 보일 겁니다.

발동 시뮬레이터 — [실행]하면 매칭 → 확인 → 로드 흐름이 보입니다.

발동 시뮬레이터 · 확인 단계까지 내 변경 사항으로 PR 설명 써줘
켤 때 읽어 둔 스킬 ‘설명’들과 요청을 대조합니다…
일치

name: pr-description

Writes pull request descriptions. Use when creating a PR…

pr-description 스킬을 불러올까요?

불러오지 않았습니다. 확인 단계에서 거부하면 본문을 읽지 않아요. 다시 [실행]해서 허용해 보세요.

1장과 달리, Claude는 본문을 읽기 전에 한 번 확인합니다 — 지금 어떤 자료를 쓰는지 늘 알 수 있게요. 발동 뒤엔 매번 같은 양식(What·Why·Changes)으로 정리됩니다.

여기서 1장보다 한 걸음 더 나아간 두 가지가 보였습니다. 첫째, Claude가 바로 실행하지 않고 “이 스킬을 불러올까요?”라고 한 번 확인했습니다. 이 확인 단계 덕분에, 지금 Claude가 어떤 자료를 참고하는지 늘 알 수 있습니다. 둘째, 일단 발동하면 매번 똑같은 양식(무엇을·왜·무엇이 바뀌었는지)으로 정리됩니다. 형식을 다시 알려줄 필요가 없죠.

핵심

만들 때 잘 써 둔 설명(description)이 ‘제때 발동’을 좌우합니다. 요청의 의도와 설명의 의도가 겹쳐야 Claude가 그 스킬을 고릅니다.

직접 해보기 내 Claude로 만들어 발동시켜 보기

순서대로 따라 해 보세요.

  1. 홈 디렉터리에 스킬 폴더를 만듭니다.
  2. 그 안에 SKILL.md를 만들고 위 예시 내용을 붙여 넣습니다.
  3. Claude Code를 재시작합니다.
  4. 브랜치에서 뭔가 고친 뒤 이렇게 입력합니다.

스킬이 등록돼 있으면, 따로 지정하지 않아도 Claude가 확인을 거쳐 알아서 불러옵니다.

스킬은 PC(Claude Code) 전용이라 모바일 앱에서는 발동하지 않습니다. 위 시뮬레이터로 발동 흐름을 익혀 두고, PC에서 직접 확인해 보세요.

안에서 무슨 일이 벌어지나

Under the hood

방금 “PR 설명 써 줘” 한마디에 어떻게 맞는 스킬이 딱 불려 왔을까요? Claude Code가 안에서 거치는 과정은 이렇습니다.

한눈에

평소엔 ‘이름·설명’만 띄워 두고, 요청과 맞을 때만 본문을 펼칩니다. 그래서 스킬이 아무리 많아도 기억 공간을 미리 잡아먹지 않습니다.

이름이 겹치면 — 우선순위

Name conflicts

저장소를 클론했더니 내 개인 스킬과 이름이 같은 스킬이 들어 있다면? 이때는 정해진 우선순위가 충돌을 정리합니다. 번호가 높은(=위에 있는) 쪽이 이깁니다.

그래서 회사에 엔터프라이즈 ‘코드 리뷰’ 스킬이 있고 내가 개인 ‘코드 리뷰’ 스킬을 만들면, 엔터프라이즈가 우선합니다. 조직 표준을 흔들지 않으면서, 구성원은 각자 ‘다른 이름의 스킬’로 맞춤 설정을 더할 수 있는 구조죠.

우선순위 · 같은 이름이 겹치면 누가 이기나

code-review라는 같은 이름의 스킬이 여러 곳에 있다면? 어디에 있는지 켜고 꺼 보세요. 가장 높은 곳이 발동합니다.

code-review가 있는 곳:
1

엔터프라이즈

관리자 표준

2

개인

~/.claude/skills

3

프로젝트

저장소의 .claude/skills

4

플러그인

받아 끼운 것

엔터프라이즈 > 개인 > 프로젝트 > 플러그인 — 위가 아래를 이깁니다. 충돌이 싫으면 이름을 구체적으로 지으세요(frontend-review 등).

한눈에

엔터프라이즈 > 개인 > 프로젝트 > 플러그인. 위가 아래를 이깁니다.

충돌을 피하는 가장 쉬운 방법은 이름을 구체적으로 짓는 것입니다. 그냥 review 대신 frontend-review, backend-review처럼요. 이름이 다르면 충돌 자체가 없습니다.

스킬 고치기·지우기

Update & remove

한 번 만든 스킬도 얼마든지 손볼 수 있습니다.

핵심

무엇을 바꾸든 ‘재시작’을 잊지 마세요. Claude Code는 켤 때 스킬을 한 번 읽어들이니, 재시작해야 변경이 반영됩니다.

한 줄 정리

스스로 점검

Check yourself

정답을 먼저 떠올려 본 뒤 골라 보세요. 맞히면 설명이 나옵니다.

Q1스킬을 새로 만든 뒤, Claude Code가 그것을 인식하게 하려면?

Q2이름이 같은 스킬이 개인과 엔터프라이즈에 모두 있으면, 어느 쪽이 우선인가요?

Q3Claude가 요청에 맞는 스킬을 찾을 때, 시작 단계에서 주로 보는 것은?

생각해보기

LAB · 실습 콘솔SKILL.MD FRONTMATTER

SKILL.md 검사기

SKILL.md 맨 위 라벨의 이름·설명·도구 항목이 agentskills.io 공개 표준에 맞는지 점검하고, 규칙을 벗어난 곳을 항목별로 짚어 봅니다.

index.js
// 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(" / ") : "표준과 일치"));
});