OpenSpec 프로젝트별 커스터마이징: 설정부터 워크플로우 확장까지
OpenSpec으로 시작해 본 스펙 주도 개발에는 OpenSpec 도입기를, OpenSpec에 검증 체계를 얹은 이야기에는 검증 체계를 얹은 과정을 다뤘다. 모두 OpenSpec을 프로젝트에 맞게 커스터마이징하는 과정을 담았지만, 설정이 AI 커맨드/스킬 파일과 AGENTS.md에 흩어져 있었고 일부는 일관된 기준 없이 그때그때 추가된 상태였다. OpenSpec 문서를 기준으로 설정을 처음부터 다시 정리한다.
OpenSpec 기본 모드
OpenSpec은 스펙 기반 개발 워크플로우를 CLI로 관리하는 도구이다. 개발 작업을 시작하기 전에 어떤 기획/설계 문서를 어떤 순서로 작성할지 정의하고, AI가 그 문서를 작성할 때 가이드를 제공한다.
스키마는 어떤 문서를 어떤 순서로 작성할지 정의한 워크플로우 템플릿이다. 기본 내장 스키마는 spec-driven 하나이며, 필요하면 이를 프로젝트 로컬 스키마로 fork해서 쓸 수 있다. 기본 스키마의 artifact 흐름은 이렇다.
proposal → specs → design → tasks
이 artifact가 갖춰지면 구현에 들어가고, 완료 후 archive로 마감한다. 구현과 archive는 스키마가 정의하는 artifact가 아니라 그 이후의 실행/종료 단계다.
커스터마이징 방법
OpenSpec은 프로젝트에 맞게 커스터마이징해서 쓸 수 있도록 설계되어 있다. 공식 문서에서는 이를 Project Config, Custom Schemas, Global Overrides 3단계로 나누어 설명한다. 나는 Project Config와 AI 커맨드/스킬 수정을 사용하고, spec 템플릿 수정은 기본 spec-driven을 프로젝트 로컬 스키마로 fork하는 방식으로 정리했다. 즉 작업 순서 자체는 spec-driven을 따르되, 템플릿 커스터마이징은 fork한 스키마에서 관리한다.
config.yaml — Project Config
openspec/config.yaml. 가장 기본적인 커스터마이징 방법이다. 3개 필드를 지원한다:
| 필드 | 역할 |
|---|---|
schema |
사용할 스키마 지정 |
context |
모든 문서 작성 시 AI에게 전달되는 프로젝트 맥락 |
rules |
특정 문서 작성 시에만 적용되는 규칙 (문서별로 지정 가능) |
openspec instructions 실행 시 이 내용이 artifact 생성 instruction에 포함된다. CLAUDE.md(Claude Code 전용)와 달리, artifact 생성 경로에서는 도구에 관계없이 동일한 맥락이 주입된다.
스키마 fork와 spec 템플릿 수정
공식 문서 기준으로 템플릿 구조를 바꾸고 싶으면 먼저 내장 스키마를 fork한다. 예를 들어:
openspec schema fork spec-driven my-spec-driven
그러면 openspec/schemas/my-spec-driven/ 아래에 schema.yaml과 templates/*.md가 생성된다. 여기서 templates/spec.md를 수정해서 Requirement/Scenario 구조를 프로젝트에 맞게 바꾼다.
즉 템플릿만 바꾸고 싶더라도 공식 경로는 단일 파일 오버라이드가 아니라, fork한 스키마 내부 템플릿을 수정하는 방식이다. 그리고 openspec/config.yaml의 schema를 my-spec-driven으로 지정해 그 스키마를 기본값으로 사용한다.
AI 커맨드/스킬 파일
- Claude Code:
.claude/commands/opsx/*.md - Codex:
.codex/skills/openspec-*/SKILL.md
openspec init --tools가 기본 파일을 생성한다. 이 파일들을 수정하거나 새로 추가해서 워크플로우를 커스터마이징한다.
AGENTS.md
프로젝트 루트의 AGENTS.md. 모든 AI 도구가 공통으로 따르는 규칙 문서이다. OpenSpec 자체가 관리하는 파일은 아니지만, 도구 간 공통 규칙을 여기에 문서화해서 일관성을 유지한다.
바꾼 것들
프로젝트 맥락 주입
config.yaml의 context에 프로젝트 정보를 추가해서 AI가 문서를 작성할 때 참고하도록 했다.
- 기술 스택 (React 19, TypeScript 5.9, Tailwind CSS v4 등)
- 폴더 구조 (
features/<feature>/{api,model,ui,lib}) - 문서 언어 규칙: 한국어로 작성하되, spec.md의 Requirement 첫 문장에 SHALL/MUST를 영어 그대로 포함
SHALL/MUST 규칙을 넣는 이유
spec.md는 "시스템이 무엇을 해야 하는지" 정의하는 요구사항 문서이고, Requirement는 그 안의 개별 요구사항 항목이다. 이렇게 하는 이유는 검증 가능한 계약으로 만들기 위해서다. Requirement 첫 문장에 SHALL/MUST가 들어가면 "이건 구현 희망사항"이 아니라 "만족해야 하는 규칙"이라는 신호가 된다.
예를 들어, Requirement 첫 문장에서 SHALL을 명시하면:
### Requirement: 사용자 데이터 내보내기
시스템은 사용자가 계정 화면에서 자신의 데이터를 CSV 형식으로 내보낼 수 있어야 한다. SHALL.
#### Scenario: 정상 내보내기
- **WHEN** 사용자가 계정 화면에서 "내보내기" 버튼을 클릭하면
- **THEN** UTF-8 인코딩의 CSV 파일 다운로드가 시작된다
이 예시는 SHALL/MUST 규칙을 설명하기 위한 최소 예시다. Scenario ID 규칙은 다음 섹션에서 다룬다.
OpenSpec의 openspec validate가 Requirement 본문에서 SHALL/MUST 키워드를 검사해서 스펙 품질을 검증한다. openspec validate는 CLI가 하는 자동 검사이고, AI가 아니라 키워드 유무를 기계적으로 체크한다. 내용의 품질은 작성자의 몫이다.
Scenario ID 기반 검증 추적
spec.md의 시나리오에 고유 ID를 붙이고, tasks.md 검증 항목에서 그 ID를 참조한다. 보통 spec에는 "무엇이 성립해야 하는가"를, tasks에는 "무엇을 구현하고 어떤 방식으로 검증할 것인가"를 적는다. 둘 사이 연결이 없으면 시간이 지난 뒤 "이 테스트가 어떤 요구사항을 검증하는지"를 추적하기 어려워진다. 그래서 SCN-AUTH-PROD-REDIRECT 같은 안정적인 ID를 붙여 두고, task나 테스트가 그 ID를 참조하게 한다.
예시:
spec.md:
#### Scenario: CSV 내보내기 요청
- **ID** SCN-EXPORT-CSV-REQUEST
- **WHEN** 사용자가 계정 화면에서 "내보내기" 버튼을 클릭하면
- **THEN** CSV 파일 다운로드가 시작된다
#### Scenario: 내보낼 데이터가 없는 계정
- **ID** SCN-EXPORT-CSV-EMPTY
- **WHEN** 내보낼 데이터가 없는 계정에서 내보내기를 실행하면
- **THEN** CSV 파일은 생성하지 않고 "내보낼 데이터가 없습니다" 안내를 표시한다
tasks.md:
- [ ] 6.1 SCN-EXPORT-CSV-REQUEST — export.test.ts 다운로드 시작 유닛 테스트 추가
- [ ] 6.2 SCN-EXPORT-CSV-REQUEST — 계정 화면 "내보내기" 버튼 E2E 테스트 추가
- [ ] 6.3 SCN-EXPORT-CSV-EMPTY — 데이터 없음 상태에서 안내 메시지 표시 유닛 테스트 추가
이 규칙은 다음 파일들에 나누어 반영했다:
- config.yaml
context: "왜, 언제 Scenario ID를 쓰라"는 가이드 - fork한 스키마의
templates/spec.md: "어디에, 어떤 형식으로 쓰라"는 구조와 예시 - AGENTS.md: tasks.md 검증 항목에서 Scenario ID를 참조하는 규칙
현재는 가이드와 템플릿 중심으로 두고, 이를 따르지 않아도 즉시 에러가 나지는 않는다. 참조 누락 검사나 검증 실행 추적처럼 자동화할 수 있는 영역은 보이지만, 무엇을 먼저 자동화해야 효과적인지는 사례를 조금 더 쌓은 뒤 결정하려 한다.
구현과 검증 분리
tasks.md는 구현 작업과 검증 작업 섹션으로 나눠 작성하게 했다. 구현 작업만 나열하면 코드는 작성했지만 동작을 확인했는지 알 수 없다. 검증 항목을 별도로 두면 구현 완료와 검증 완료를 구분해서 추적할 수 있다.
검증 항목은 성격에 따라
- CI 체크: 빌드, 린트
- 자동 테스트: 유닛 테스트, E2E 테스트
- 수동 확인: 실제 기기에서 확인
이렇게 나누고, 구현이 끝난 뒤 "뭘 확인해야 완료인지"를 tasks.md에 명시한다.
예시:
## 1~5. 구현 작업
- [x] 1.1 모바일 safe-area 대응 유틸 적용 (env(safe-area-inset-*))
- [x] 3.2 iOS WebView safe-area 상단 여백 처리
- [x] 5.1 SurveyLayout 3영역 레이아웃 재구성
## 6. 검증
**기본 검증**
- [x] 6.1 pnpm build 통과 확인
- [x] 6.2 pnpm lint 통과 확인
**변경 검증**
- [x] 6.3 기존 survey 관련 테스트 통과 확인
- [ ] 6.4 iOS WebView에서 상단 헤더가 겹치지 않는지 확인
- [ ] 6.5 iOS 키보드 오픈 시 footer 노출 확인
GRAY-AREAS — 미확정 사항 관리
design.md에 설계 단계에서 아직 결정되지 않은 것들을 명시하는 섹션이다. 구현에 들어가기 전에 "이건 아직 모른다, 확인이 필요하다"를 정리해서 잘못된 가정으로 구현하는 걸 방지한다.
항상 스펙에 고정으로 넣어야 하는 항목은 아니지만, 여러 요소가 함께 작동할 때 드러나는 문제를 미리 적어 두는 데 유용하다. 예를 들어 SurveyLayout을 3영역(header/content/footer)으로 재설계할 때:
### GA-1 하단 입력 UI + 키보드 + footer 겹침
- type: ui
- impact: 설문 마지막 문항이 텍스트 입력인데 화면 하단에 있으면,
키보드가 올라오면서 입력칸이 footer와 키보드 사이에 끼어
보이지 않을 수 있다
- options:
- A) 키보드 열릴 때 입력칸으로 자동 스크롤
- B) 키보드 열릴 때 footer를 숨김
- C) host(Flutter)가 viewport를 조정해서 웹은 신경 안 씀
- needed_info: Flutter WebView가 키보드 열릴 때 viewport를 줄여주는지 확인 필요
- owner: 앱팀
- deadline: 2026-03-20
- status: open
이건 3영역 레이아웃 + 하단 고정 footer + 모바일 키보드가 조합됐을 때만 생기는 문제라, 설계 단계에서 구조를 그려봐야 보인다.
GRAY-AREAS(미확정 사항)를 6개 카테고리로 분류한다.
- ui: 레이아웃, 밀도, 인터랙션, 로딩/빈 화면/에러 상태
- integration: API 계약, 인증/세션, 에러/재시도/버전 처리
- state-form: 스토어, 쿼리 키, 상태 전이
- shared: 여러 feature에 걸친 공유 컴포넌트 영향
- platform: CI, 환경, 빌드, 툴링
- content: 문구, 정보 구조, 톤/깊이/흐름
config.yaml의 rules.design에 GRAY-AREAS를 이 6개 카테고리로 분류하도록 유도해서, change마다 분류 기준이 달라지는 걸 방지한다.
discuss 워크플로우 추가
기본 OpenSpec에 없는 /opsx:discuss 커맨드를 추가했다. design.md의 GRAY-AREAS 섹션만 집중적으로 관리하는 워크플로우다. 다른 커맨드와 마찬가지로 Claude Code와 Codex 양쪽에 진입점을 만들었다(아래 이중 진입점 참조).
explore 모드 커스터마이징
기본 생성된 explore 스킬에 "생각 파트너" 철학을 추가했다:
- 코드 작성 금지 — 탐색과 토론만 허용
- ASCII 다이어그램 적극 사용
- OpenSpec 문서를 자연스럽게 참조하면서 대화
- 결론을 강요하지 않고, 탐색 자체가 가치
archive 계약 통일
기본 생성된 archive 스킬에서 Claude Code와 Codex의 동작이 달랐다. 이를 통일했다:
- 양쪽 모두
openspec archive "<change-id>"로 실행 - spec 업데이트가 기본 동작. 생략은
--skip-specs로 명시적 예외일 때만 허용 - archive는 수동
mv대신openspec archive명령으로만 수행
이중 진입점 — Claude Code / Codex 동일 계약
같은 워크플로우를 Claude Code와 Codex에서 모두 실행할 수 있도록 진입점을 분리했다:
- Claude Code:
.claude/commands/opsx/*.md—/opsx:*슬래시 커맨드로 실행 - Codex:
.codex/skills/openspec-*/SKILL.md—opsx:*로 실행 (슬래시 없음) - 공통 규칙:
AGENTS.md에 도구별 매핑과 운영 규칙 문서화
Claude Code는 명령어를 입력하면 정해진 커맨드가 바로 실행되지만, Codex는 문맥을 보고 어떤 스킬을 쓸지 판단한다. AGENTS.md에 매핑 테이블을 명시해서 올바른 스킬로 연결되도록 했다.
다음에 할 것들
여기까지 커스터마이징하고 실제로 써보면서 두 가지 문제가 있었다.
스펙 변경이 너무 자주 일어난다
스펙 작성에 많은 시간을 쓰고 있다. validate도 돌리고, discuss 스킬로 gray area도 검토한다. 그런데도 구현, 검증, 리뷰 과정에서 스펙을 고치는 일이 반복된다. 현재 워크플로우에 구멍이 있다는 뜻이다.
원인 중 하나는 한 PR에서 너무 많은 일을 하려는 습관이다. AI가 실행을 빠르게 해주니까 범위를 넓히게 되고, 넓힌 만큼 놓치는 부분이 생긴다. 스펙 작성 시 시나리오가 일정 수를 넘으면 경고하거나 change를 나누도록 유도하는 장치가 필요하다.
스펙 리뷰 과정을 개선해야 한다
구현 단계에서 스펙 수정이 발생한다는 건, 리뷰와 discuss 단계에서 불확실성이 충분히 해소되지 않았다는 뜻이다. 현재의 리뷰 방식만으로는 부족해서 스펙 리뷰와 discuss 워크플로우 자체를 개선할 계획이다. 다만 어떤 검증 방식이 효과적인지는 아직 확신이 없어서, 스펙 정의 과정의 로그를 쌓으면서 경험과 학습을 함께 축적하고 그 안에서 문제를 찾아보려고 한다.
(2026-03-24 추가) 이거 살펴보니, OpenSpec은 현재 아티팩트만 보면 문제 신호를 찾을 수 있다. proposal, design, tasks, spec가 서로 같은 방향을 가리키는지 비교하면 범위 확장, 설계 누락, 검증 누락 같은 이상 징후가 드러난다. 현재 문서에서 지금 어디가 어긋나 있는지를 볼 수 있고, 과거 문서로부터는 그 어긋남이 언제, 왜 생겼는지를 볼 수 있다.
내 문제는 이렇다. 여러 문제를 한 change에 같이 넣고 있으며, 중요한 전제를 문서에 충분히 드러내지 않은 채 진행하고 있다. 가정의 전제를 명확하게 하지 않으면, 나중에 실제 동작이 다를 때 스펙이 틀린 것처럼 보인다. 사실은 스펙이 틀린 게 아니라, 숨겨진 가정이 깨진 것이다. 이것부터 고쳐야겠다.