
【후편】 AI에게 '설계대로' 구현하게 하기 ― 사양 주도 개발(SDD) 하네스를 Claude Code로 구축하다
요약
Claude Code를 활용하여 AI가 사양을 준수하도록 강제하는 사양 주도 개발(SDD) 하네스 구축 방법을 소개합니다. 프롬프트 대신 결정론적인 워크플로우와 인간의 승인 단계를 통해 AI 코딩의 품질을 보장하는 기법을 다룹니다.
핵심 포인트
- 프롬프트가 아닌 결정론적 가드(Harness)를 통한 AI 제어
- EARS 형식의 요건 정의와 Gherkin 기반 수락 기준 활용
- 인간의 승인(Approved) 단계가 포함된 5단계 SDD 워크플로우
- Claude Code의 훅, 서브 에이전트, 스킬을 이용한 감독 계층 구축
전편에서 소개한 아이마스 진단 도구
iMAS Chara Match는 단순히 AI에게 구현하게 한 것이 아니라, "AI가 사양을 무시하고 멋대로 만들거나 / 테스트를 작성하지 않거나 / 리뷰를 통과해 버리는 것"을 구조적으로 방지하는 하네스(Harness)를 구축한 상태에서 만들었습니다. 후편에서는 그 개발 기법 ―― 사양 주도 개발(SDD, Specification-Driven Development)과, 이를 Claude Code의 훅(Hook)으로 강제하는 장치 ―― 에 대해 해설합니다.
- AI 코딩의 "멋대로 만드는" 문제를, 프롬프트가 아닌 결정론(Determinism)으로 억제하는 발상을 배웁니다.
- SDD의 5단계 워크플로우와 인간의 승인 게이트를 이해합니다.
- Claude Code의 **훅(Hook), 서브 에이전트(Sub-agent), 스킬(Skill), 프로파일(Profile)**로 감독 계층을 어떻게 구축했는지 알아봅니다.
대상 독자는, AI 에이전트에게 규모가 큰 구현을 맡기고 싶지만 "품질을 담보할 수 있을지" 불안한 사람입니다.
AI 에이전트에게 구현을 통째로 맡기면 다음과 같은 일이 발생하기 쉽습니다.
이러한 문제들은 산문 형태의 지시("~해 주세요", "~는 금지입니다")만으로는 방지할 수 없습니다. 자연어 프롬프트는 확률적으로만 작용하기 때문입니다. README에 "domain은 Web에 의존하지 않을 것"이라고 적어두어도, AI가 실수로 import를 해버리면 그대로 통과되어 버립니다.
그래서 발상을 전환합니다.
지키게 하고 싶은 것은 부탁(프롬프트)이 아니라, 벽(결정론적인 가드)으로 만드는 것입니다.
이를 실현하는 것이 **SDD(사양 주도 개발)**와, 이를 AI에게 강제하는 **하네스(Harness)**입니다.
개발은 다음 5단계를 순차적으로 진행합니다. 각 단계는 Claude Code의 스킬( /discover 와 같은 슬래시 커맨드)로 구동되며, AI가 결과물을 초안 작성하고, 인간이 승인할 때까지 다음 단계로 넘어가지 않습니다.
각 단계의 결과물은 서두에 Status: Draft를 가지며, 인간이 리뷰하여 Status: Approved로 바꿀 때까지 동결됩니다. "코드 이전에 사양을 확정하고, 승인된 사양에 코드를 닻 내리듯 고정하는(spec-anchored)" 진행 방식입니다.
- 요건은 EARS 형식으로 작성합니다 (예:
WHEN <trigger>, the <system> SHALL <response>). EARS는 "규칙의 대장"입니다. - **수락 기준(Acceptance Criteria)은 Gherkin (Given-When-Then)**으로 작성합니다. EARS 요건 1건에 대해 여러 시나리오가 연결됩니다 (정상계, 이상계, 경계값).
- Gherkin의 1차 저작은 specify 단계에서 단 한 번만 이루어집니다. 하류 단계(tasks/implement)는 참조 및 변환만 할 뿐 새로 생성하지 않습니다. 이를 통해 "사양이 구현될 때마다 흔들리는 것"을 방지합니다.
# acceptance.feature 예시 (이미지)
Rule: 추천에 적합하지 않은 선호도 입력은, 추천을 실행하지 않고 이유를 알 수 있는 형태로 거부된다
Scenario: 빈 문자열은 거부된다
...
각 단계는 **역할별 서브 에이전트(Sub-agent)**에게 위임하여 진행합니다. 진행 역할은 스스로 결과물을 쓰지 않고, Claude Code의 Agent 도구를 사용하여 실제로 별도의 컨텍스트를 가진 에이전트를 기동합니다.
- discover는 3명의 분석가(product / quality / solution)를 병렬 기동하여 관점을 도출하고, 진행 역할이 이를 통합합니다.
- specify는 scenario-author가 EARS + Gherkin으로 정제합니다.
- plan / tasks는 solution-analyst가 설계 및 분해를 수행합니다.
- implement는 engineer가 TDD로 구현합니다. 구현 후에는 리뷰 역할을 독립된 컨텍스트에서 실행합니다. 구현자의 선입견이 개입되지 않으므로, 리뷰가 "자작극"이 되는 것을 방지합니다.
여기가 핵심입니다. "절대로 일어나서는 안 되는 일"은 README의 문장으로는 강제할 수 없습니다. 진짜 가드레일을 **Claude Code의 훅(PreToolUse)**에 배치했습니다.
| 훅 (Hook) | 차단 대상 |
|---|---|
guard_architecture.py | Clean Architecture 위반 (domain/application으로의 FW import, 직접적인 SQL 사용) |
guard_sdd_gates.py | SDD 게이트 위반 (이전 단계가 승인되지 않았는데 다음 단계를 작성함 / 승인된 문서를 수정함) |
guard_no_verify.py | pre-commit 우회 (--no-verify, -n, SKIP=, core.hooksPath 교체 등) |
guard_harness.py | 인간 게이트 (ask). specs에 Status: Approved 쓰기 (AI의 자기 승인 방지) 및 강제 계층 자체에 대한 변경 시 인간의 승인 요구 |
예를 들어, AI가 domain/ 계층에서 실수로 from fastapi import ...라고 쓰려고 하면, 편집이 그 자리에서 거부됩니다. "domain은 무엇에도 의존하지 않는다"라는 불변 조건(Invariant)이 단순한 바람이 아니라 실행 시점의 벽이 되는 것입니다.
실제로 가드(Guard)를 발동시키면 다음과 같이 차단됩니다 (실제 guard_architecture의 출력).
편집은 실행되지 않으며, AI가 몇 번을 시도해도 domain 계층의 순수성은 깨지지 않습니다.
Status: Approved가 된 사양은 편집 불가능 상태가 됩니다. 변경하고 싶다면 다음의 경로를 통해서만 가능합니다.
이를 통해 "승인된 사양을 몰래 수정하여 구현을 통과시키는 행위"를 할 수 없게 됩니다.
철저히 관리되는 점은, 가드 자체에도 검증이 있다는 점입니다.
- 가드를 수정하면 →
test_guards.py(79개의 셀프 테스트)가 pre-commit/CI에서 실행되며, 테스트와 불일치하는 수정은 커밋할 수 없음 - 프로파일을 수정하면 →
validate.py(계약 적합성 체크)가 실행됨 - 구현을 수정하면 → 편집 시 가드 + 품질 게이트 (lint, 타입, 아키텍처 검증, 테스트) 작동
- 우회하려고 하면 →
guard_no_verify가--no-verify등의 우회 시도 자체를 차단
즉, 검증의 연쇄는 "구현 ← 가드 ← 가드의 테스트"까지 거슬러 올라가며 닫혀 있으며, 어떤 계층의 수정에도 "그것을 검사하는 계층"이 존재합니다. 유일한 예외는 스킬 에이전트(Skill Agent)의 산문(Prose)이며, 그 부분은 인간의 리뷰가 최후의 보루입니다.
방법론(SDD 워크플로우, 불변 조건)은 **스택 독립적(Stack-agnostic)**으로 유지하고, 기술 고유의 사항(계층 → 실제 디렉토리 대응, 금지된 import, 테스트 명령어, 구현자 에이전트)은 .claude/profiles/<id>/에 격리했습니다.
이 하네스(Harness)는 변경 빈도에 따라 4개로 구분되어 있습니다.
이번 프로파일은 python-fastapi (FastAPI + React)입니다. 다른 스택으로 만들고 싶다면, 프로파일 한 세트만 작성하면 동일한 SDD 하네스를 그대로 사용할 수 있습니다. profile.yml이 "단일 진실 공급원(Single Source of Truth)"이며, 가드 훅도 이 guard: 섹션을 직접 읽습니다 (설정의 이중화로 인한 드리프트(Drift)를 방지하기 위함).
Git 히스토리를 따라가 보면, SDD 풀 사이클을 2개의 기능 단위로 깔끔하게 완주한 것을 볼 수 있습니다.
Discover 단계에서 Example Map을 사용하여 가치, 규칙, 구체적인 예시, 의문점을 도출했습니다. 여기서 해결한 "의문"의 예:
- 대상은 몇 명인가? → 자료에 14명/15명으로 불일치하던 것을 실제 데이터로 확인하여 14명으로 확정
- 상한 초과 시 절삭(Truncate)인가 거부(Reject)인가? → 거부 (몰래 절삭하면 사용자가 이유를 알 수 없음)
- 저유사도 임계값을 설정할 것인가? → 설정하지 않음 (원시 값의 절대적 해석이 어렵고 근거 있는 임계값을 지금 결정할 수 없음)
Specify 단계에서 EARS 요구사항과 38개의 Gherkin 시나리오로 정리했습니다. Implement 단계의 TDD는 Git 히스토리가 그대로 내부 계층 → 외부 계층 순서로 나타납니다.
feat(domain): 선호 추천 도메인 모델 구현 (T04~T10)
feat(application): 포트와 유스케이스 구현 (T11, T13)
feat(adapters): API 계약과 에러 매핑 구현 (T14~T16)
...
0001의 추천을 사람이 다룰 수 있는 형태로 만드는 얇은 클라이언트입니다. React 19 + Vite + Atomic Design을 사용하여 27개의 Gherkin 시나리오를 정의했습니다.
- 비즈니스 규칙(길이 상한·정규화·정렬·유사도 범위)은 **backend/domain이 유일한 진리(Single Source of Truth)**입니다. UI에 재구현하지 않습니다. 상태의 3가지 표시(전송 중/목록/에러)와 에러의 일본어 구분(입력 기인 422/서버 사정 503·504·500/통신 단절)
- 이중 전송·stale 응답 방지
UI의 상태 전이는 다음과 같이 설계했습니다.
서버가 응답할 수 없을 때는 화면을 백지로 만들지 않고, 일본어 에러 메시지를 출력하여 "다음에 무엇을 해야 할지"를 전달합니다.
"코드 작성 전에 사양을 확정하는" 규율이 AI의 폭주를 근본적으로 억제합니다. discover 단계에서 "의문점"을 명시적으로 해소하는 것이 특히 효과적이었습니다.
강제성은 산문(Prose)이 아니라 훅(Hook)에 배치합니다. domain의 FW(Framework) 의존성을 "바라는" 것이 아니라, 편집 시점에 "차단"합니다. 몇 번을 시도해도 불변 조건(Invariant)이 깨지지 않습니다.
리뷰를 독립된 컨텍스트의 서브 에이전트(Sub-agent)에게 맡기면, 구현자의 선입견이 개입되지 않습니다.
테스트 더블(Test Double)로 무거운 의존성을 격리하면, CI(Continuous Integration)가 가벼워지고 수락 기준(Acceptance Criteria)을 결정론적으로 작성할 수 있습니다.
"AI에게 맡기는" 질은, 맡기기 전에 얼마나 많은 메커니즘을 만들 수 있느냐에 따라 결정됩니다.
프롬프트로 부탁하는 것이 아니라, 지키게 하고 싶은 사항을 훅(Hook)과 게이트(Gate)로 녹여냅니다. SDD는 이를 위한 좋은 골격이었습니다.
전편:
무엇을·어떤 메커니즘으로 만들었는가 (임베딩 × 코사인 유사도, Clean Architecture)
- 후편 (본 기사):
AI가 설계대로 구현하게 하는 하네스(Harness) (SDD 5단계, 4가지 가드, 프로파일)
이 하네스는 범용 템플릿인 sdd-harness를 베이스로 합니다. 동일한 골격을 다른 스택이나 다른 프로덕트에도 가져올 수 있도록 설계했습니다.
👉 전편 읽기 【전편】자연어의 "취향"으로부터 아이마스 캐릭터를 추천하는 도구를 만들었다
by NeoSoleil
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기