Covenant



시작하며


Claude Code를 쓰다 보면 비슷한 요청을 계속 반복하게 됩니다.


"테스트는 Given-When-Then으로 작성해줘", "API 응답에는 에러 핸들링을 포함해줘", "우리 팀 컨벤션에 맞춰 파일명을 정리해줘" 같은 요청입니다. 처음 몇 번은 프롬프트로 적으면 되지만, 프로젝트가 길어질수록 같은 말을 반복하는 것이 번거로워집니다.


이때 사용할 수 있는 기능이 Claude Code의 Skill입니다.


Skill은 Claude에게 특정 업무를 어떻게 수행해야 하는지 알려주는 재사용 가능한 지침입니다. 회사에서 신입사원에게 온보딩 문서를 주는 것처럼, Claude에게도 프로젝트의 규칙과 작업 방식을 문서로 전달하는 방식입니다.


이번 글에서는 Claude Code Skill이 무엇인지, Agent와는 무엇이 다른지, 실제로 SKILL.md를 어떻게 작성하면 좋은지 정리해봅니다.




Skill이란 무엇인가?


Claude Code에서 Skill은 AI 에이전트에게 "이렇게 일해라"를 알려주는 업무 규칙 문서입니다.


개발 조직에는 코딩 컨벤션, 브랜치 전략, 코드 리뷰 가이드, 테스트 작성 규칙 같은 문서가 있습니다. Skill은 이런 문서를 Claude가 읽고 따를 수 있는 형태로 만든 것입니다.


예를 들어 다음과 같은 규칙을 Skill로 만들 수 있습니다.


  • 모든 테스트는 Given / When / Then 주석으로 구분한다.
  • API 응답은 반드시 에러 핸들링을 포함한다.
  • React 컴포넌트 파일 이름은 PascalCase로 작성한다.
  • 서비스 레이어 테스트는 @SpringBootTest 대신 Mockito 기반 단위 테스트를 우선한다.

Skill을 한 번 만들어 두면 이후 비슷한 작업에서 같은 규칙을 재사용할 수 있습니다. 매번 긴 프롬프트를 붙여 넣는 대신, Skill이 필요한 순간에 Claude가 해당 지침을 읽고 작업하도록 만드는 방식입니다.


다만 한 가지 주의할 점이 있습니다.


Skill은 항상 자동으로 적용되는 마법 같은 설정은 아닙니다. 사용자가 명시적으로 특정 Skill을 사용하라고 요청할 수도 있고, Claude가 description을 보고 적절한 Skill이라고 판단해 사용할 수도 있습니다. 따라서 Skill의 발동 조건을 잘 쓰는 것이 중요합니다.




Agent와 Skill의 차이


Claude Code를 처음 접하면 Agent와 Skill이 헷갈릴 수 있습니다.


둘 다 AI의 동작을 정리하는 개념처럼 보이지만 역할이 다릅니다. 한 문장으로 정리하면 Agent는 "누가 일할 것인가"에 가깝고, Skill은 "어떻게 일할 것인가"에 가깝습니다.


정리하면 다음과 같습니다.


구분 에이전트 스킬
역할 특정 업무를 담당하는 전문 작업자 작업자가 공통으로 따르는 업무 규칙
비유 기획자, 개발자, 테스터 온보딩 문서, 코딩 컨벤션, 사내 규칙
적용 범위 특정 자동화 작업에 특화 여러 작업에서 재사용 가능
예시 코드를 구현하는 에이전트 "모든 테스트는 Given-When-Then으로 작성한다"

Agent가 작업의 담당자를 정한다면, Skill은 그 담당자가 지켜야 할 작업 방식을 정합니다.




Skill의 파일 구조


Skill은 단순한 폴더 구조로 되어 있습니다. 복잡한 설정이 필요하지 않습니다.


your-skill-name/
├── SKILL.md          ← 필수: 핵심 지침 파일
├── scripts/          ← 선택: 실행 가능한 스크립트
│   └── validate.py
├── references/       ← 선택: 참고 문서
│   └── api-guide.md
└── assets/           ← 선택: 템플릿, 폰트 등
    └── template.md

