코드를 전혀 읽지 않고 3개월간 스펙 기반 개발 (Spec-driven development)을 수행한 경험
요약
코드를 직접 읽지 않고 스펙(Spec)과 테스트를 중심으로 개발하는 '스펙 기반 개발(Spec-driven development)' 방법론을 소개합니다. 백로그를 Git 데이터로 관리하며, LLM 에이전트의 작업 효율을 높이기 위한 데이터 구조와 성숙 단계 모델을 제안합니다.
핵심 포인트
- 코드를 블랙박스로 취급하고 스펙과 테스트가 코드를 제어하는 방식
- 백로그를 문서가 아닌 YAML 프론트매터 기반의 Git 데이터로 관리
- 상태 변경을 텍스트 이동이 아닌 멱등성을 가진 필드 업데이트로 처리
- 모델, 노력, 리뷰 강도를 티켓별로 설정하는 성숙 단계(Maturation) 도입
저는 스크럼 마스터 (Scrum Master)입니다. 10년 전에는 개발자였습니다. LLM과 설계 및 트레이드오프 (Trade-offs)에 대해 논의할 수 있을 만큼의 충분한 배경 지식을 가지고 있습니다. 하지만 3개월 전, 저는 저의 개인 프로젝트에 대해 의도적인 도박을 했습니다. 바로 코드를 절대 읽지 않겠다는 것이었습니다.
스펙 (Specs)이 테스트를 정의합니다. 테스트가 코드를 제어합니다. 코드는 블랙박스 (Black box)입니다.
이것이 모든 사람이 해야 하는 방식이라고 주장하는 것은 아닙니다. 하지만 이것은 저의 도박이며, 이로 인해 하나의 시스템이 존재하게 되었습니다. 아무도 코드를 읽지 않을 때, **프로세스 (Process)**는 보통 코드를 읽는 인간이 제공하는 신뢰를 반드시 담보해야 합니다. 저는 방금 그 시스템을 참조 구현체 (Reference implementation)로 공개했습니다:
backlog-as-data — 전체 기술 문서, 영어로 번역된 Claude Code 기술, 그리고 제 일상적인 설정에서 그대로 가져온 CLI 소스 코드입니다.
요약하자면 다음과 같습니다.
백로그 (Backlog)는 문서가 아니라 git 데이터입니다
대부분의 에이전트 (Agent) 작업 관리 도구들은 작업을 전용 장소 — tasks.json, 데이터베이스, 또는 backlog/ 폴더 — 에 저장합니다. 저의 방식은 다릅니다. 백로그는 제 스펙 파일의 YAML 프론트매터 (Frontmatter) 그 자체입니다. 티켓 하나당 파일 하나를 사용하며, 티켓의 상태는 문서 내의 위치가 아니라 하나의 **필드 (Field)**입니다.
---
id: PARSE-07
title: Tolerate CRLF in decklist import
...
프론트매터 아래의 모든 내용은 스펙입니다. 이는 제가 대화 중에 표현한 요구사항에 대해 LLM이 이의를 제기한 후 작성합니다. 프론트매터는 데이터입니다. 작은 CLI에 의해 소유되며, 오직 그 CLI를 통해서만 변경(Mutation)됩니다. 동일한 파일이므로 데이터가 서로 어긋날(Drift) 일이 없습니다.
이것이 중요한 이유: "완료(Done)로 이동하기"는 연산이 아닙니다. 상태 변경이 텍스트의 위치를 옮기는 것을 의미할 때, LLM(과 인간)은 문서를 망가뜨립니다. 상태를 필드로 만들면 모든 전환이 한 줄의, 멱등성(Idempotent)을 가진, 테스트 가능한 변경(Mutation)이 됩니다. 제가 보는 보드(각 스펙에 대한 GitHub 딥링크가 포함된 제 서버의 작은 웹 페이지)와 읽기 쉬운 마크다운 (Markdown) 뷰는 편집 금지 감시자(Sentinel)에 의해 잠겨 있고 일관성 테스트 (Coherence test)로 보호되는 **생성된 투영 (Generated projections)**입니다.
성숙 단계 (Maturation): 티켓별 모델, 노력 및 리뷰 깊이 결정 — 데이터로서
티켓을 맡기로 결정하는 것과 그 티켓에 대해 얼마나 깊게 고민할지 결정하는 것은 별개의 행위입니다. 어떤 에이전트(Agent)가 실행되기 전에, 티켓은 다음의 세 가지 요소(Triplet)를 통해 성숙 (Matured) 단계를 거칩니다:
model— 어떤 모델이 이를 구현할 것인가 (haiku→fable)effort— 프롬프트에 주입될 추론 깊이 (Reasoning depth)review— 리뷰 게이트 (Review gate)의 강도:none(없음),light(가벼움, 리뷰어 1명),deep(깊음, 리뷰어 3명)
사소한 이름 변경 작업에는 haiku / none / none이 할당됩니다. 되돌릴 수 없는 데이터 마이그레이션(Data migration) 작업에는 가장 유능한 모델, 최대의 추론 능력, 그리고 세 명의 리뷰어가 할당됩니다. 이 결정은 티켓과 함께 버전 관리되며, 몇 달 후에도 감사(Auditable)가 가능합니다 (matured: <date>). 그리고 구현 서브 에이전트(Implementer sub-agent)는 성숙 단계에서 결정된 모델을 정확히 실행합니다. 에이전트의 보고서는 반드시 Model used: …로 시작해야 하며, 이를 통해 사후에 결정 사항을 검증할 수 있습니다.
이는 에이전트 예산(Agent budget)에 적용된 린 사고(Lean thinking)입니다. 즉, 결함이 유출되었을 때 발생하는 비용에 비례하여 결함 탐지 비용을 지불하는 것입니다.
라이프사이클은 누군가의 기억이 아닌 훅(Hooks)에 의해 적용됩니다
todo → wip → merged → shipped 단계는 제 워크플로우(Workflow) 명령에 부착된 훅(Hooks)에 의해 설정됩니다. launch 명령은 wip를 설정하고, integration은 merged를 설정합니다 (단, feat(TICKET-ID): 커밋이 실제로 브랜치에 존재할 경우에만). deploy는 shipped를 설정합니다. 사람이나 에이전트 그 누구도 라이프사이클의 후반부를 수동으로 옮기지 않습니다. 훅은 항상 exit 0으로 종료되어야 하며 (라이프사이클 자동화가 배포를 차단해서는 안 됩니다), 정밀하게 커밋해야 합니다 (10개 이상의 병렬 워크트리(Worktrees)를 사용하는 공유 메인 체크아웃 환경을 경험하며, git add specs/ 명령이 이웃 세션의 작업까지 휩쓸어 버릴 수 있다는 것을 뼈저리게 배웠습니다).
리뷰 게이트: 아무것도 모르는 리뷰어들
이 부분은 제가 다른 곳에서는 본 적이 없는 대목입니다. 구현 서브 에이전트 (implementer sub-agent)가 (자신만의 격리된 git worktree 내에서) 작업을 마치면, 오케스트레이터 (orchestrator)는 **새로운 컨텍스트를 가진 리뷰어 (fresh-context reviewers)**를 생성합니다. 이들은 티켓 ID, 스펙 경로, 워크트리 (worktree), 커밋 SHA, 그리고 네 가지 리뷰 축 (review axes) 정보만을 전달받습니다. 그 외에는 아무것도 없습니다. 구현자가 무엇을 했는지에 대한 요약도, 어디를 살펴봐야 할지에 대한 힌트도 없습니다. 리뷰어의 컨텍스트를 오염시키는 것이 확증 편향 (confirmation bias)의 주요 경로이기 때문입니다.
사고를 겪으며 배우게 된 세 가지 세부 사항은 다음과 같습니다:
- 리뷰의 증거는 오케스트레이터에 의해 생성되며, 감사 대상이 되는 엔티티 (entity)에 의해 생성되지 않습니다. 구현자는 투입된 양을 알지 못하며, 리뷰 프롬프트 (reviewer prompt)를 결코 볼 수 없고, 리뷰에 대해 그 어떤 것도 증언할 수 없습니다. SHA는 git에서 프로그래밍 방식으로 읽어오며 (한 번은 수기로 옮겨 적은 SHA가 39자로 전달된 적이 있습니다), 리뷰 전후로
git status를 확인합니다. - 결과물(findings)은 정확히 두 가지 경로로만 나갑니다: 수정되었거나, 혹은 정당한 사유와 함께 에스컬레이션 (escalated)되거나 (스펙이 잘못됨 / 기존의 기술 부채 / 수정 사항이 통과된 테스트를 깨뜨림). "큰 문제는 아님"은 결정 사항이 될 수 없습니다.
- 리뷰어는 발견 사항(findings)만을 보고하며, 찬사는 하지 않습니다. "그 외 모든 사항은 준수함"이라고 말하는 보고서는 잘못된 자신감을 만들어냅니다. 한 번은 결함이 있는 결정을 준수한다고 선언한 보고서에 이러한 문구가 포함된 적이 있었습니다.
이 방식이 효과가 있을까요? 게시하기 전날, 저는 게시된 저장소 자체에 이 게이트 (gate)를 실행해 보았습니다. 새로운 리뷰어가 저의 영어 번역본을 프랑스어 원본과 비교하여 3개의 발견 사항을 찾아냈습니다. 여기에는 영어 버전을 따르는 사람들의 리뷰 레지스터 (review register)를 조용히 오염시켰을 오역된 카운터 (counter)도 포함되어 있었습니다. 이 게이트는 첫 번째 공개 실행에서 그 가치를 스스로 증명했습니다.
인간이 실제로 하는 일
티켓당 저의 세 가지 접점은 모두 결정(decision)이며, 결코 메커니즘(mechanics)이 아닙니다. 즉, 필요성에 동의하고(대화를 통해 — LLM이 저에게 이의를 제기한 후 스펙(spec)을 작성합니다), "성숙시키고 실행하라(mature it and run it)"(검토 강도와 함께)라고 말하며, 배포를 결정하는 것입니다. 그 사이의 모든 것 — CLI 호출, 스펙 작성, 에이전트 오케스트레이션 (agent orchestration), 통합(integration) — 은 에이전트의 몫입니다. 저는 백로그(backlog) 명령어를 한 번도 직접 입력하지 않습니다. CLI는 에이전트 지향적입니다. 결정론(determinism)은 제가 장부를 정리해서가 아니라, 에이전트에게 수동 편집 경로가 없다는 점에서 비롯됩니다.
제가 주장하는 것이 아닌 것들
- 코드를 읽는 것을 중단해야 한다는 것이 아닙니다. 이것은 저의 개인 프로젝트이자, 저의 리스크 프로필(risk profile)에 기반한 저의 베팅입니다.
- 이것이 하나의 제품이라는 것이 아닙니다. 이것은 작동하는 설정에서 추출한 참조 구현체(reference implementation)입니다. 읽어보고, 아이디어를 훔치고, 조각들을 적응시키세요. README에는 정확히 어떤 조각들이 잘 이식되는지에 대한 섹션이 있습니다.
- 시스템이 완성되었다는 것이 아닙니다. 가장 큰 미결 과제는 README에 솔직하게 적혀 있습니다. 시스템의 모든 규칙은 제가 습관적으로 수행한 장애 회고(incident retro)에서 나왔습니다. 즉, 기술이 아직 스스로 개선 루프를 트리거하는 단계에는 이르지 못했습니다.
만약 여러분이 매일 코딩 에이전트를 실행하고 있음에도 불구하고, 에이전트가 "무언가를 완료(Done)로 이동"할 때마다 엉망이 되는 마크다운(markdown) 할 일 목록을 백로그로 사용하고 있다면, 데이터 모델만으로도 읽어볼 가치가 있을 것입니다:
github.com/giboulz/backlog-as-data.
댓글을 통해 무엇이든 기쁘게 답변해 드리겠습니다. 코드를 전혀 읽지 않겠다는 베팅이 저를 파멸시켰는지 여부를 포함해서 말이죠.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기