aster®

Next.js 개발 에이전트 하네스 curvez 와 자율 머지를 위한 리팩터링

2026-09 · 개발 도구

github.com

curvez 는 에이전트 셋을 동시에 띄워도 서로의 파일을 덮어쓰지 않게 만든 Claude Code 하네스다. 그런데 이 하네스는 단계마다 사람을 기다렸다. 팀을 짜려면 승인을 받아야 했고, 커밋과 머지는 사용자가 요청할 때만 돌았다.

사람이 자리를 비워도 머지까지 가게 하려면 무엇이 먼저 있어야 할까?

pstack 을 참고해 다섯 단계로 나눠 고쳤다. 마지막 단계에서는 샌드박스에서 실제로 머지된 PR 하나가 설계에 빠진 곳을 드러냈다.

1. 아키텍처를 먼저 정하고 에이전트를 띄우는 하네스

아키텍처와 담당 범위를 정한 뒤 에이전트를 병렬로 실행한다. 저장소 루트가 플러그인 마켓플레이스다.

실행 순서를 강제했다. 구현 에이전트는 .curvez/architecture.md 의 ## 금지 import 표를 위반 판정의 유일한 근거로 쓴다. 그 파일이 없으면 blocked 로 멈추고 아무것도 만들지 않는다.

에이전트끼리는 대화를 공유하지 못한다. 그래서 .curvez/handoff/*.json 파일로 결과를 넘긴다. 핸드오프는 한 에이전트가 끝낸 일을 다음 에이전트에게 넘기는 인계 파일이다. status·decisions·verification 같은 필드가 미리 정해져 있어 받은 쪽이 grep 으로 증거를 찾을 수 있다.

이 사이트를 포함해 앱 셋이 이 하네스 위에서 돌아간다. 병렬 런타임은 tmux 가 아니라 Claude Code 내장 서브에이전트다.

2. 사람 없이 돌리려면 판단을 믿을 수 있어야 한다

머지를 에이전트에게 맡기려면 두 가지가 먼저 있어야 했다. 에이전트가 내린 판단을 믿을 수 있어야 하고, 머지 전에 결과를 독립적으로 확인할 수 있어야 한다.

리팩터링 전에 main 을 읽고 세어 보니 둘 다 없었다.

pstack 은 이 문제를 먼저 다룬 하네스다. 원칙 22개를 파일 하나씩 두고, 작업 종류마다 플레이북을 두고, 빈 칸이 있으면 워커를 띄우지 않는다. pstack 은 Cursor 전용 기능에 기대는 부분이 있어서, 그 자리는 Claude Code 에 맞게 바꿨다.

단계바꾼 것PR
P1여러 파일에 복사된 규칙을 원칙 파일 12개로 분리#37
P2워커 지시서를 칸 8개로 고정하고 빈 칸이면 띄우지 않음#38
P3다른 모델(codex)이 diff 를 따로 보는 교차 리뷰어#39, #40
P4작업 종류별 플레이북을 고르는 입구 스킬 curvez-mode#41
P5사용자가 준 자율 권한 안에서 팀 구성과 머지를 스스로 진행#42, #43

표 1. 리팩터링 다섯 단계

단계마다 PR 하나씩 냈고, 검증된 상태로 끝나야 다음 단계로 갔다.

3. 복사된 규칙을 원칙 파일 12개로 모으기

plugins/curvez/principles/ 에 원칙 하나당 파일 하나를 두고, README.md 에 인덱스를 둔다. 인덱스의 한 줄은 "언제 적용하는가"와 규칙 문장이다. 판정 질문과 패턴은 원칙 파일에만 있다.

- **추측하지 않고 blocked 로 돌아온다** ([blocked-over-guessing](blocked-over-guessing.md))
  — 언제: 필요한 입력·결정·파일이 없거나 해석이 둘 이상일 때. …

처음 설계는 복사된 문장을 지우고 원칙을 가리키는 줄로 바꾸는 것이었다. 실제로 찾아보니 "어느 쪽이 맞는지 판정할 근거가 사라진다" 같은 문장은 대부분 그 파일에서만 쓰는 규칙의 이유였다. 지우면 그 규칙의 이유가 없어지고, 이유 없는 규칙을 잡는 검사에도 걸린다.

그래서 원칙 파일에는 일반 규칙을 두고, 그 규칙이 처음 적혀 있던 자리에 (원칙: principles/<slug>.md) 를 붙였다. 파일마다 다른 이유 문장은 그대로 뒀다.

두 벌이 다시 어긋나지 않게 validate-principles.mjs 가 검사한다. 인덱스와 파일이 1:1 로 맞는지, 인덱스 문장이 원칙 파일의 첫 규칙 문장과 같은지, 원칙마다 밖에서 가리키는 곳이 한 곳 이상 있는지를 본다. 일부러 깨뜨린 사본 다섯 종에서 index-rule-drift·label-missing·not-indexed 같은 오류가 각각 잡혔다.

4. 빈 칸이 있는 지시서로는 워커가 뜨지 않게 하기

오케스트레이터가 워커에게 넘기는 지시서를 칸 8개로 고정했다.

GOAL        한 문장. 대화를 모르는 사람도 실행할 수 있게
SCOPE       쓸 수 있는 경로 / 쓸 수 없는 경로
CONTEXT     입력 파일 경로. 앞 단계 핸드오프에 기대면 그 경로
ACCEPTANCE  판정 가능한 기준, 한 줄에 하나
VERIFY      실행할 명령 (profile.json 의 commands 에서)
FORBIDDEN   소유 경로 밖 쓰기, 강제 push, 의존성 추가, 이 작업만의 금지
REPORT      .curvez/handoff/<name>.<ts>.json, status 규칙
STANDING    .curvez/standing.md 경로 (고정 지시문)

글로만 적어 두면 다시 빠진다. hooks/check-brief.mjs 가 curvez: 로 시작하는 서브에이전트 호출을 가로채 칸이 빠지거나 비었으면 exit 2 로 막는다. GOAL·SCOPE·REPORT 가 아닌 칸은 없음: <이유> 를 허용한다.

실제 Claude Code 세션에서 curvez:curvez-researcher 를 자유 서술로 불렀더니 훅이 막았다. 띄운 서브에이전트는 0개였고, 모델은 칸 8개가 빠졌다는 메시지를 받았다. 회귀 테스트는 20건이다.

5. 구현자와 다른 모델에게 diff 를 따로 보이기

새 에이전트 curvez-cross-reviewer 는 스스로 판정하지 않는다. diff 와 루브릭을 파일로 만들어 codex exec -s read-only 로 검토를 맡기고, 그 지적을 findings JSON 으로 옮겨 반환한다. profile.json 의 crossReview 로 켠다.

원래 curvez 는 외부 CLI 없이 내장 서브에이전트만 쓰기로 정해 두었다. 그 결정은 유지했다. 워커는 계속 내장 서브에이전트이고, codex 는 읽기 전용 리뷰어 한 명이 Bash 로 부르는 도구로만 쓴다.

합치는 규칙은 설계와 다르게 갔다. 설계는 "한쪽만 낸 지적은 한 단계 내린다"였다. 실제로는 교차 리뷰어만 낸 지적만 내리고, 기존 curvez-reviewer 만 낸 지적은 그대로 둔다. 새 리뷰어가 못 봤다는 이유로 기존 판정을 약하게 만들면 교차 검토를 넣기 전보다 기준이 느슨해진다.

버그를 넣은 연습 저장소(div 의 / 를 * 로 바꿈)에서 돌리니 115초, $0.168 에 codex 가 m.js:1 major 지적 1건을 냈다. 반환 JSON 은 핸드오프 검사를 통과했다.

훅에만 있고 Bash 에는 없던 플러그인 경로

이 단계에서 버그가 하나 나왔다. CLAUDE_PLUGIN_ROOT 는 훅 실행 환경에만 있고 에이전트의 Bash 명령에는 전달되지 않는다. 메인 세션과 서브에이전트 모두 빈 값이라 $CLAUDE_PLUGIN_ROOT/scripts/... 가 /scripts/... 로 접혀 실패했다.

SessionStart 훅이 CLAUDE_ENV_FILE 에 export CURVEZ_ROOT 를 쓰게 하고, 에이전트·스킬·문서의 $CLAUDE_PLUGIN_ROOT 50곳을 $CURVEZ_ROOT 로 옮겼다(#40).

6. 버그 수정과 기능 추가를 다른 순서로 돌리기

입구 스킬 curvez-mode 는 요청을 플레이북 하나에 맞추고, 그 단계를 원문 그대로 체크리스트로 싣고 순서대로 실행한다. 단계에 curvez-* 이름이 있으면 메인 세션이 직접 하지 않고 오케스트레이터에게 지시서로 넘긴다.

플레이북단계 요약
feature요구사항과 수용 기준 → 설계 → 구현 → 품질 게이트 → 리뷰와 교차 검토 → shipping
bug-fix재현하는 실패 테스트를 먼저 커밋 → 원인 추적 → 수정 → 실패에서 통과로 바뀐 출력 첨부 → 리뷰 → shipping
refactoring동작을 고정하는 테스트 → 목표 구조 → 작게 옮기기 → 동등성 확인 → 구조 리뷰 → shipping
investigation읽기 전용. 조사 결과를 출처와 함께 보고한다
autonomous-rungoal 과 완료 조건을 받아 위 플레이북을 이어 돌린다
shipping머지 전에 독립 판정을 하고, 조건을 모두 채운 것만 머지한다

표 2. 플레이북 6개

실제 세션으로 네 번 돌리며 스킬을 고쳤다. 첫 실행에서는 체크리스트가 안 나왔고, curvez-qa 몫의 테스트 파일을 메인 세션이 직접 쓰려 했다. claude -p 세션에는 할 일 목록 도구(TodoWrite)가 없었다. 그래서 첫 응답에 체크리스트를 싣도록 스킬 맨 앞에 ## 첫 응답의 형식 절을 올렸다. 네 번째에 단계 7개가 원문 그대로 체크리스트로 나왔다.

7. 사용자가 준 권한 안에서만 스스로 머지하기

사용자가 .curvez/grant.md 에 머지 대상 브랜치, 권한이 끝나는 조건, 사용자 원문을 적어 두면 오케스트레이터는 팀 구성 승인 없이 워커를 띄우고 끝에 한 번 보고한다. shipping 조건을 채우면 PR 을 만들고 머지한다.

권한이 유효한지는 모델이 아니라 scripts/check-grant.mjs 가 판정한다. 권한이 있어도 멈추는 행동이 있다. 강제 push 와 원격 브랜치 삭제는 기존 guard-bash 훅이 막고, 의존성·curvez 자체·배포 워크플로 변경은 check-grant 가 막는다.

권한만 보고 머지가 열렸다

샌드박스 저장소에서 종단 실행을 했더니 PR #1 이 사람 개입 없이 머지됐다. 376초, $1.729 였다. 그런데 실패 테스트, 품질 게이트, 리뷰 라운드, shipping 조건 확인이 모두 빠져 있었다. 띄운 에이전트는 orchestrator·nextjs·git 셋뿐이었다.

원인은 셋이었다.

  1. 메인 세션이 지시서에서 bug-fix 를 "고치고 테스트", shipping 을 "머지"로 줄였다.
  2. 오케스트레이터가 "단일 파일·단일 관심사라 워커 하나"로 판정했다.
  3. check-grant 는 권한만 봤다.

1과 2는 문서 규칙이라 다시 어길 수 있다. 그래서 3을 코드로 막았다(#43). check-grant 를 --stage team|merge 로 나누고, merge 단계에서는 분기점 이후의 핸드오프에서 증거 셋을 요구한다.

머지됐던 샌드박스 브랜치를 다시 판정하니 이렇게 나왔다.

DENY 머지 증거가 부족하다: curvez-reviewer 핸드오프 없음; curvez-qa 핸드오프 없음;
curvez-cross-reviewer 핸드오프 없음 (crossReview 설정됨)

회귀 테스트는 15건에서 28건으로 늘었다. 종단 실행의 "핸드오프 없음" 상황을 재현한 케이스가 들어 있다.

8. 리팩터링 전후의 구성과 검증 수치

항목전후
에이전트1213 (curvez-cross-reviewer)
스킬1617 (curvez-mode) + 플레이북 6
훅56 (check-brief)
원칙 파일012
스크립트 회귀 테스트—check-brief 20건, check-grant 28건

표 3. 리팩터링 전후 구성

마지막 PR 기준 검증 수치다. validate-agents 13/13, validate-skills 23/23(스킬 17 + 플레이북 6), validate-principles 13/13 이다. doctor --plugin 은 정적 검사 8종 OK·런타임 8/8, 훅 테스트는 20/20·35/35·17/17 이다.

트레이드오프 — 문서 규칙은 여전히 어길 수 있다

코드로 막은 것은 지시서의 빈 칸과 머지 증거뿐이다. 메인 세션이 플레이북을 줄여 넘기는 일, 오케스트레이터가 리뷰어를 빼는 일은 여전히 문서 규칙이다. #43 은 그 둘이 무너져도 머지가 열리지 않게 한 것이지, 둘을 막은 것이 아니다.

증거 판정은 핸드오프가 있는지와 그 내용만 본다. 핸드오프가 실제 실행을 반영하는지는 각 에이전트의 자체 검증에 기댄다.

교차 리뷰를 켜면 codex CLI 가 있어야 하고 호출마다 비용과 시간이 든다. 연습 저장소 한 번에 115초였다.

확인하지 못한 것 — 재실행과 원칙 읽기

9. 그래서 사람이 자리를 비워도 머지까지 가려면

사람이 자리를 비워도 머지까지 가려면, 에이전트의 판단을 믿을 수 있게 하는 장치와 머지 전에 결과를 따로 확인하는 장치가 먼저 있어야 했다. 원칙 파일과 지시서가 앞쪽을, 교차 리뷰와 머지 증거 검사가 뒤쪽을 맡는다. 문서 규칙만 있을 때 샌드박스 PR 은 리뷰 없이 머지됐다. 같은 브랜치를 다시 판정했을 때 DENY 를 낸 것은 check-grant 가 요구하는 핸드오프 세 건이었다.