가장 중요한 파일은 SKILL.md입니다. 이 파일에 Skill의 이름, 설명, 사용 조건, 실제 작업 규칙을 작성합니다.


scripts/ 폴더는 선택사항이지만 중요한 워크플로에서는 적극적으로 활용하는 것이 좋습니다. 데이터 형식이 맞는지, 필수 필드가 채워졌는지, 테스트 파일 이름 규칙이 맞는지 같은 검증은 문장으로 "확인해라"라고 적는 것보다 실행 가능한 스크립트로 처리하는 편이 훨씬 안정적입니다.


언어 지시는 Claude가 상황에 따라 다르게 해석할 수 있지만, 코드는 항상 같은 방식으로 실행됩니다.


예를 들어 테스트 메서드명이 팀 규칙을 따르는지 확인하는 스크립트를 Skill에 함께 둘 수 있습니다.


# scripts/validate_test.py
import ast
import sys


def validate(filepath):
    with open(filepath) as f:
        source = f.read()

    tree = ast.parse(source)
    methods = [
        node.name
        for node in ast.walk(tree)
        if isinstance(node, ast.FunctionDef)
    ]

    errors = []
    for method in methods:
        if method.startswith("test") and method.count("_") < 2:
            errors.append(
                f"메서드명 형식 오류: {method} -> '메서드명_상황_기대결과' 형식이어야 합니다"
            )

    if errors:
        print("\n".join(errors))
        sys.exit(1)

    print("검증 통과")


validate(sys.argv[1])

작성할 때는 다음 규칙을 지키는 것이 좋습니다.


  • SKILL.md 파일 이름은 대소문자를 정확히 지킵니다. skill.md, SKILL.MD처럼 쓰지 않습니다.
  • 폴더 이름은 kebab-case를 사용합니다. 예를 들어 my-skill처럼 작성합니다.
  • Skill 폴더 안에는 README.md를 넣지 않는 편이 좋습니다.



SKILL.md 직접 만들어보기


이번에는 "JUnit Given-When-Then 테스트 코드 작성 Skill"을 처음부터 만들어보겠습니다.


개발자마다 테스트 스타일이 다르면 테스트 코드를 읽는 비용이 커집니다. 어떤 사람은 test1처럼 이름을 짓고, 어떤 사람은 getUserTest처럼 작성합니다. Given-When-Then 구분도 없는 경우가 많습니다.


이런 팀 규칙을 Skill로 만들면 테스트 코드 스타일을 어느 정도 통일할 수 있습니다.




1. 프론트매터 작성


SKILL.md는 YAML 프론트매터와 마크다운 본문으로 구성됩니다.


본문의 지침도 중요하지만, 실제로는 프론트매터의 description이 매우 중요합니다. 아무리 좋은 Skill을 만들어도 적절한 순간에 로드되지 않으면 소용이 없습니다. Claude가 이 Skill을 언제 사용해야 하는지 판단하는 진입점이 바로 description입니다.


description에는 두 가지를 반드시 포함하는 것이 좋습니다.


  • What: 이 Skill이 무엇을 하는지
  • When: 사용자가 실제로 입력할 법한 트리거 문구

---
name: spring-junit-test-style
description: Spring Boot 프로젝트의 JUnit 테스트 코드를 Given-When-Then 패턴으로 작성합니다.
             "테스트 코드 작성", "단위 테스트 만들어줘", "JUnit 테스트",
             "Service 테스트", "Repository 테스트" 등의 요청 시 사용합니다.
             통합 테스트(@SpringBootTest)나 E2E 테스트에는 사용하지 마세요.
---

좋은 description과 나쁜 description을 비교하면 다음과 같습니다.


  • "좋은 테스트 코드를 작성한다": 너무 모호하고 트리거 문구가 없습니다.
  • "MockitoExtension 기반 단위 테스트를 생성한다": 기술 용어만 있고 사용자가 실제로 말할 표현이 부족합니다.
  • "JUnit 테스트 코드를 Given-When-Then 패턴으로 작성합니다. '테스트 코드 만들어줘', 'Service 테스트' 등의 요청 시 사용합니다.": 무엇을 하는지와 사용자가 입력할 표현이 함께 들어 있습니다.



2. 본문 작성


