KMP Todo 앱에서 commonMain에 expect 함수를 하나 추가할 때마다 androidMain, iosMain에 actual 구현체를 각각 만들어야 했습니다. 시그니처를 그대로 옮겨 적고 본문만 채우는 반복 작업이라, 이 부분을 Claude Code 커스텀 슬래시 커맨드로 묶어봤습니다.

어떤 작업이었나

TodoRepository에 로컬 백업 기능을 추가하면서 expect suspend fun loadFromDisk(): List<Todo>를 commonMain에 선언했습니다. 이런 expect 선언을 만들 때마다 android/iOS 양쪽에 actual을 채워 넣어야 하는데, 프로젝트가 커지면서 이 작업이 하루에도 몇 번씩 반복됐습니다. 매번 "이런 expect가 있으니 actual 만들어줘"라고 새로 프롬프트를 쓰는 대신, 명령 하나로 고정하기로 했습니다.

AI를 어떻게 활용했나

프로젝트 루트에 .claude/commands/expect-actual.md 파일을 만들었습니다.

---
description: commonMain의 expect 선언에 맞춰 android/iOS actual 구현체를 생성
argument-hint: <expect 선언이 있는 파일 경로>
allowed-tools: Read, Edit, Write, Grep, Glob
---

`$1`에 정의된 expect 선언을 모두 찾아라. 각 선언에 대해:

1. androidMain에 대응하는 actual 구현체가 있는지 확인하고, 없으면 같은 패키지 경로에 새로 만들 것
2. iosMain도 동일하게 처리
3. 이미 존재하는 actual 구현체는 시그니처만 맞추고 본문은 건드리지 말 것
4. 새로 만든 파일은 기존 actual 구현체의 코딩 스타일(들여쓰기, import 순서)을 따를 것

처음 버전에는 4번 제약이 없었는데, 그때는 커맨드가 build.gradle.kts의 소스셋 설정까지 손대려고 시도했습니다. expect/actual과 무관한 파일이라 매번 diff를 되돌려야 했고, allowed-tools에서 Bash를 빼고 프롬프트에 "expect/actual 파일 외에는 건드리지 말 것"을 명시한 뒤로는 재현되지 않았습니다.

/expect-actual commonMain/data/TodoRepository.kt를 실행한 결과:

  • androidMain 쪽에는 이미 DataStore 기반 구현이 있어서 시그니처만 맞춰 수정
  • iosMain 쪽에는 파일이 없어서 NSUserDefaults 기반 구현을 새로 생성
  • 두 파일 모두 기존 코드의 import 정렬 방식을 그대로 따름

결과와 얻은 팁

  • argument-hint를 정확히 써두면 커맨드 자동완성에 인자 힌트가 그대로 노출되어, 나중에 다시 쓸 때 인자 순서를 헷갈리지 않습니다.
  • allowed-tools를 필요한 도구만 나열해두면 권한 프롬프트가 매번 뜨지 않으면서도, 커맨드가 의도치 않은 파일을 건드리는 걸 막아줍니다.
  • "기존 파일은 시그니처만 맞추고 본문은 건드리지 말 것"처럼 명시적 제약을 프롬프트에 넣지 않으면, 이미 구현된 함수 본문까지 다시 쓰려고 시도하는 경우가 있었습니다. 반복 작업을 커맨드로 고정할 때는 이런 제약을 처음부터 박아두는 편이 낫습니다.
  • 한 번 만들어두면 이후 expect 함수를 추가할 때마다 같은 커맨드를 재사용할 수 있어서, 매번 프롬프트를 새로 작성할 필요가 없어졌습니다.