Skip to content

의도·스펙 기반 개발

스펙 기반 개발(Spec-Driven Development, SDD) 은 코드보다 스펙을 먼저 쓰고, 그 스펙을 기준으로 구현하는 개발 방식이에요. 스펙이 "지금 시스템이 무엇인가"의 유일한 기준(single source of truth)이 되고, 사람과 코딩 에이전트가 모두 그 스펙을 보고 일해요. 에이전트와 함께 개발하는 일이 늘면서 널리 쓰이게 됐어요.

ResearchTree는 이 방식을 연구에 맞게 바꿔요. 일반적인 스펙 기반 개발은 무엇을 만들지 이미 안다고 보고, 스펙을 확정된 요구사항으로 다뤄요. 연구에서는 결과를 미리 알 수 없어요. 그래서 ResearchTree에서는 스펙 변경이 곧 가설이고, 실험의 결과(채택 또는 기각)가 그 변경을 스펙에 넣을지 정해요. 그리고 스펙 앞에 의도를 한 층 더 둬요. 무엇을 주장하려고 이 설계를 하는지 적어 두고, 실험이 그 주장을 검증해요.

정리하면 의도·스펙 기반 개발은 판정이 붙는 스펙 기반 개발이에요.

세 가지 문서

문서답하는 질문위치언제 바뀌나
의도왜 하나? 무엇을 주장하나? 무엇은 안 하나?research 브랜치의 INTENT.md드물게, 의도적으로
스펙지금 시스템은 무엇인가?research 브랜치의 SPEC.md설계를 바꾸는 실험이 채택될 때
근거무엇을 해 봤고, 무엇을 알게 됐나?각 실험의 PR 본문실험마다

계획이나 할 일 목록은 문서로 두지 않아요. 할 일 하나가 곧 초안 PR 하나예요.

핵심 원칙

  1. 의도가 먼저예요. 무엇을 주장하는지 INTENT.md에 id(N1, N2 …)를 붙여 적고, 실험은 어느 주장을 검증하는지 밝혀요.
  2. 스펙이 기준이에요. 구현은 스펙을 따르고, 스펙에는 지금의 설계만 결정 위주로 적어요. 사람도 에이전트도 설계를 알려면 스펙부터 읽어요.
  3. 설계 변경은 실험 단위로 해요. 설계를 바꾸는 실험은 자기 브랜치에서 스펙을 먼저 고치고, 같은 브랜치에서 구현해요. 스펙 수정과 코드가 한 PR에 함께 들어가요.
  4. 판정이 스펙을 확정해요. 채택되면 스펙 수정이 research에 들어가고, 기각되면 제안으로만 남아요. 그래서 버전의 스펙에는 검증을 통과한 설계만 모여요.
  5. 근거는 PR에 둬요. 측정값, 비교 표, 유도 과정은 실험 PR에 두고, 스펙에서는 링크만 걸어요.

개발 흐름

단계하는 일남는 곳
1. 의도 세우기문제, 목표, 주장, 하지 않을 것, 열린 결정을 적어요INTENT.md
2. 첫 스펙처음 구현의 설계를 섹션별로 적고 첫 버전을 태그해요SPEC.md, research/v1
3. 실험 제안검증할 주장이나 답할 열린 결정을 골라 브랜치와 초안 PR을 열어요PR YAML의 hypothesis, claims
4. 스펙 먼저, 구현은 다음같은 브랜치에서 바꿀 섹션을 먼저 고치고, 그 스펙대로 구현하고 학습·측정해요브랜치의 SPEC.md, 코드, rt.log()
5. 판정결론을 쓰고 채택(머지)하거나 기각(close)해요PR의 결론
6. 버전채택된 설계를 모아 research/vN을 태그해요. 주장이 바뀌었으면 의도 문서도 고쳐요research/vN 시점의 SPEC.md, INTENT.md

값만 조정하는 실험은 4단계에서 스펙을 건드리지 않아요. PR YAML에 spec: none이라고 적어 두면 의도가 분명해져요.

일반적인 스펙 기반 개발과의 차이

