스펙 주도 개발에서 명세는 무엇을 적는가 — 행동 계약·RFC 2119·EARS·델타 명세

정기창·

스펙 주도 개발(Spec-Driven Development)의 명세는 생각보다 정해진 모양을 하고 있습니다. 요구는 대문자 SHALL 로 쓰고, 그 아래에는 시나리오를 두고, 바꿀 때는 전체를 다시 쓰지 않고 바뀌는 것만 델타로 적습니다. OpenSpec 의 원칙만 빌려 적용해 본 앞 글에서 그 적용기를 적었는데, 적고 나서 질문이 하나 남았습니다. 명세는 왜 하필 이런 모양일까 하는 것입니다.

이 글은 그 질문에 답하려고 읽은 것들을 정리한 개념편입니다. 명세를 얼마나 오래 붙드는가로 나눈 세 층위, 명세를 «행동 계약»이라 부르는 이유, 요구 문장의 문법(RFC 2119·EARS·시나리오), 델타로 바꾸는 이유, 그리고 모델 주도 개발(MDD)이 남긴 교훈을 차례로 적었습니다. 폴더 구조와 델타 세 절이 합쳐지는 방식, 명령어는 앞 글에서 다뤘으니 다시 적지 않습니다.

먼저 밝혀 둘 것이 있습니다. 이 글은 공개된 문서와 RFC, 칼럼을 읽고 정리한 것이지 새로 실험한 결과가 아닙니다. 원문은 영어 그대로 옮기고 풀이를 붙였고, 제 해석이 들어간 곳은 해석이라고 표시했습니다. 예시가 필요한 곳은 앞 글과 같이 가상의 «회의실 예약 서비스»로 적었습니다.

스펙 주도 개발의 세 층위 — spec-first부터 spec-as-source 까지

스펙 주도 개발이라는 말은 요즘 여러 뜻으로 쓰입니다. 그 뜻을 가장 깔끔하게 갈라 준 글은 Birgitta Böckeler 가 martinfowler.com 에 2025년 10월 15일 올린 «Understanding Spec-Driven-Development: Kiro, spec-kit, and Tessl»이었습니다.

Böckeler 는 먼저 명세가 무엇이 아닌지를 가릅니다. 명세는 «A spec is a structured, behavior-oriented artifact», 즉 구조를 갖춘, 행동을 중심에 둔 산출물이고, 특정 기능을 만들거나 바꾸는 작업에 딸린 문서입니다. 이와 구별되는 것이 메모리 뱅크입니다. 규칙 파일이나 제품·코드베이스 개요처럼 «These files are relevant across all AI coding sessions in the codebase», 코드베이스의 모든 세션에 두루 적용되는 일반 맥락 문서입니다. 둘을 섞으면 명세가 모든 것을 담는 문서가 되고, 그러면 무엇을 검증해야 하는지가 흐려집니다.

그다음 Böckeler 는 스펙 주도 개발을 세 층위로 나눕니다.

층위 원문 풀이
spec-first «A well thought-out spec is written first, and then used in the AI-assisted development workflow for the task at hand.» 작업 하나를 위해 먼저 쓰고, 작업이 끝나면 역할도 끝남
spec-anchored «The spec is kept even after the task is complete» 작업 뒤에도 남겨 그 기능을 고치고 유지하는 데 씀
spec-as-source «The spec is the main source file over time, and only the spec is edited by the human, the human never touches the code.» 명세가 주된 원천이고, 사람은 명세만 고치며 코드는 건드리지 않음

세 층위를 가르는 축은 결국 하나라는 생각이 들었습니다. 명세를 얼마나 오래, 얼마나 무겁게 붙드는가입니다. 층위가 올라갈수록 명세는 작업의 입력에서 기능의 기록으로, 다시 코드를 대신하는 원천으로 책임이 커집니다.

OpenSpec 은 Böckeler 글이 다룬 도구가 아니어서, 어느 층위인지는 제 읽기일 뿐입니다. OpenSpec 의 개념 문서는 «Specs are the source of truth — they describe how your system currently behaves.»라고 적고, 변경을 마치면 델타를 본 명세에 합칩니다. 작업이 끝나도 명세가 남아 현재 동작을 적는 구조이니, 제 읽기로는 spec-anchored 에 가깝습니다.