프론트매터 아래에는 Claude가 따라야 할 실제 규칙을 작성합니다. 핵심은 구체적이고 실행 가능하게 쓰는 것입니다.


# JUnit Given-When-Then 테스트 작성 가이드

## 기본 규칙
- 모든 테스트는 Given / When / Then 주석으로 섹션을 구분한다
- 테스트 메서드 이름은 "메서드명_상황_기대결과" 형식으로 작성한다
- @ExtendWith(MockitoExtension.class)를 사용하고 @SpringBootTest는 지양한다
- 외부 의존성은 @Mock, 테스트 대상 클래스는 @InjectMocks로 선언한다

## 테스트 구조 예시
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    @DisplayName("존재하는 유저 ID로 조회하면 유저 정보를 반환한다")
    void findById_existingUser_returnsUser() {
        // Given
        Long userId = 1L;
        User mockUser = new User(userId, "홍길동", "hong@example.com");
        given(userRepository.findById(userId)).willReturn(Optional.of(mockUser));

        // When
        UserResponse result = userService.findById(userId);

        // Then
        assertThat(result.getName()).isEqualTo("홍길동");
        assertThat(result.getEmail()).isEqualTo("hong@example.com");
    }
}

## 예외 케이스 테스트 규칙
- 예외 발생 케이스는 assertThatThrownBy()를 사용한다
- 예외 메시지 검증은 hasMessageContaining()으로 작성한다

이 정도로 구체적으로 적어두면 Claude가 테스트를 작성할 때 어떤 구조를 따라야 하는지 훨씬 명확해집니다.




3. 폴더 생성과 활성화


프로젝트 루트에 폴더를 만들고 SKILL.md를 배치하면 됩니다.


mkdir -p .claude/skills/spring-junit-test-style

위에서 작성한 프론트매터와 본문을 .claude/skills/spring-junit-test-style/SKILL.md에 저장한 뒤 Claude Code를 재시작하면 Skill이 인식됩니다.


이후에는 다음과 같은 요청에서 자동으로 활성화될 수 있습니다.


  • "UserService 테스트 코드 작성해줘"
  • "이 메서드에 대한 단위 테스트 만들어줘"
  • "예외 케이스 테스트 추가해줘"



4. Skill 적용 전후 비교


Skill이 적용되면 실제로 어떤 차이가 생기는지 확인해보겠습니다.


Skill 없이 작성된 테스트는 구조도, 의도도 제각각일 수 있습니다.


@Test
void test1() {
    UserService service = new UserService(userRepository);
    when(userRepository.findById(1L)).thenReturn(Optional.empty());
    assertThrows(RuntimeException.class, () -> service.findById(1L));
}

@Test
void getUserTest() {
    User user = new User(1L, "홍길동", "hong@example.com");
    when(userRepository.findById(1L)).thenReturn(Optional.of(user));
    UserResponse res = userService.findById(1L);
    assertEquals("홍길동", res.getName());
}

Skill을 적용하면 Given-When-Then 패턴으로 테스트 구조가 통일됩니다.


@ExtendWith(MockitoExtension.class)
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    @DisplayName("존재하는 유저 ID로 조회하면 유저 정보를 반환한다")
    void findById_existingUser_returnsUser() {
        // Given
        Long userId = 1L;
        User mockUser = new User(userId, "홍길동", "hong@example.com");
        given(userRepository.findById(userId)).willReturn(Optional.of(mockUser));

        // When
        UserResponse result = userService.findById(userId);

        // Then
        assertThat(result.getName()).isEqualTo("홍길동");
        assertThat(result.getEmail()).isEqualTo("hong@example.com");
    }

    @Test
    @DisplayName("존재하지 않는 유저 ID로 조회하면 UserNotFoundException을 던진다")
    void findById_notExistingUser_throwsException() {
        // Given
        Long userId = 999L;
        given(userRepository.findById(userId)).willReturn(Optional.empty());

        // When & Then
        assertThatThrownBy(() -> userService.findById(userId))
            .isInstanceOf(UserNotFoundException.class)
            .hasMessageContaining("유저를 찾을 수 없습니다");
    }
}

테스트 코드의 품질을 Claude에게 매번 설명하는 대신, Skill에 규칙으로 고정해두는 것이 핵심입니다.




