이슈 하나를 개발하는 데 평균 30~60분이 걸렸다. 이 시간을 줄이고 싶어서 Claude에게 "개발 속도를 개선할 방법이 있어?"라고 물은 것이 시작이었다. 결론부터 말하면 병목은 코딩이 아니었다. 그 진단 과정에서 나온 결론을 skill-creator로 굳힌 것이 dev-issue 스킬이다. 이 글은 스킬 소개보다 "왜, 어떤 순서로 이 스킬이 만들어졌는지"를 정리한 기록이다.
1. 출발점: 느낌이 아니라 측정부터
처음 던진 질문은 단순했다. "이슈 한 개 개발하는 데 30~60분 걸리는데 속도를 개선할 방법이 있어?"
Claude는 처방보다 먼저 되물었다. 30~60분 안에서 시간이 어디로 새는지에 따라 처방이 완전히 달라진다는 것이다. 선택지(코드 위치 탐색, 동작 확인, 검증·CI 왕복 등)를 놓고 내가 고른 답은 검증과 CI 왕복이었다. cs-fixer, phpstan, phpunit을 돌리고, CI가 실패하면 고쳐서 다시 푸시하는 패턴이다.
그다음부터는 추측이 아니라 실측이었다. 같은 질문을 두 저장소에서 각각 던졌고, 결과는 비슷하게 수렴했다.
| 저장소 A | 저장소 B | |
|---|---|---|
| 코드 스타일 검사 | 약 2초 | 약 0.3초 |
| PHPStan | 약 4초 | 약 1초 |
| PR 생성에서 머지까지 | (빠름) | 평균 2~8분 |
| CI 1회 | 약 4분 | 매번 122~138초 |
| 로컬에서 전체 테스트 | 가능, 185개 통과 | 742개 중 658개가 에러 |
저장소 B의 658개 에러가 결정적이었다. 로컬 테스트가 아예 돌지 않으니 CI가 유일한 검증 수단이었고, 그래서 "일단 푸시하고 CI에서 확인하고 실패하면 수정하는" 왕복이 구조적으로 강제되고 있었다. 원인은 .env의 database.tests가 이 머신에 없는 root 계정을 가리키고 있던 것이었다. 계정을 고치고 테스트 DB를 마이그레이션하자 742개가 77초에 전부 통과했다.
저장소 A에서는 반대의 발견이 있었다. 테스트 스위트를 빠른 unit 스위트와 느린 feature 스위트로 나누려고 보니, tests/unit 안의 두 클래스가 실제로는 DB에 의존하고 있었다. 전체를 돌릴 때는 앞선 feature 테스트가 마이그레이션을 해놓아서 우연히 통과하던 것이었다. 이 둘을 feature 스위트로 옮기자 composer test:unit이 37개 테스트를 0.79초에 끝냈다.
2. 첫 번째 결과물: 사람이 기억하지 않아도 되는 장치
진단이 끝난 뒤 먼저 만든 것은 스킬이 아니라 훅이었다.
pre-commit: 스테이징된 PHP 파일을 CS Fixer로 자동 수정하고 다시 스테이징pre-push: 푸시 직전에composer check를 돌리고 실패하면 푸시를 중단- 테스트 스위트 분리와 빠른 검증용 composer 스크립트
- 팀 공유 문서와 전역 Claude 규칙(
testing.md)에 같은 내용을 일반화해서 기록
훅을 만들고 나서 재미있는 일이 있었다. 문서를 푸시하는 순간 방금 만든 pre-push 훅이 실제로 돌아서 composer check를 실행하고 통과한 뒤 푸시가 진행됐다. 개선안이 스스로를 검증한 셈이었다.
여기까지는 "푸시 전 검증"이라는 한 구간의 문제를 푼 것이다. 하지만 측정하면서 보인 두 번째 병목이 남아 있었다. 코드를 짜기 전에 "무엇을, 어디를 손댈지"를 매번 새로 찾는 탐색 비용이다. 저장소 B에서는 파일이 300개가 넘었고, 이슈마다 같은 질문(이미 구현된 건 아닌지, 복제할 패턴이 있는지, 어느 파일인지)을 처음부터 다시 하고 있었다.
3. 그래서 스킬로: 반복되는 절차를 문서로 고정
같은 날 오전, 이슈 착수 절차를 묶은 start-issue 스킬이 이미 만들어져 있었다. dev 최신화, 브랜치 생성, 관련 파일 탐색을 한 번에 하는 스킬이다. 종료 쪽에는 end-issue(작업내역 코멘트와 이슈 닫기)가 있었다. 빠져 있던 것은 둘 사이, 즉 탐색 → 구현 → 검증 → PR → 머지를 순서대로 엮는 상위 흐름이었다.
그 빈칸을 채운 것이 dev-issue다. 이 스킬의 설계 방침은 측정 결과를 그대로 순서로 옮긴 것이다.
- 구현 전에 탐색부터 위임한다. 불확실성이 가장 큰 누수였기 때문이다.
- 푸시 전에 전체 검증을 통과시킨다. CI 왕복이 가장 큰 누수였기 때문이다.
- 작업 크기에 프로세스를 맞춘다. 검증된 패턴의 복제와 신규 설계를 같은 무게로 다루지 않는다.
스킬 파일은 새 기능을 구현하지 않는다. start-issue, test-driven-development, end-issue, prune-branch를 올바른 순서로 호출하는 오케스트레이터다.
4. skill-creator로 다듬은 부분
skill-creator로 작업하면서 스킬 파일에 반영된 결정은 이런 것들이다.
description을 트리거 문장으로 쓴다. 스킬이 언제 불려야 하는지는 본문이 아니라 description이 결정한다. 그래서 "이슈 231 개발", "dev-issue 231", "이슈 처리해줘" 같은 실제로 입력할 법한 문장을 description에 그대로 넣었다. 그리고 start-issue·end-issue를 조합한 상위 스킬이며 개별 단계만 필요하면 그 스킬을 직접 쓰라는 문장도 넣어, 스킬끼리 역할이 겹치지 않게 했다.
왜 이렇게 하는지를 본문에 적는다. 단순히 "검증을 돌려라"가 아니라 "CI 재실패 왕복이 사이클 시간을 가른다"는 이유를 같이 적었다. 이유가 있으면 모델이 상황이 조금 달라져도 취지에 맞게 판단한다.
승인이 필요한 지점을 표로 못 박는다. 착수·탐색·구현·로컬 검증·커밋은 승인 없이 진행하고, 푸시·PR 생성·PR 머지·이슈 종료만 매번 사용자 승인을 받는다. 외부에 영향을 주거나 되돌리기 어려운 일과 그렇지 않은 일을 구분한 것이다.
함정은 발견할 때마다 한 줄씩 추가한다. 예를 들어 composer check에는 CS Fixer가 빠져 있어 로컬 그린이 CI 레드가 될 수 있으므로 composer ci를 쓰라는 문구, 그리고 Closes #NN은 기본 브랜치에 머지될 때만 이슈를 닫는데 우리 흐름은 feature → dev → main이라 dev 머지로는 닫히지 않는다는 문구가 그렇다.
5. 첫 실전: 만든 지 1분 뒤
스킬 파일이 만들어진 것이 13:18이고, 첫 실행 기록은 13:19다. 다른 저장소의 82번 이슈(AI 대시보드 자연어 질의)로 /dev-issue 82를 실행했다.
흐름은 설계한 그대로 흘렀다.
start-issue가gh인증·워킹트리 상태를 확인하고 이슈를 읽은 뒤 브랜치를 만들었다.- 탐색 서브에이전트가 "미구현이며, 복제할 AI 클라이언트 구조가 이미 완비되어 있다"는 결과를 가져왔다. 새로 설계할 필요 없이 기존 패턴을 따라가면 되는 작업이었다.
- 열려 있던 설계 결정은 하나였다. 자연어 질의의 AI 호출을 동기로 할지 비동기로 할지였다. 기존 AI 서비스는 전부 배치였지만 이 기능은 사용자가 바로 응답을 기대하는 대화형이다. 이 하나만 사용자에게 묻고, 나머지는 묻지 않고 진행했다.
- 화이트리스트 enum과 실패하는 테스트를 먼저 만들어 RED를 확인하고, 구현해서 GREEN을 확인했다.
"이미 있는 패턴을 찾고, 정말 열려 있는 결정만 묻고, 테스트 먼저 쓴다"는 설계 의도가 첫 실행에서 그대로 드러났다.
6. 만들고 나서 남은 것들
- 스킬은 코드가 아니라 절차 문서라서, 실패를 겪을 때마다 한 줄씩 보태는 식으로 키울 수 있다.
- 여러 저장소에서 반복되는 규칙(푸시 전 검증, 훅)은 저장소별 문서가 아니라 전역 규칙으로 올렸다. 저장소마다 처음부터 다시 쓰지 않기 위해서다.
- 같은 날
start-issue,dev-issue,prune-branch가 연달아 만들어졌다. 진단이 끝나자 "이 흐름의 각 조각을 스킬로 만들면 되겠다"는 생각이 이어졌기 때문이다.
마치며
속도를 올리려면 먼저 어디서 새는지 재야 한다. 이번에는 "코딩이 느린 것"이라고 생각했지만 실제로는 로컬에서 돌아가지 않던 테스트 DB 설정 하나, 그리고 이슈마다 반복되던 탐색이 시간을 먹고 있었다. 측정으로 병목을 확인하고, 훅으로 사람의 기억에 의존하던 부분을 없애고, 남은 반복 절차를 skill-creator로 스킬에 고정했다. 이 순서가 이번 작업에서 얻은 가장 큰 교훈이다.
댓글 0개