반면 Spec Kit 의 spec-driven.md는 «Specifications don't serve code—code serves specifications.»라고 선언합니다. 명세가 코드를 섬기는 것이 아니라 코드가 명세를 섬긴다는 말이고, 같은 문서의 «When specifications and implementation plans generate code, there is no gap—only transformation.»까지 함께 읽으면 spec-as-source 쪽을 지향하는 선언으로 읽힙니다. 다만 선언이 곧 실제 동작은 아니라서, 이 분류를 단정하지는 않겠습니다.

층위를 먼저 적은 것은, 아래의 개념들이 모두 명세를 오래 붙들수록 더 필요해지는 장치이기 때문입니다. 한 번 쓰고 버릴 문서라면 이런 규율이 없어도 큰 탈이 나지 않습니다.

명세는 행동 계약입니다 — 구현을 빼는 두 가지 이유

OpenSpec 의 개념 문서는 명세를 한 문장으로 정의합니다. «A spec is a behavior contract, not an implementation plan.» 명세는 구현 계획이 아니라 행동에 대한 계약이라는 뜻입니다. 그래서 명세에 넣지 않을 것도 적혀 있습니다. 내부 클래스와 함수 이름(«Internal class/function names»), 라이브러리나 프레임워크 선택(«Library or framework choices»), 단계별 구현 세부(«Step-by-step implementation details») 같은 것들입니다.

판별하는 질문도 하나 있습니다.

If implementation can change without changing externally visible behavior, it likely does not belong in the spec.

바깥에서 보이는 행동을 바꾸지 않고도 구현을 바꿀 수 있다면, 그 구현은 아마 명세에 속하지 않는다는 말입니다. 명세 작성 문서는 같은 말을 조금 다르게 적습니다. «A spec says what your system does, in terms anyone could check — not how it's built.» 명세는 시스템이 무엇을 하는지를 누구나 확인할 수 있는 말로 적는 것이지, 어떻게 만들었는지를 적는 것이 아니라는 뜻입니다. 큐나 라이브러리, 테이블 스키마 같은 «어떻게»는 design.md 나 코드에 두라고 합니다.

처음 읽었을 때는 이것이 추상화에 관한 미학처럼 들렸습니다. 다시 읽어 보니 이유는 훨씬 실용적이었습니다. 같은 문서의 다음 문장이 그 이유를 둘로 말해 줍니다. 행동과 구현을 한 요구에 섞으면 «the requirement stops being testable», 그 요구는 시험할 수 없게 되고, «starts going stale the moment the code changes», 코드가 바뀌는 순간 낡기 시작한다는 것입니다.

첫째는 시험 가능성입니다. 누구나 확인할 수 있는 말로 적어야 누구든 확인할 수 있습니다. 둘째는 낡지 않음입니다. 구현은 자주 바뀌지만 행동은 그보다 덜 바뀝니다. 구현을 적은 명세는 구현이 바뀔 때마다 함께 고쳐야 하고, 고치지 않으면 거짓이 됩니다. 회의실 예약 서비스로 옮기면 차이가 분명해집니다.

구현을 적은 요구
  The system SHALL acquire a Redis lock on the room ID before inserting a booking.

행동을 적은 요구
  The system SHALL reject a booking that overlaps an existing booking for the same room.

앞의 문장은 락을 다른 방식으로 바꾸는 순간 틀린 문장이 됩니다. 예약이 겹치지 않는다는 행동은 그대로인데도 말입니다. 그리고 «락을 잡았는가»는 사용자가 확인할 수 없는 사실입니다. 뒤의 문장은 저장소를 무엇으로 바꾸든 참이고, 겹치는 예약을 넣어 보면 누구든 확인할 수 있습니다.

이 경계가 말처럼 쉽지는 않은 모양입니다. Böckeler 도 같은 글에서 «I frequently got confused when to stay on the functional level, and when it was time to add technical details.»라고 적었습니다. 기능 수준에 머물러야 할 때와 기술 세부를 더해야 할 때를 자주 헷갈렸다는 고백입니다. 그리고 요구와 구현을 가르는 일에 대해 «I don't think we have a good track record as a profession to do this well.», 우리가 직업으로서 이 일을 잘해 온 이력이 있다고 생각하지 않는다고 덧붙입니다. 행동 계약이라는 원칙은 새로운 발명이라기보다, 오래 잘 안 되던 일을 다시 한번 명시적으로 요구하는 것이라는 생각이 들었습니다.