Skill 마켓플레이스 활용


직접 Skill을 만드는 것 외에도, Anthropic이 공개한 Skill 마켓플레이스에서 검증된 Skill을 참고할 수 있습니다.


  1. anthropics/skills GitHub 저장소를 방문합니다.
  2. 원하는 기술 스택의 Skill 폴더를 확인합니다.
  3. 필요한 Skill을 .claude/skills/ 폴더에 복사합니다.
  4. Claude Code를 재시작한 뒤 사용합니다.

마켓플레이스에서 제공되는 Skill 예시는 다음과 같습니다.


  • Figma 개발 핸드오프 자동화 Skill
  • Linear 스프린트 플래닝 Skill
  • Sentry 버그 감지 및 PR 리뷰 Skill
  • Notion, Asana, Slack 연동 Skill

처음부터 모든 Skill을 직접 만들기보다, 공개된 Skill의 구조를 보고 자기 팀에 맞게 바꾸는 것도 좋은 접근입니다.




MCP와 Skill 함께 사용하기


MCP(Model Context Protocol)를 이용하면 Claude를 Notion, Jira, Slack 같은 외부 서비스에 연결할 수 있습니다.


MCP만 연결해두면 Claude가 해당 서비스의 데이터에 접근할 수 있습니다. 하지만 "그 데이터를 어떻게 써야 하는지"까지 자동으로 알지는 못합니다. 바로 이 지점에서 Skill이 필요합니다.


요리에 비유하면 MCP는 재료와 조리 도구를 갖춘 주방이고, Skill은 그 주방에서 무엇을 어떻게 만들지 알려주는 레시피입니다. 주방만 있고 레시피가 없으면 무엇을 어떻게 만들어야 할지 막막합니다.


정리하면 다음과 같습니다.


구분 MCP Skill
역할 외부 서비스 연결 및 데이터 접근 서비스를 활용하는 워크플로우 정의
답하는 질문 Claude가 무엇을 할 수 있는가 Claude가 어떻게 해야 하는가
예시 Jira 이슈 조회/생성 API 호출 스프린트 플래닝 절차, 이슈 작성 규칙

MCP 서버를 연결해두었는데도 Claude가 기대한 대로 동작하지 않는다면, 대부분 "도구는 있는데 워크플로우가 없는" 상태입니다.


  • 매번 "Jira에서 이번 스프린트 이슈 가져와서 우선순위 정리해줘" 같은 긴 지시를 반복해야 합니다.
  • 개발자마다 다르게 요청하기 때문에 결과가 들쑥날쑥합니다.
  • 팀 컨벤션, 이슈 제목 형식, 담당자 배정 규칙이 반영되지 않습니다.

Skill에 MCP 도구 호출 순서와 팀 규칙을 함께 정의해두면 한 마디만 해도 Claude가 전체 절차를 따라갈 수 있습니다.


---
name: jira-sprint-planning
description: Jira MCP를 활용해 스프린트 플래닝을 진행합니다.
             "스프린트 계획 잡아줘", "이번 스프린트 정리해줘",
             "Jira 이슈 우선순위 정해줘" 등의 요청 시 사용합니다.
---

# Jira 스프린트 플래닝 워크플로우

## Step 1: 현황 파악
- MCP로 현재 백로그에서 미완료 이슈 전체를 조회한다
- 마감일이 지난 이슈가 있으면 먼저 보고한다

## Step 2: 우선순위 결정
- Priority가 Highest/High인 이슈를 최상단에 배치한다
- 팀원 1인당 최대 스토리포인트 합계가 13을 넘지 않도록 조정한다

## Step 3: 이슈 업데이트
- 제목은 반드시 "[컴포넌트] 동사 + 목적어" 형식으로 수정한다
  예) [Auth] 소셜 로그인 기능 구현
- assignee와 sprint 필드를 반드시 채운다
- 변경 내역을 표 형태로 요약해서 보여준다

이렇게 해두면 "이번 스프린트 계획 잡아줘" 한 마디로 Jira 조회, 우선순위 정렬, 이슈 업데이트 흐름을 더 안정적으로 진행할 수 있습니다.



LinkedIn: 원문 링크