일반적인 스펙 기반 개발ResearchTree의 의도·스펙 기반 개발
스펙 변경의 성격확정된 요구사항검증할 가설
변경 단위기능이나 작업실험 하나 (브랜치 하나, PR 하나)
스펙에 반영되는 조건구현이 끝나면실험이 채택되면
채택되지 않은 변경버려져 사라짐기각된 PR과 보관 머지로 기록이 남음
스펙의 버전브랜치나 릴리스research/vN 태그마다 확정된 스펙
목적과의 연결선택 사항의도 문서의 주장과 PR의 claims로 연결

파일 위치: .researchtree.yml

기본 경로는 SPEC.mdINTENT.md예요. 레포가 다른 이름을 쓰면, 루트 브랜치(research)의 맨 위에 .researchtree.yml을 두고 경로를 적어요. 이 파일은 팀 전체가 같이 쓰는 설정이에요.

yaml
spec: docs/PHASE.md      # 스펙 문서 (기본 SPEC.md)
intent: docs/INTENT.md   # 의도 문서 (기본 INTENT.md)
prefix: experiment/      # 실험 브랜치 접두사 (기본 experiment/)
  • 뷰어, CLI, Python API, 에이전트 스킬이 모두 이 파일을 읽어요.
  • prefix는 뷰어의 개인 브랜치 설정보다 먼저예요. Python에서는 rt.load(prefix=…)RESEARCHTREE_PREFIX가 이 파일보다 먼저예요.
  • 루트 브랜치 이름은 이 파일에 적을 수 없어요. 파일이 그 브랜치 안에 있기 때문이에요. 루트 브랜치는 뷰어 설정이나 RESEARCHTREE_ROOT로 정해요.
  • 알 수 없는 키나 잘못된 값은 무시하고, 뷰어의 설정 창에 무엇을 무시했는지 보여줘요.

스펙 쓰는 법

결정만 적어요. 시스템이 무엇을 하는지, 어떤 식을 쓰는지, 어떤 값을 골랐는지만 둬요. 유도 과정, 측정값, 비교 표는 PR에 두고 스펙에서는 링크만 걸어요.

markdown
### 그래디언트 이득
> 사전학습 행의 그래디언트는 0, 신규 행은 k ≈ 1.44배로 둔다.

스텝 길이만 복원하고 읽기 방향 성분은 복원하지 않는다.