명세 문장의 문법 — RFC 2119, EARS, 시나리오

행동만 적기로 했다고 해서 문장이 저절로 명확해지지는 않습니다. 자연어는 같은 문장도 읽는 사람마다 다르게 읽히기 쉽습니다. 명세가 문장 틀을 정해 두는 것은 그 때문인데, 들여다보니 틀마다 줄이려는 모호함이 달랐습니다.

RFC 2119 — 의무의 강도를 정하는 어휘

대문자 SHALL 의 출처는 1997년 3월 S. Bradner 가 쓴 RFC 2119(BCP 14)입니다. 초록은 짧습니다. «In many standards track documents several words are used to signify the requirements in the specification.» 많은 표준 문서가 요구를 나타내려고 몇몇 낱말을 쓰는데, 이 문서는 그 낱말들을 IETF 문서에서 어떻게 해석할지 정한다는 것입니다. 표준 문서마다 «반드시»와 «권장»을 섞어 쓰면 구현하는 쪽마다 다르게 읽고, 그러면 서로 맞물려야 할 구현들이 어긋납니다.

낱말 원문 정의 풀이
MUST · SHALL «an absolute requirement of the specification» 예외 없는 절대 요구
SHOULD «there may exist valid reasons in particular circumstances to ignore a particular item, but the full implications must be understood and carefully weighed» 특정한 상황에서 따르지 않을 타당한 이유가 있을 수 있지만, 그 결과를 충분히 이해하고 따져 본 뒤에야 다르게 갈 수 있음
MAY «an item is truly optional» 정말로 선택

2017년의 RFC 8174는 여기에 한 가지를 분명히 했습니다. 이 낱말들은 대문자로 쓸 때만 이런 특별한 뜻을 갖는다는 것입니다. 소문자 should 는 그냥 영어의 should 입니다. 명세의 SHALL 이 늘 대문자인 이유가 여기에 있습니다.

OpenSpec 은 이 어휘를 그대로 가져와 «RFC 2119 keywords (SHALL, MUST, SHOULD, MAY) communicate intent», 의도를 전하는 데 쓴다고 적습니다. 명세 작성 문서의 권고는 «Reach for MUST/SHALL by default.», 기본은 MUST 나 SHALL 이고, SHOULD 는 정당한 예외의 여지를 둔 강한 권고일 때만 쓰라는 것입니다. 제 생각에는 이 권고가 중요한 지점입니다. SHOULD 를 «되도록이면» 정도의 부드러운 말투로 쓰기 시작하면, 그 요구는 지켜지지 않아도 위반이 아닌 요구가 되어 버립니다.

EARS — 조건이 앉을 자리를 정하는 문형

RFC 2119 가 의무의 강도를 정한다면, EARS(Easy Approach to Requirements Syntax)는 문장의 모양을 정합니다. Alistair Mavin 과 동료들이 Rolls-Royce 에서 제트엔진 제어 시스템의 감항(airworthiness) 규정을 분석하면서 만들었고, 2009년 RE'09 학회에서 처음 발표했습니다. 자연어로 쓴 요구가 모호하고, 중복되고, 장황하고, 시험할 수 없게 되는 문제를 줄이려는 문형 틀입니다.

EARS 는 요구를 몇 가지 패턴으로 나눕니다. 원문의 틀은 이렇습니다.

Ubiquitous         The <system name> shall <system response>
State driven       While <precondition(s)>, the <system name> shall <system response>
Event driven       When <trigger>, the <system name> shall <system response>
Optional feature   Where <feature is included>, the <system name> shall <system response>
Unwanted behaviour If <trigger>, then the <system name> shall <system response>
Complex            While 와 When 을 함께 씀

틀을 보면 조건이 앉는 자리가 문장 맨 앞으로 정해져 있습니다. 상태는 While, 사건은 When, 선택 기능은 Where, 원치 않는 상황은 If 로 시작하니, 문장 구조만 보고도 조건과 반응을 가를 수 있습니다. 조건이 문장 끝에 붙거나 여러 절에 흩어져 있으면, 그 조건이 반응 전체에 걸리는지부터 헷갈립니다.

