의도·스펙 기반 개발
스펙 기반 개발(Spec-Driven Development, SDD) 은 코드보다 스펙을 먼저 쓰고, 그 스펙을 기준으로 구현하는 개발 방식이에요. 스펙이 "지금 시스템이 무엇인가"의 유일한 기준(single source of truth)이 되고, 사람과 코딩 에이전트가 모두 그 스펙을 보고 일해요. 에이전트와 함께 개발하는 일이 늘면서 널리 쓰이게 됐어요.
ResearchTree는 이 방식을 연구에 맞게 바꿔요. 일반적인 스펙 기반 개발은 무엇을 만들지 이미 안다고 보고, 스펙을 확정된 요구사항으로 다뤄요. 연구에서는 결과를 미리 알 수 없어요. 그래서 ResearchTree에서는 스펙 변경이 곧 가설이고, 실험의 결과(채택 또는 기각)가 그 변경을 스펙에 넣을지 정해요. 그리고 스펙 앞에 의도를 한 층 더 둬요. 무엇을 주장하려고 이 설계를 하는지 적어 두고, 실험이 그 주장을 검증해요.
정리하면 의도·스펙 기반 개발은 판정이 붙는 스펙 기반 개발이에요.
세 가지 문서
| 문서 | 답하는 질문 | 위치 | 언제 바뀌나 |
|---|---|---|---|
| 의도 | 왜 하나? 무엇을 주장하나? 무엇은 안 하나? | research 브랜치의 INTENT.md | 드물게, 의도적으로 |
| 스펙 | 지금 시스템은 무엇인가? | research 브랜치의 SPEC.md | 설계를 바꾸는 실험이 채택될 때 |
| 근거 | 무엇을 해 봤고, 무엇을 알게 됐나? | 각 실험의 PR 본문 | 실험마다 |
계획이나 할 일 목록은 문서로 두지 않아요. 할 일 하나가 곧 초안 PR 하나예요.
핵심 원칙
- 의도가 먼저예요. 무엇을 주장하는지
INTENT.md에 id(N1,N2…)를 붙여 적고, 실험은 어느 주장을 검증하는지 밝혀요. - 스펙이 기준이에요. 구현은 스펙을 따르고, 스펙에는 지금의 설계만 결정 위주로 적어요. 사람도 에이전트도 설계를 알려면 스펙부터 읽어요.
- 설계 변경은 실험 단위로 해요. 설계를 바꾸는 실험은 자기 브랜치에서 스펙을 먼저 고치고, 같은 브랜치에서 구현해요. 스펙 수정과 코드가 한 PR에 함께 들어가요.
- 판정이 스펙을 확정해요. 채택되면 스펙 수정이
research에 들어가고, 기각되면 제안으로만 남아요. 그래서 버전의 스펙에는 검증을 통과한 설계만 모여요. - 근거는 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.md와 INTENT.md예요. 레포가 다른 이름을 쓰면, 루트 브랜치(research)의 맨 위에 .researchtree.yml을 두고 경로를 적어요. 이 파일은 팀 전체가 같이 쓰는 설정이에요.
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에 두고 스펙에서는 링크만 걸어요.
### 그래디언트 이득
> 사전학습 행의 그래디언트는 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
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 # 파일로 저장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이 같은 규칙을 쓰고, 같은 테스트 픽스처로 결과를 맞춰요.