iBetter Books
수정

커맨드 작성과 활용

커맨드를 만드는 일은 마크다운 파일 하나를 두는 것으로 끝난다. 별도 빌드도, 등록 절차도 없다. 이 절은 파일을 어디에 두고 어떻게 이름 짓는지, 프론트매터로 무엇을 제어하는지, 인자와 셸 출력을 어떻게 본문에 끼워 넣는지, 그리고 누가 커맨드를 호출하게 할지를 다룬다. 시험은 이 메커니즘의 동작 주체와 순서를 집요하게 묻는다.

위치, 명명, 범위

커맨드 파일은 두 곳에 둘 수 있다. 프로젝트 범위는 .claude/commands/(스킬은 .claude/skills/)이고, 개인 범위는 ~/.claude/commands/(스킬은 ~/.claude/skills/)다. 프로젝트 범위는 해당 저장소에서만, 개인 범위는 모든 프로젝트에서 쓸 수 있다. 명명 규칙은 단순하다. 커맨드 파일의 경우 확장자를 뗀 파일 이름이 커맨드 이름이 된다. deploy.md/deploy가 된다. 스킬의 경우 디렉터리 이름이 커맨드 이름이 된다. .claude/skills/deploy-staging/SKILL.md/deploy-staging이 된다.

여기서 함정 하나. 스킬 프론트매터의 name 필드는 목록에 표시되는 이름표일 뿐, 입력하는 커맨드 이름을 바꾸지 않는다. 커맨드 이름은 파일이나 디렉터리의 위치에서 온다. 또한 같은 이름이 중첩 디렉터리에 있을 때만, 예를 들어 모노레포에서 apps/web/.claude/skills/deploy/에 있는 스킬은 /apps/web:deploy라는 디렉터리 한정 이름으로 나타난다.

프론트매터로 동작 제어

파일 맨 위 --- 사이의 YAML 프론트매터로 커맨드 동작을 설정한다. 모두 선택 사항이지만 description은 권장된다. Claude가 이 설명으로 커맨드를 언제 쓸지 판단하기 때문이다.

# .claude/commands/fix-issue.md
---
description: GitHub 이슈를 우리 코딩 표준에 맞춰 수정한다
argument-hint: [issue-number]
disable-model-invocation: true
allowed-tools: Bash(git add *) Bash(git commit *)
model: claude-opus-4-8
---

GitHub 이슈 $ARGUMENTS 를 코딩 표준에 맞춰 수정한다.

1. 이슈 설명을 읽는다
2. 요구사항을 파악한다
3. 수정을 구현하고 테스트를 작성한다
4. 커밋을 만든다

핵심 필드와 함정은 다음과 같다. description은 커맨드가 무엇을 하고 언제 쓰는지를 적고, argument-hint는 자동완성에서 보일 인자 힌트다. model은 이 커맨드가 활성화된 동안 사용할 모델을 지정한다(값을 쓸 때는 claude-opus-4-8을 쓴다). allowed-tools는 시험에서 가장 많이 틀리는 필드다. 이것은 나열한 도구를 승인 없이 쓰도록 허용할 뿐, 도구를 제한하지 않는다. 나머지 도구도 여전히 호출 가능하며 권한 설정의 적용을 받는다.

인자 치환

인자는 본문의 플레이스홀더로 들어온다. $ARGUMENTS는 커맨드 이름 뒤에 입력한 모든 인자를 하나의 문자열로 받는다. 위치별로는 $ARGUMENTS[N] 또는 줄임 표기 $N을 쓰며, 0부터 시작한다. $0이 첫 번째, $1이 두 번째다. 프론트매터 arguments 필드에 이름을 선언하면 $name 형태로 쓸 수 있다. 예를 들어 arguments: [issue, branch]로 선언하면 $issue가 첫 번째, $branch가 두 번째 인자로 확장된다.

함정 두 가지. 본문에 $ARGUMENTS가 없는데 인자를 넘기면, Claude Code가 본문 끝에 ARGUMENTS: <입력값>을 덧붙여 입력 내용이 그대로 전달된다. 또 위치 인자는 셸식 따옴표 규칙을 따르므로, 여러 단어를 하나의 인자로 넘기려면 따옴표로 감싼다. /명령 "hello world" second이면 $0hello world, $1second가 된다.

셸 주입과 호출 제어

!`<명령>` 구문은 셸 명령을 실행해 그 출력을 본문에 끼워 넣는다. 여기서 시험의 단골 질문이 나온다. 이 명령을 실행하는 주체는 누구인가. Claude가 아니다. 커맨드 내용이 Claude에게 전달되기 전에, 즉 전처리 단계에서 하네스가 명령을 실행하고 출력으로 플레이스홀더를 치환한다. Claude는 이미 채워진 최종 프롬프트만 본다.

# .claude/commands/summarize-changes.md
## 현재 변경

!`git diff HEAD`

## 지시

위 변경을 두세 개 불릿으로 요약하고 위험 요소를 나열한다.

치환은 원본 파일을 한 번 훑으며 일어난다. 명령 출력은 평문으로 삽입되고 다시 스캔되지 않으므로, 명령이 또 다른 플레이스홀더를 내보내 다음 패스에서 확장되게 만들 수는 없다. 또 이 인라인 형태는 !가 줄 맨 앞이나 공백 바로 뒤에 올 때만 인식된다. KEY=!`cmd` 처럼 다른 문자 뒤에 오면 명령이 실행되지 않고 그대로 문자열로 남는다.

호출 주체는 두 프론트매터 필드로 제어한다. disable-model-invocation: true는 Claude의 자동 호출을 막아 사용자만 /이름으로 부를 수 있게 한다. /deploy, /commit처럼 부작용이 있어 실행 시점을 통제해야 하는 워크플로우에 쓴다. 반대로 user-invocable: false/ 메뉴에서 숨겨 Claude만 호출하게 한다. 사용자가 직접 부를 의미가 없는 배경 지식용 스킬에 쓴다.

정리

  • 커맨드는 프로젝트 범위 .claude/commands/(또는 .claude/skills/)와 개인 범위 ~/.claude/에 둔다. 커맨드 파일은 확장자를 뗀 파일명이, 스킬은 디렉터리명이 커맨드 이름이 된다.
  • allowed-tools는 나열한 도구를 승인 없이 허용할 뿐 도구 풀을 제한하지 않는다. 이것을 "제한"으로 읽으면 함정에 빠진다.
  • $ARGUMENTS는 전체 인자를 한 문자열로, $0·$1은 0부터의 위치 인자를, 프론트매터 arguments에 선언한 이름은 $name으로 확장된다.
  • !`명령`은 Claude가 아니라 하네스가 전처리 단계에서 실행해 출력을 본문에 끼워 넣는다. 출력은 다시 스캔되지 않으며, !는 줄 맨 앞이나 공백 뒤에서만 인식된다.
  • disable-model-invocation: true는 사용자 전용 호출, user-invocable: false는 Claude 전용 호출로 호출 주체를 가른다.