AWS 의 Kiro 는 요구 문서인 requirements.md 를 이 EARS 로 씁니다. Kiro 문서의 틀은 «WHEN [condition/event] THE SYSTEM SHALL [expected behavior]»이고, 예시는 «WHEN a user submits a form with invalid data THE SYSTEM SHALL display validation errors next to the relevant fields»입니다. EARS 의 사건 패턴에 RFC 2119 의 대문자 강도를 얹은 모양입니다. 회의실 예약 서비스로 옮기면 이렇게 됩니다.

Event driven       When a user books a room, the booking system shall send a confirmation email.
State driven       While a room is under maintenance, the booking system shall hide it from search results.
Unwanted behaviour If a booking overlaps an existing booking for the same room,
                   then the booking system shall reject it with a conflict message.

시나리오 — 해석을 구체 사례로 묶기

강도와 조건을 정해도 남는 모호함이 있습니다. «겹친다»는 것이 정확히 무엇인지 같은 것입니다. 앞 예약이 10시에 끝나고 다음 예약이 10시에 시작하면 겹치는 걸까요. 이런 것은 문장을 아무리 다듬어도 읽는 사람마다 다르게 읽힐 수 있습니다. 시나리오는 그 자리에 구체적인 사례를 박아 둡니다.

OpenSpec 문서는 «Scenarios are the "when" — they provide concrete examples that can be verified.»라고 적습니다. 시나리오는 확인할 수 있는 구체 사례라는 뜻입니다.

명세 작성 문서는 나쁜 시나리오도 짚습니다. «A scenario that just restates the requirement in other words tests nothing.», 요구를 다른 말로 되풀이할 뿐인 시나리오는 아무것도 시험하지 않는다는 것입니다. 시나리오의 제목에도 경우의 이름을 적으라고 합니다. «Scenario: Rejects an expired token» 은 무엇을 다루는지 한눈에 알려 주지만, «Scenario: Test 2» 는 그렇지 않습니다.

#### Scenario: Back-to-back bookings do not overlap
- GIVEN a booking for Room A from 09:00 to 10:00
- WHEN a user books Room A from 10:00 to 11:00
- THEN the booking is accepted

이 시나리오 하나로 «끝 시각과 시작 시각이 같으면 겹치지 않는다»는 해석이 고정됩니다. 행복한 경로만이 아니라 이런 경계의 경우를 적어야 시나리오가 제 몫을 한다는 생각이 들었습니다.

요구 하나의 크기에 대한 권고도 있습니다. «A good requirement is one behavior, stated so plainly you could hand it to someone else to test.» 좋은 요구는 행동 하나이고, 남에게 건네 시험하게 할 수 있을 만큼 평이해야 한다는 것입니다. «The system SHALL handle large uploads gracefully» 는 무엇을 보면 통과인지 알 수 없지만, «The system SHALL show an error banner when the upload exceeds 10 MB» 는 10MB 를 넘는 파일을 올려 보면 확인할 수 있습니다.

GIVEN·WHEN·THEN 문형 자체는 Dan North 가 2006년 «Introducing BDD»에서 소개한 행동 주도 개발(BDD)에서 왔습니다. 인수 기준을 이 문형으로 적는 이야기는 «기획 문서의 종류와 체계 (3): TRD·ADR·API Spec과 검증»에 적은 적이 있어 여기서는 줄입니다.

세 가지 틀은 세 가지 모호함을 줄입니다

셋을 나란히 놓고 보니, 각각 다른 종류의 모호함을 맡고 있다는 생각이 들었습니다. 아래 분류는 출처의 말이 아니라 제 해석입니다.

틀 줄이는 모호함 묻는 질문
RFC 2119 강도의 모호함 이것은 반드시인가, 권장인가, 선택인가
EARS 조건의 모호함 언제, 어떤 상태에서 이 반응이 일어나는가
시나리오 해석의 모호함 이 낱말은 구체적으로 어떤 경우를 뜻하는가

셋은 서로 대신하지 않습니다. 대문자 SHALL 을 써도 조건이 흐릿할 수 있고, EARS 틀을 지켜도 «겹친다»의 뜻은 정해지지 않습니다. 그래서 OpenSpec 의 요구가 SHALL 문장 아래에 시나리오를 두는 모양을 하고 있는 것이라고 읽었습니다.