근거: experiment/gradient-gain (#22)
  • 섹션마다 첫 줄은 > 한 줄 요약이에요. 제목과 요약만 모으면 전체 설계가 한 페이지가 돼요(researchtree spec --summary, 뷰어의 요약 보기).
  • 섹션 하나가 기능 하나예요. 실험의 스펙 변경이 "어느 기능을 추가하거나 고쳤나"로 보이게 돼요.
  • 현재 상태만 적어요. "예전에는 X를 썼다", "Y는 해 봤는데 기각" 같은 이력은 Git과 PR에 있어요. 뷰어가 섹션마다 이력을 보여줘요.
  • 섹션은 한 화면(40줄) 안으로 둬요. 넘으면 먼저 근거를 PR로 옮길 게 있는지 봐요. researchtree spec --check가 요약 줄이 없는 섹션과 너무 긴 섹션을 알려줘요.
  • 제목은 되도록 바꾸지 않아요. 섹션은 제목으로 알아보기 때문이에요. 바꿔야 한다면 먼저 id를 달아 두세요. 그러면 제목이 바뀌어도 같은 섹션으로 이어져요. id 주석은 GitHub에서도 보이지 않아요.
    markdown
    ## 영어 및 듀플렉스 능력 보존 전략 <!-- id: p1-english -->
  • 문서가 커지면 파일로 떼어내요. 떼어낸 자리에 include 주석을 두면, 도구가 원래 자리에 이어 붙여 한 문서로 보여줘요. 경로는 그 주석이 있는 파일 기준이에요.
    markdown
    <!-- include: phase1/gradient.md -->
    떼어낼 때는 내용을 바꾸지 않아요. 옮기기만 하는 커밋은 실험이 아니라서 research에 바로 커밋해도 돼요.

판정과 스펙

  • 채택된 실험의 스펙 수정은 머지와 함께 research에 들어가요.
  • 기각된 브랜치는 researchtree release가 revert한 뒤 보관 머지해요. 그래서 기각된 스펙 제안은 기록으로는 남지만 버전의 스펙에는 들어가지 않아요.
  • 뷰어의 실험 패널 스펙 탭에서, 그 실험이 갈라진 지점과 비교해 스펙의 어느 섹션을 바꿨는지 볼 수 있어요. 기각된 실험이면 "제안으로만 남았다"고 알려줘요.

의도와 주장

  • 의도 문서에는 문제, 목표, 제약, 하지 않을 것, 그리고 주장을 적어요. 주장에는 N1, N2 같은 id를 붙이고, 제목을 ### N1. 말하기만 학습해도 듣기가 생긴다처럼 써요.
  • 주장을 검증하는 실험은 PR YAML에 claims: [N1]을 적어요. 뷰어의 내용 탭에 칩으로 보이고, researchtree spec --claims가 주장별로 실험과 결과를 모아 보여줘요.
  • 답이 나오면 주장을 바꿀 질문은 열린 결정으로 적어 두고, 답이 나오면 의도 문서를 고쳐요.
  • 채택된 결과가 주장과 어긋나면 의도 문서를 그대로 두지 말고 고쳐요. 의도 문서가 틀린 채 남아 있으면, 사람도 에이전트도 틀린 전제로 일하게 돼요.

뷰어에서 보기

버전 패널 → 스펙 탭

보기내용
전체그 버전의 스펙 전체를 섹션별로 보여줘요. 직전 버전 대비 추가·수정·이름 변경된 섹션에는 배지가 붙고, 섹션 옆 시계 버튼을 누르면 그 섹션이 어느 버전에서 추가되고 바뀌었는지(합쳐진 실험과 함께) 나와요
요약제목과 요약 줄만 모은 한 페이지
변경직전 버전 대비 바뀐 섹션 목록. 누르면 그 섹션의 줄 단위 변경이 펼쳐져요

가장 최근 버전의 스펙 탭이 곧 지금의 최종 스펙이에요.

실험 패널 → 스펙 탭: 이 브랜치가 스펙에서 바꾼 섹션이에요. 기준은 브랜치가 갈라진 커밋이에요.

터미널과 Python

bash
researchtree spec                    # 최신 버전의 스펙 (include를 이어 붙인 한 문서)
researchtree spec v3                 # 특정 버전
researchtree spec --summary          # 제목과 요약 줄만
researchtree spec --diff             # 직전 버전 대비 바뀐 섹션과 줄 단위 변경 (--diff v2 로 다른 버전과 비교)
researchtree spec --experiment duet-mix   # 실험 하나가 스펙에서 바꾼 섹션
researchtree spec --history vocoder  # 섹션 하나의 이력 (섹션 키)
researchtree spec --check            # 요약 줄 없음, 너무 긴 섹션, 읽지 못한 include
researchtree spec --claims           # 주장별로 검증한 실험
researchtree spec --intent           # 의도 문서 (다른 옵션과 함께 쓸 수 있어요)
researchtree spec --out SPEC-v5.md   # 파일로 저장
python
spec = research.spec()                     # 최신 버전 (research.spec("v3"), research.version("v3").spec())
print(spec.summary())
spec["vocoder"].summary, spec["vocoder"].body
research.version("v4").spec_changes()      # 직전 버전 대비 섹션 변경
research["duet-mix"].spec_changes()        # 실험이 갈라진 지점 대비
research.spec_history("vocoder")           # [(Version, "added"), (Version, "changed"), ...]
research.claims()["N1"]                    # N1을 검증한 실험들 (Experiments)
research.intent()                          # 의도 문서

섹션을 알아보는 규칙

  • 섹션은 # 제목 하나마다 하나예요. 코드 블록 안의 #는 제목이 아니에요. 첫 제목 앞의 글은 "머리말" 섹션이에요.
  • 섹션 키는 id가 있으면 id, 없으면 부모 섹션의 키와 제목을 이어 만든 이름이에요(예: vocoder/sample-rate). 문서 제목(#)은 키에 들어가지 않아서, 문서 제목을 바꿔도 이력이 끊기지 않아요.
  • 섹션의 "변경"은 제목 아래의 자기 본문이 바뀐 거예요. 줄 끝 공백이나 앞뒤 빈 줄만 바뀐 건 변경이 아니에요. 본문은 그대로이고 제목만 바뀌면 "이름 변경"이에요. id 없이 제목을 바꾸면 "삭제"와 "추가"로 보여요.
  • 뷰어(TypeScript)와 CLI·Python이 같은 규칙을 쓰고, 같은 테스트 픽스처로 결과를 맞춰요.