효과적인 Skill 작성 팁


Skill을 작성할 때는 추상적인 좋은 말보다 실행 가능한 규칙을 적는 것이 중요합니다.


이렇게 작성하세요 이렇게 하지 마세요
"함수는 20줄 이내로 작성한다" "좋은 코드를 작성한다"
트리거 문구를 구체적으로 명시한다 언제 쓸지 설명 없이 기능만 서술한다
범용 규칙은 Skill에, 특수 규칙은 Agent에 둔다 모든 규칙을 하나의 Skill에 몰아넣는다
개발하면서 필요한 규칙을 점진적으로 추가한다 처음부터 완벽한 Skill을 만들려고 한다
구체적인 예시 코드를 Skill 안에 포함한다 추상적인 설명만 나열한다

한 가지 더 기억할 점이 있습니다. Skill은 완성품이 아니라 살아있는 문서입니다.


처음 만든 Skill이 완벽할 필요는 없습니다. 사용하면서 트리거 문구가 부족하다는 것을 발견하면 추가하고, 팀 컨벤션이 바뀌면 본문도 업데이트해야 합니다. MCP 도구의 스펙이 변경되었다면 그에 맞춰 수정하는 것도 Skill의 수명주기입니다.


그리고 이런 개선이 가능하려면 측정이 먼저입니다. Skill 도입 전후를 비교해보면 효과가 생각보다 뚜렷하게 나타납니다. 동일한 워크플로 기준으로 도입 전에는 약 12,000토큰과 5회 이상의 대화 왕복, 3건의 API 실패가 발생하던 것이, 도입 후에는 약 6,000토큰, 1~2회 왕복, API 실패 0건으로 줄어드는 경우도 있습니다.


수치 자체보다 중요한 것은 이런 개선을 측정할 수 있다는 사실입니다. 측정할 수 있어야 개선할 수 있습니다.




자주 발생하는 문제와 해결법


Skill이 자동으로 활성화되지 않을 때


description 필드의 트리거 문구가 부족한 경우입니다. 사용자가 실제로 사용할 법한 표현을 더 추가해야 합니다.


# 트리거 문구가 부족한 경우
description: JUnit 테스트를 작성합니다.

# 트리거 문구를 풍부하게 추가
description: Spring Boot 프로젝트의 JUnit 테스트 코드를 Given-When-Then 패턴으로 작성합니다.
             "테스트 코드 작성", "단위 테스트 만들어줘", "JUnit 테스트",
             "Service 테스트", "Repository 테스트" 등의 요청 시 사용합니다.

Skill이 엉뚱한 상황에서 활성화될 때


description이 너무 넓게 작성된 경우입니다. "이런 경우에는 사용하지 마세요" 문구를 추가하면 효과적입니다.


description: Spring Boot 프로젝트의 JUnit 테스트 코드를 Given-When-Then 패턴으로 작성합니다.
             "테스트 코드 작성", "단위 테스트 만들어줘" 요청 시 사용합니다.
             통합 테스트(@SpringBootTest)나 E2E 테스트에는 사용하지 마세요.



정리


Skill은 Claude Code에게 팀의 작업 방식을 알려주는 재사용 가능한 업무 규칙 문서입니다.


Agent가 "누가 일할 것인가"를 정한다면, Skill은 "어떻게 일할 것인가"를 정합니다. 그래서 Skill은 특정 작업 하나를 해결하는 도구라기보다, 반복되는 작업 방식을 표준화하는 장치에 가깝습니다.


좋은 Skill을 만들기 위해서는 세 가지를 기억하면 됩니다.


  • description에 실제 사용자가 입력할 법한 트리거 문구를 넣습니다.
  • 본문에는 추상적인 원칙보다 실행 가능한 규칙과 예시를 적습니다.
  • 검증이 필요한 작업은 가능하면 scripts/ 폴더의 실행 가능한 코드로 처리합니다.

처음부터 완벽한 Skill을 만들 필요는 없습니다. 실제로 사용하면서 부족한 트리거를 추가하고, 팀 규칙이 바뀌면 본문을 업데이트하고, 반복 검증이 필요하면 스크립트를 붙이면 됩니다.



Source: ByteByteGo




참고 자료