왜 전체가 아니라 델타 명세인가 — 지금의 사실과 지나온 길을 가르기

앞 글에서는 델타 명세의 세 절이 합쳐질 때 어떻게 동작하는지만 적었습니다. 여기서는 왜 그렇게 하는지를 적습니다. OpenSpec 개념 문서의 «Why Deltas Instead of Full Specs» 절은 이유를 넷 듭니다.

  • 명확성 — «A delta shows exactly what's changing.» 전체 명세를 읽으면 현재 판과 머릿속으로 비교해야 합니다(«you'd have to diff it mentally against the current version»).
  • 충돌 회피 — «Two changes can touch the same spec file without conflicting, as long as they modify different requirements.» 서로 다른 요구를 고치는 한, 두 변경이 같은 명세 파일을 건드려도 충돌하지 않습니다.
  • 리뷰 효율 — «Reviewers see the change, not the unchanged context.» 리뷰하는 사람은 바뀌지 않은 문맥이 아니라 바뀐 것을 봅니다.
  • 기존 프로젝트와의 맞음 — «Most work modifies existing behavior. Deltas make modifications first-class, not an afterthought.» 대부분의 일은 이미 있는 행동을 고치는 일이니, 수정을 덧붙이는 것이 아니라 일급으로 다룬다는 것입니다.

그리고 변경을 마치는 일을 이렇게 정의합니다. «Archiving completes a change by merging its delta specs into the main specs and preserving the change for history.» 델타를 본 명세에 합쳐 변경을 마무리하고, 변경 자체는 이력으로 보존한다는 뜻입니다.

여기서부터는 제 해석이고, 유비입니다. 이 구조는 소프트웨어가 여러 번 다시 발명한 모양과 닮았습니다.

분야 지금 무엇이 참인가 어떻게 여기까지 왔나
데이터베이스 현재 스키마 순서대로 쌓인 마이그레이션 파일
git 작업 트리 커밋
이벤트 소싱 스냅샷 이벤트 로그
OpenSpec 본 명세(specs) 보관된 변경(archive)

공통점은 «지금 무엇이 참인가»와 «어떻게 여기까지 왔나»를 다른 자리에 둔다는 것입니다. 현재 상태만 남기면 왜 이렇게 되었는지가 사라집니다. 반대로 변경만 남기면, 지금을 알기 위해 처음부터 모든 변경을 다시 재생해야 합니다. 둘을 함께 두는 것은 두 질문의 비용을 나누는 방법이라고 생각합니다. 지금을 묻는 사람은 본 명세를 읽고, 이유를 묻는 사람은 이력을 읽습니다.

리뷰 쪽에서도 이유가 보입니다. 리뷰하는 사람의 주의는 한정된 자원이라, 바뀌지 않은 문맥까지 다시 읽게 하면 정작 바뀐 한 줄을 놓치기 쉽습니다. 요구 단위로 충돌을 판정하는 발상도 코드 리뷰의 diff 와 같습니다. 명세를 코드처럼 다룬다는 것은 코드에 쓰던 장치를 명세에도 쓰겠다는 뜻이라는 생각이 들었습니다.

오래된 꿈의 반복 — 모델 주도 개발이 남긴 교훈

명세에서 코드를 만든다는 생각은 새롭지 않습니다. Böckeler 는 같은 글에서 모델 주도 개발(MDD, Model-Driven Development)을 떠올립니다. MDD 에서는 UML 이나 텍스트 DSL 로 쓴 모델이 명세의 역할을 했습니다. 그리고 이렇게 우려합니다.

I wonder if spec-as-source, and even spec-anchoring, might end up with the downsides of both MDD and LLMs

spec-as-source 는 물론이고 spec-anchored 조차, MDD 의 단점과 LLM 의 단점을 함께 떠안게 될 수 있지 않을까 하는 의문입니다. 그래서 «we should look to code-from-spec attempts in the past to learn from them», 명세에서 코드를 만들려던 과거의 시도에서 배워야 한다고 적습니다.

