GitHub Spec Kit으로 스펙 주도 개발(SDD) 처음부터 끝까지 따라 해 보기 본문
GitHub Spec Kit으로 스펙 주도 개발(SDD) 처음부터 끝까지 따라 해 보기
AI 코딩 에이전트에게 "할 일 앱 만들어 줘"라고 하면 일단 뭔가는 나온다. 문제는 무엇을 왜 만들었는지가
어디에도 남지 않는다는 것이다. GitHub Spec Kit은 이 문제를
"코드보다 스펙을 먼저 쓰고, 스펙에서 계획·작업·구현을 끌어낸다"는 방식으로 풀려는 오픈 소스 툴킷이다.
이 글은 Spec Kit의 개념을 정리하고, Claude Code와 함께 작은 할 일 웹앱을 constitution부터 converge까지
한 사이클 완주한 기록이다.
TL;DR
- Spec Kit = 프롬프트 템플릿 + 보조 스크립트 + CLI(
specify) 묶음. AI를 직접 호출하지 않고, 코딩 에이전트에게
"정해진 순서와 형식"을 주입한다. - 흐름:
constitution → specify → (clarify) → plan → tasks → implement → converge - 각 단계는 Markdown 산출물(
spec.md,plan.md,tasks.md…)을 남기고, 다음 단계는 그 파일을 입력으로 쓴다. - 실습 결과: 기능 요구 사항 13개 → 작업 28개 → 테스트 81개, 런타임 의존성 0개의 바닐라 JS 앱.
- 가장 인상적이었던 점: 테스트 79개가 모두 통과한 뒤에도
converge가 스펙과 어긋난 부분 2건을 찾아냈다.
1. 스펙 주도 개발(SDD)이란
전통적으로 스펙은 코드를 짜기 위한 "참고 문서"였고, 코드가 진짜였다. 시간이 지나면 스펙은 낡고 코드만 남는다.
SDD는 이 관계를 뒤집는다.
스펙이 1차 산출물이고, 코드는 스펙에서 생성되는 결과물이다.
Spec Kit이 강조하는 원칙을 요약하면 다음과 같다.
| 원칙 | 의미 |
|---|---|
| 무엇/왜 먼저, 어떻게는 나중 | spec.md에는 기술 스택을 쓰지 않는다. 기술 결정은 plan.md에서 한다 |
| 헌법(constitution) | 프로젝트당 한 번 정하는 불변 원칙. 이후 모든 계획이 이 원칙으로 검사받는다 |
| 모호함은 표시하고 질문한다 | 추측 대신 [NEEDS CLARIFICATION] 표시 → clarify 단계에서 질문으로 해소 |
| 추적 가능성 | 요구 사항(FR) → 설계 결정 → 작업(Task) → 테스트 → 코드가 ID로 연결된다 |
| 수렴(converge) | 구현 후 코드를 다시 스펙과 대조해 빠진 작업을 찾아 추가한다 |
2. Spec Kit의 구조
저장소를 클론해서 뜯어보면 핵심은 세 가지다.
templates/commands/*.md # 각 단계(/speckit-specify 등)의 실제 프롬프트
templates/*-template.md # spec, plan, tasks 같은 산출물의 틀
scripts/{bash,powershell,python}/ # 기능 폴더 생성, 경로 계산 같은 결정적(deterministic) 작업
src/specify_cli/ # 위 자산을 프로젝트에 설치해 주는 CLI
- 명령 템플릿이 곧 제품이다.
specify init은 이 템플릿을 에이전트별 형식으로 변환해 설치한다.
Claude Code라면.claude/skills/speckit-*/SKILL.md스킬이 된다. - LLM에게 맡기면 안 되는 일은 스크립트가 한다. 기능 번호 매기기, 현재 작업 중인 기능 폴더 찾기 등은
스크립트가 JSON으로 돌려주고, 프롬프트는 그 값을 쓴다. - 현재 기능 추적:
.specify/feature.json에{"feature_directory": "specs/001-..."}이 저장되어
다음 단계(plan,tasks)가 어느 폴더를 다룰지 안다. - 확장(extension), 프리셋(preset), 워크플로(workflow), 번들(bundle)로 동작을 추가·변경할 수 있다.
예:specify extension add bug로 버그 수정 흐름(assess → fix → test)을 추가.
3. 환경 준비
필요한 것: Python 3.11+, uv, 코딩 에이전트(여기서는 Claude Code).
brew install uv
uv tool install specify-cli
specify init practice-01 --integration claude --script sh
cd practice-01 && claude
uv: 빠른 파이썬 패키지/도구 관리자.uv tool install은 CLI를 전역 설치한다(pipx와 비슷).specify-cli:specify명령을 제공. 프로젝트에 템플릿·스크립트·스킬을 깔아 주는 역할만 하고 AI는 호출하지 않는다.
init 직후 구조는 이렇다.
practice-01/
├── .claude/skills/speckit-*/SKILL.md # 10개 스킬
└── .specify/
├── memory/constitution.md # 프로젝트 헌법 (빈 템플릿)
├── templates/ # spec/plan/tasks 틀
├── scripts/bash/ # 보조 스크립트
└── workflows/speckit/workflow.yml # 전체 사이클 자동 실행용
4. 실습: 마감일 정렬 할 일 웹앱
4-1. /speckit-constitution — 프로젝트 헌법
/speckit-constitution 단순함 우선, 테스트 필수, 외부 의존성 최소화 원칙으로 만들어줘
세 원칙이 MUST 문장으로 구체화되었다.
- I. 단순함 우선: 스펙에 없는 기능 금지, 사용처가 2곳 이상 생기기 전 추상화 금지, 복잡도는 근거 기록
- II. 테스트 필수 (NON-NEGOTIABLE): 테스트 먼저 → 실패 확인 → 구현, 버그 수정엔 회귀 테스트
- III. 외부 의존성 최소화: 표준 기능으로 안 될 때만 추가, 추가 시
research.md에 대안과 근거 기록
버전(1.0.0), 개정 절차, Sync Impact Report(변경 요약 주석)까지 붙는다.
이 원칙은 장식이 아니라 뒤의 plan 단계에서 게이트로 검사된다.
4-2. /speckit-specify — 무엇을, 왜
/speckit-specify 할 일을 추가/완료/삭제하고 마감일 순으로 정렬해서 보는 웹앱. 데이터는 브라우저에 저장
결과로 specs/001-todo-due-date/spec.md가 생겼다.
- 우선순위가 붙은 사용자 스토리: P1 추가·조회 → P2 마감일 정렬 → P3 완료·삭제.
각 스토리는 단독으로 테스트 가능한 MVP 조각이어야 한다는 규칙이 있다. - 스토리마다 Given/When/Then 인수 시나리오
- 기능 요구 사항 FR-001~010, 측정 가능한 성공 기준(예: "할 일 500개에서 1초 안에 반영")
- 엣지 케이스(빈 제목, 저장 실패, 손상 데이터…)와 가정(Assumptions)
- 스펙 품질 체크리스트(16항목) 자동 검증
주목할 점: spec.md에는 React도 localStorage도 등장하지 않는다. "브라우저에 저장"이라는 요구만 있다.
4-3. /speckit-clarify — 모호함 줄이기
AI가 스펙을 훑어 영향이 큰 모호함을 찾아 한 번에 하나씩, 최대 5개 질문한다.
각 질문에 추천안과 근거, 선택지 표가 함께 나온다.
| # | 질문 | 내 답 |
|---|---|---|
| 1 | 추가한 할 일을 수정할 수 있어야 하나? | 수정 불가 (단순함 우선) |
| 2 | 실수로 삭제하는 걸 어떻게 막나? | 삭제 전 확인 |
| 3 | 완료 항목이 쌓이면? | "완료 항목 모두 삭제" 제공 |
| 4 | 탭 두 개에서 동시에 쓰면? | 다른 탭에 자동 반영 |
| 5 | 키보드만으로 다 쓸 수 있어야 하나? | 예 |
답할 때마다 ## Clarifications 섹션에 Q/A가 기록되고, 해당 FR·시나리오·성공 기준이 즉시 수정된다.
예를 들어 4번 답변으로 FR-012(다른 탭 반영)와 SC-005(2초 안에 반영, 변경 유실 0)가 새로 생겼다.
특히 4번은 추천안 설명이 결정에 큰 도움이 됐다. 원래 스펙의 "마지막 저장이 이김"은
한 탭의 변경이 다른 탭 때문에 조용히 사라지는 데이터 유실이라는 점을 짚어 줬다.
4-4. /speckit-plan — 어떻게
/speckit-plan Vite + 바닐라 JS, localStorage 사용, 테스트는 Vitest
여기서 처음으로 기술이 등장한다. 생성된 산출물:
| 파일 | 내용 |
|---|---|
plan.md |
기술 컨텍스트, Constitution Check 표, 소스 구조 |
research.md |
결정 7개(R1~R7) — 각각 Decision / Rationale / Alternatives considered |
data-model.md |
Todo 엔티티, 검증 규칙, 상태 전이, 정렬 규칙 |
contracts/storage.md |
localStorage 키·JSON 형식, 손상/실패 처리 |
contracts/ui.md |
화면 요소(data-testid), 상호작용, 키보드 규칙 |
quickstart.md |
실행 명령, 테스트↔요구 사항 매핑, 수동 검증 시나리오 |
기억에 남는 설계 결정 몇 가지:
- 런타임 의존성 0개. 개발 도구만
vite,vitest,jsdom. 헌법 III 게이트를 통과. - 삭제 확인 =
window.confirm. 직접 만든<dialog>모달보다 단순하고, Enter/Esc 키보드 처리가 기본 제공된다. - 다른 탭 반영 =
storage이벤트 + read-modify-write. 이벤트만 쓰면 오래된 메모리 상태로 덮어쓸 수 있으므로,
모든 변경은 저장소에서 최신 값을 다시 읽고 적용한 뒤 저장한다. - 날짜는
YYYY-MM-DD문자열 비교. 시간대 때문에 하루 밀리는 버그를 원천 차단.
research.md의 "Alternatives considered"(IndexedDB, Playwright, happy-dom 등을 왜 안 썼는지)는
나중에 "왜 이렇게 만들었지?"에 답해 주는 기록이 된다.
4-5. /speckit-tasks — 실행 가능한 작업 목록
설계 문서에서 작업 26개가 나왔다. 형식이 엄격하다.
- [ ] T013 [P] [US2] Add failing unit tests to `tests/unit/todo.test.js` for `sortTodos(todos)` ...
T013: 실행 순서 ID /[P]: 다른 파일이라 병렬 가능 /[US2]: 어느 스토리 소속인지- 단계: Setup → Foundational(모든 스토리의 공통 기반) → US1 → US2 → US3 → Polish
- 헌법 II 때문에 모든 스토리가 "실패하는 테스트 작성 → 구현" 순서로 짜였다
data-model.md의 제약("1~200자", "YYYY-MM-DD또는null")이 작업 설명에 원문 그대로 들어가
구현 시 임의 해석 여지를 줄인다- 제안 MVP 범위: Phase 1~3(US1까지)
4-6. /speckit-implement — 구현
에이전트가 tasks.md를 위에서부터 처리하며 완료한 작업을 [X]로 체크한다. 매 스토리마다 Red → Green을 거쳤다.
US1 테스트 작성 → 9 failed → 구현 → 48 passed
US2 테스트 작성 → 11 failed → 구현 → 59 passed
US3 테스트 작성 → 12 failed → 구현 → 72 passed
Polish → 3 failed → 구현 → 79 passed, build OK
최종 구조는 단순하다.
src/todo.js # 순수 로직: 생성·검증·정렬·기한 지남·토글·삭제
src/store.js # localStorage 읽기/쓰기, 손상 처리, storage 이벤트 구독
src/app.js # DOM 렌더링과 이벤트
src/main.js # 진입점
재미있었던 장면: Polish 단계에서 "다른 탭 변경 유지" 테스트를 먼저 썼는데 구현 전부터 통과했다.
plan 단계에서 정한 read-modify-write 결정(R5)이 이미 문제를 막고 있었던 것이다.
설계 결정이 코드보다 먼저 효과를 낸 셈이다.
4-7. /speckit-converge — 스펙으로 돌아가 대조하기
테스트 79개가 모두 초록불인데, converge는 코드를 스펙 문장과 대조해 2건을 찾았다.
| 발견 | 심각도 | 내용 |
|---|---|---|
| F1 | MEDIUM | 날짜를 덜 입력하면(2026-10-) 브라우저가 빈 값을 넘겨 조용히 "마감일 없음"으로 저장됨 |
| F2 | LOW | "오늘"을 앱을 열 때 한 번만 계산 → 자정을 넘기면 "기한 지남" 표시가 갱신 안 됨 |
테스트가 못 잡은 이유도 명확했다. jsdom은 브라우저의 "불완전 날짜 → 빈 값" 동작을 재현하지 않고,
테스트는 날짜를 고정해서 돌렸다. converge는 tasks.md 맨 아래에 Phase 7: Convergence로
T027, T028을 추가만 했다(기존 작업이나 코드는 건드리지 않는다).
다시 implement(81개 통과) → converge 결과:
✅ Converged — the implementation satisfies the spec, plan, and tasks.
5. 전체 흐름 한눈에 보기
constitution ─ 원칙 3개 (게이트로 사용)
│
specify ───── spec.md: 스토리 3, FR 10, SC 4 (기술 언급 없음)
│
clarify ───── 질문 5개 → FR 13, SC 5로 확장
│
plan ──────── plan/research/data-model/contracts/quickstart (기술 결정 + 근거)
│
tasks ─────── 26개 작업, 스토리별 단계, 테스트 우선
│
implement ─── 25/26 완료, 테스트 79개
│
converge ──── 테스트가 놓친 2건 발견 → 작업 추가
│
implement ─── 테스트 81개
│
converge ──── ✅ Converged
추적 예시 하나: Clarify Q4 "다른 탭 자동 반영" → FR-012 / SC-005 → research R5(read-modify-write) →contracts/storage.md "다른 탭 동기화" → T021(테스트)·T022(구현) → store.update(), store.subscribe().
어떤 코드 줄이 왜 존재하는지 문서만 따라가면 답이 나온다.
6. 느낀 점
좋았던 점
- 결정이 기록으로 남는다. 질문과 답, 기각한 대안까지 파일로 남아 나중에 맥락을 잃지 않는다.
- 헌법이 실제로 작동한다. "외부 의존성 최소화" 한 줄이 jsdom 하나를 추가할 때도 근거를 쓰게 만들었다.
- clarify의 추천안이 학습 도구다. 선택지마다 영향(데이터 유실, 테스트 범위 등)을 설명해 줘서
요구 사항을 어떤 관점으로 봐야 하는지 배우게 된다. - converge가 마지막 안전장치다. "테스트 통과 = 완료"라는 착각을 깨 준다.
주의할 점
- 작은 앱에도 문서가 꽤 많이 생긴다. 프로토타입이나 일회성 스크립트에는 과할 수 있다.
- 산출물 품질은 결국 리뷰에 달려 있다. 단계마다 생성물을 읽고 확인한 뒤 다음으로 넘어가야 한다.
- 자동 테스트(jsdom)는 실제 브라우저를 완전히 대체하지 못한다.
quickstart.md의 수동 시나리오
(특히 두 탭 동기화, 키보드 전용 조작)는 직접 브라우저에서 확인해야 한다. - 에이전트의 보고도 검증이 필요하다. 실습 중 검증 로그에 테스트 개수가 잘못 적혔다가 실제 실행 결과로 바로잡은 일이 있었다.
7. 다음에 해 볼 것
- 같은 프로젝트에 두 번째 기능 추가:
/speckit-specify 할 일 수정 기능→specs/002-*로 사이클 반복 - 선택 단계 체험:
/speckit-analyze(산출물 간 일관성 점검),/speckit-checklist(요구 사항 품질 체크리스트) - 확장:
specify extension add bug로 버그 수정 흐름(assess → fix → test)
참고
- GitHub Spec Kit: https://github.com/github/spec-kit
- Spec Kit 문서: https://github.github.io/spec-kit/
- 방법론 원문: 저장소의
spec-driven.md
