AI를 활용한 명세 기반 개발(Spec-driven development)은 이제 현실입니다. 하지만 아무도 해결하지 못한 문제는 바로 '낡은
요약
명세 기반 개발(SDD)이 확산되고 있으나, 코드 변경 시 명세가 실제 구현과 일치하지 않는 '명세 드리프트(Spec drift)' 문제가 발생합니다. 이를 해결하기 위해 명세를 단순 문서가 아닌 테스트와 연결된 수락 기준(Acceptance criteria)으로 관리해야 함을 강조합니다.
핵심 포인트
- 명세 기반 개발(SDD)의 확산과 GitHub Spec Kit, AWS Kiro 등의 등장
- 코드 수정 후 명세가 업데이트되지 않아 발생하는 '명세 드리프트' 문제
- 명세가 환각을 일으켜 에이전트가 잘못된 설계를 바탕으로 구축할 위험
- 해결책으로 명세와 테스트를 연결하여 드리프트를 가시화하는 전략 제시
명세 기반 개발(Spec-driven development, SDD)이 승기를 잡았습니다. 1년 전만 해도 에이전트(Agent)가 코드에 손을 대기 전에 명세(Spec)를 작성하라는 말은 그저 절차를 위한 절차처럼 들렸습니다. 하지만 이제 GitHub의 Spec Kit은 수만 개의 스타를 기록하고 있고, AWS는 Kiro를 비슷한 개념으로 출시했으며, Claude Code, Cursor 및 대부분의 다른 도구들도 '명세 우선 작성' 방식의 변형을 내장하고 있습니다. 이 방법론은 정착되었습니다. 하지만 거의 아무도 이야기하지 않는 것은 3주 후에 나타나는 실패입니다. 당신이 정성스럽게 작성한 명세가 당신이 배포한 결과물과 더 이상 일치하지 않게 되고, 당신의 에이전트가 거짓 정보를 담은 문서를 바탕으로 자신 있게 무언가를 구축하고 있을 때의 문제입니다.
저는 규제가 엄격한 금융권에서 수년간 자동화 작업을 해온 경험을 바탕으로 이 흐름에 합류했습니다. 그곳에서는 "그냥 프롬프트(Prompt)를 입력하고 결과를 보자"라는 방식이 리스크 관리 팀을 통과할 수 없었기 때문입니다. 그래서 SDD 도구들이 등장했을 때 저는 쉽게 설득되었습니다. 그 제안은 깔끔합니다. 명세는 진실의 원천(Source of truth)이고, 코드는 재생 가능한 출력물이며, 에이전트는 채팅창에 붙여넣은 문단이 아니라 의도(Intent)를 바탕으로 구축합니다. Spec Kit은 심지어 이를 '명세(Spec) -> 계획(Plan) -> 작업(Tasks) -> 구현(Implement)'이라는 흐름으로 공식화하며, 변경되어서는 안 되는 원칙을 담은 '헌법(Constitution)' 파일에 고정합니다. Kiro는 코드를 한 줄 쓰기 전에 요구사항, 설계, 작업을 거치도록 안내합니다. 두 도구 모두 훌륭합니다. 저는 이들을 비난하려는 것이 아닙니다.
문제는 여기서부터 시작되었습니다.
명세는 '시점 0(Time zero)'에서는 탁월합니다. 명세를 작성하면 에이전트가 이를 바탕으로 구축하며, 첫 번째 결과물은 진정으로 바이브 코딩(Vibe coding)보다 낫습니다. 그다음 배포를 합니다. 지원 팀으로부터 엣지 케이스(Edge case)가 접수되고, 누군가 급한 불을 끄기 위해 코드를 직접 패치하면, 명세는 이제 조용히 틀린 상태가 됩니다. 아무도 명세를 업데이트하지 않습니다. 별도의 문서를 업데이트하는 것은 어떤 스프린트 보드(Sprint board)에서도 추적되지 않는, 보상 없는 작업이기 때문입니다. 두 번의 스프린트가 지난 후, 기존 기능 옆에 새로운 기능이 추가됩니다. 에이전트는 방향을 잡기 위해 오래된 명세를 읽고, 한 달 전에 이미 사라진 시스템에 대한 설명을 바탕으로 그 위에 기능을 쌓아 올립니다. 모델이 환각(Hallucination)을 일으킨 것이 아닙니다. 명세가 환각을 일으킨 것입니다.
해결책은 더 나은 명세 형식이 아닙니다
저도 처음에는 그 방법을 시도했습니다. 더 나은 템플릿, 더 깔끔한 폴더 구조, 더 긴 헌장(Constitution) 같은 것들 말이죠. 하지만 그 효과는 일주일 정도뿐이었습니다. 실제 해결책은 테스트 실패가 눈에 보이는 것과 똑같은 방식으로 명세 드리프트(Spec drift)를 가시화하는 것입니다. 명세는 그것이 참임을 증명하는 대상, 즉 테스트와 연결되어 있을 때만 제 역할을 다하며, 그 연결이 끊어졌을 때 사람들이 이미 확인하고 있는 곳에 신호가 나타나야 합니다. 이는 에이전트(Agent)와 테스트 러너(Test runner)가 모두 읽을 수 있는 수락 기준 (Acceptance criteria)을 작성하는 것에서 시작됩니다. 컨디션이 좋은 날에만 인간이 해석할 수 있는 산문(Prose)이 아니라 말입니다.
# checkout.feature (위키가 아닌 코드 옆에 위치함)
@story:PLAN-412 @verifies:checkout_guest.spec.ts
Feature: Guest checkout
...
이 파일은 명세인 동시에 테스트 계약(Test contract)입니다. @verifies 태그는 시나리오를 증명하는 테스트를 가리키고, @story 태그는 해당 작업을 요청한 작업(Work)을 다시 가리킵니다. 이제 모든 .feature 파일을 검색하여 @verifies 대상을 찾고, 테스트가 누락되었거나 실패(Red)했을 때 빌드를 실패시키는 CI 단계를 추가할 수 있습니다. 이렇게 하면 드리프트(Drift)는 리뷰어가 알아차려 주길 바라는 대상이 아닙니다. 엔지니어가 무시할 수 없는 유일한 신호인 '깨진 빌드(Broken build)'로 변하게 됩니다.
이 모든 것은 오늘 당장 여러분의 저장소(Repo)에 있는 파일들과 몇 줄의 CI 설정만으로 수행할 수 있습니다. 별도의 벤더(Vendor)는 필요하지 않습니다. 만약 이 글에서 단 한 가지만 얻어 가신다면, 이것을 가져가십시오: 명세를 테스트에 연결하고, 그 연결이 끊어지면 요란하게 실패를 알리도록 만드십시오.
하나의 저장소만으로는 충분하지 않은 지점
저장소 로컬(repo-local) 버전은 나머지 전달(delivery) 과정이 개입되기 전까지만 작동하며, 반드시 개입하게 되어 있습니다. 스토리는 Jira나 Linear에 존재합니다. 해당 기능을 위험하게 만든 아키텍처 결정(architecture decision)은 킥오프(kickoff) 이후 아무도 열어보지 않은 다이어그램 속에 있습니다. 산문 형태의 명세(prose spec)는 Notion에 있습니다. 테스트는 CI(지속적 통합)에 있습니다. 각 도구는 자신의 영역에서 제 역할을 다합니다. Notion은 서사적 명세(narrative spec)를 작성하고 팀의 실제 합의를 끌어내기에 쾌적한 장소입니다. Linear는 제가 사용해 본 것 중 가장 깔끔한 순수 트래커(tracker)입니다. Jira는 당신이 상상할 수 있는 거의 모든 워크플로(workflow)에 맞춰 변형될 수 있으며, 바로 그 점 때문에 대기업들이 이를 계속 사용합니다. 하지만 이 중 그 어떤 것도 명세, 스토리, 결정, 그리고 테스트를 연결된 노드(linked nodes)로 보유하지 않습니다. 따라서 SDD(명세 기반 개발)를 지속 가능하게 만드는 '연결성(linkage)'이야말로, 당신의 기술 스택이 탭 사이에서 바닥에 떨어뜨려 버리는 바로 그 핵심 요소입니다.
연결성이 지금 중요한 두 번째 이유는 바로 에이전트(agent) 그 자체입니다. 명세는 에이전트가 실행되는 순간에 무엇을 볼 수 있느냐에 따라 유용성이 결정됩니다. 명세를 컨텍스트(context)에 붙여넣으면 한 가지 작업에는 작동하지만, 에이전트가 그 주변을 수정함에 따라 동일한 세션 내에서도 금세 낡은 정보(stale)가 되어버립니다. 당신이 실제로 원하는 것은 에이전트가 실시간 명세(live spec), 연결된 스토리, 그리고 현재 테스트 상태를 쿼리(query)하여, 지난달에 입력한 내용이 아니라 '오늘의 진실'을 바탕으로 구축하는 것입니다. 이것이 바로 "프롬프트가 아니라 컨텍스트가 모델이다"라는 아이디어의 핵심이며, 제가 명세를 문서가 아닌 '간선(edges)을 가진 노드(node)'로 생각하기 시작한 이유입니다.
저는 이 문제에 너무 깊이 빠져들어 이를 중심으로 제품을 만들었으므로, 다음 문장은 적절한 수준의 비판적 시각(salt)을 가지고 읽어주시기 바랍니다. Stride는 계획, 설계, 테스트, 프로세스를 하나의 연결된 그래프(graph)로 유지하며, MCP 서버를 실행하여 Claude Code와 Codex가 당신이 붙여넣은 스냅샷(snapshot) 대신 실제 스토리와 테스트를 읽을 수 있게 합니다. 이것이 저의 편향된 견해임을 솔직히 밝힙니다. 하지만 여기서 얻을 수 있는 가치의 대부분을 얻기 위해 반드시 제 도구가 필요한 것은 아닙니다. 당신에게 필요한 것은 명세가 테스트에 연결되는 것이며, 드리프트(drift, 괴리)가 발생했을 때 빌드를 깨뜨리는 것입니다. .feature 파일 하나와 하나의 CI 체크만으로도, 무언가를 구매하기 전까지 부끄러울 정도로 먼 거리까지 나아갈 수 있습니다.
제가 아직 깔끔하게 해결하지 못한 부분은 명세 (spec)의 산문(prose)적인 절반입니다. 수락 기준 (Acceptance criteria)은 테스트가 이를 검증할 수 있기 때문에 동작의 정직함을 유지해 줍니다. 하지만 결정 뒤에 숨겨진 이유, 당신이 고민하고 거절했던 트레이드오프 (tradeoff), 컴플라이언스 검토 (compliance review)에서 나온 제약 사항과 같은 추론 (reasoning)은 Given/When/Then 구조로 환원되지 않으며, 그 무엇보다 가장 조용하게 부패합니다. 그래서 이 문제를 여러분께 넘기고자 합니다. 저는 진심으로 작동하고 있는 방식이라면 무엇이든 훔치고 싶기 때문입니다. 만약 여러분이 현재 에이전트 (agent)와 함께 명세 기반 개발 (spec-driven development)을 수행하고 있다면, 수락 테스트 (acceptance tests)뿐만 아니라 의도 (intent)와 추론 (reasoning)이 낡아지지 않도록 어떻게 유지하고 계신가요?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기