둘을 견주어 보면 차이가 하나 눈에 띕니다. 여기서부터는 제 정리입니다. MDD 의 모델은 형식 언어였고, 지금의 명세는 자연어입니다. 자연어 덕분에 진입은 훨씬 쉬워졌습니다. 누구나 읽고 쓸 수 있습니다. 대신 같은 명세에서 같은 코드가 나온다는 보장, 즉 결정성은 사라졌습니다. Spec Kit 문서의 «there is no gap—only transformation»은 명세와 구현 사이에 틈이 없고 변환만 있다는 말인데, 변환하는 쪽이 같은 입력에 매번 같은 결과를 내지 않는다면 그 틈이 정말 없는지는 아직 검증되지 않은 약속이라고 봅니다. 틀렸다고 말할 근거도 저에게는 없습니다. 다만 아직 증명되지 않았다는 것까지는 말할 수 있습니다.

Böckeler 의 우려는 그 밖에도 있습니다. 명세 중심의 흐름이 «ultimately amplifying existing challenges like review overload and hallucinations», 리뷰 과부하나 환각 같은 기존의 문제를 오히려 키울 수 있다는 것입니다. 제 짐작으로는, 명세가 길어지면 리뷰할 문서도 그만큼 늘어나는 것이 그 한 갈래입니다.

또 «Kiro and spec-kit provide one opinionated workflow each», 두 도구가 저마다 하나의 정해진 흐름을 내놓다 보니, 작은 버그 하나를 고칠 때는 «like using a sledgehammer to crack a nut», 호두 하나를 깨려고 큰 망치를 쓰는 것 같았다고 적습니다. 그리고 독일어 낱말 Verschlimmbesserung 을 꺼냅니다. 고치려다 오히려 더 나빠지는 일을 뜻하는 말입니다.

Are we making something worse in the attempt of making it better?

도구들 사이의 차이도 결국 이 무게의 축 위에 있습니다. OpenSpec 의 README는 Spec Kit 을 «Thorough but heavyweight. Rigid phase gates»라고, Kiro 는 IDE 와 모델에 묶여 있다고 평하고, 자신의 철학을 «fluid not rigid», «iterative not waterfall»로 적습니다. 도구 쪽의 자기 소개이니 그대로 믿을 것은 아니지만, 무엇을 차이로 내세우는지는 보여 줍니다. 명세를 얼마나 무겁고 단단한 단계로 붙드는가입니다.

정리 — 스펙 주도 개발 명세가 가진 네 가지 성질

읽은 것을 한 표로 모으면 다음과 같습니다.

질문 답 근거
무엇을 적나 바깥에서 관찰할 수 있는 행동. 구현은 빼고 design.md 나 코드로 OpenSpec concepts·writing-specs
어떤 문장으로 RFC 2119 의 강도 + EARS 의 조건 + 시나리오의 사례 RFC 2119·8174, EARS, OpenSpec
어떻게 바꾸나 델타로 적고, 본 명세에 합치고, 변경은 이력으로 보존 OpenSpec concepts
얼마나 오래 붙드나 spec-first · spec-anchored · spec-as-source Böckeler

돌이켜 보면 앞 글에서 해 본 일도 이 개념들 위에 있었습니다. 요구마다 동작과 합격·실패 사례를 적은 것은 행동 계약과 시나리오에, 요구와 현황을 다른 파일로 가른 것은 지금의 사실과 지나온 길을 가르는 발상에 해당합니다. 그때는 원칙을 빌린다고만 생각했는데, 그 뒤에 RFC 와 항공 규정 분석과 MDD 의 경험이 쌓여 있었다는 것을 이번에 알았습니다.

명세의 모양이 정해져 있는 이유도 조금 분명해졌습니다. 형식 자체가 목적이 아니라, 자연어로 쓴 문서가 시험할 수 있고 오래 버티게 하려는 장치들이라는 생각이 들었습니다.

아직 모르는 것도 남아 있습니다. 자연어 명세가 MDD 가 겪은 실패를 피할 수 있을지는 업계 전체로도 아직 검증되는 중입니다. 명세를 오래 붙들수록 명세를 지키는 비용도 커질 텐데, 그 비용이 얻는 것보다 작을지는 층위마다, 일의 크기마다 다를 것 같습니다. 이 글은 문서를 읽고 정리한 개념편이라 그 답을 내지 못합니다. 고치려다 더 나빠지는 것은 아닌가 하는 Böckeler 의 질문을, 저도 명세를 쓸 때마다 한 번씩 꺼내 보려 합니다.

명세 작성행동 계약RFC 2119EARS델타 명세스펙 주도 개발spec-anchored