Writing Specs
Manage spec files in specs/ with conflict detection and a concise final report.
Hard Rules
- Template-driven. 네 절(Purpose·Requirements·Approach·Verification)은 templates/spec-template.md 의 순서와 이름 그대로 반드시 있고 각 절의 분량 제한을 지킨다. 앱의 사용자 기능 스펙이면
## 이벤트 맵과## 판정도 채운다(사용자 표준). 그 뒤에 번호 붙인 상세 절을 더하는 것은 다른 세션이 스펙만 보고 구현해야 할 때만 허용하고, 그 경우 Phase 5 보고에 "인계용 상세 절 N개 추가"라고 적는다. - Search before write. Never create or update a spec without running Phase 1.
- One spec = one task. A request spanning multiple concerns becomes multiple specs.
- Never auto-modify outdated specs. Propose updates in the report; never apply.
- 이 스킬은 fork 로 돌아 사용자에게 직접 물을 수 없다. Phase 2 결정은 non-interactive.md 기본값을 적용하고, 되돌릴 수 없는 갈림길(기존 스펙 덮어쓰기)만 부모 세션에
SendMessage로 한 번 묶어 확인한 뒤 진행한다. 포크 19회 실측에서AskUserQuestion호출은 0회였다 (skills-internal#25). - Always end with the report. No silent operations.
Directory Rules
- Resolve the specs root first. If the user's global rules (e.g. a
work-routingrule) map this repo's 2nd-class docs to an external docs repo, the specs root is<external-root>/<repo-name>/specs/— not repo-localspecs/. Otherwise it isspecs/at the project root. Every rule below (phase dirs, naming, auto-pick, search) applies relative to the resolved root. The directory-structure SSOT ischeck-doc-structure/golden_manifest.py(shared withwriting-tasks,scaffold-new-app,reconcile-docs); filenames are lowercase-with-hyphens. - A leftover repo-local
specs/is read-only legacy once the external root is in play: include it in the Phase 1 search, write new specs only to the external root, and note the split in the report. Never bulk-move it. Decision records (docs/adr/,CONTEXT.md) always stay in-repo. - Default path:
specs/phase-N-slug/NN-name.md— one phase dir per milestone; the spec→work handoff is carried by theissue:backlink, not by a parallel directory.phase-N: 1-indexed milestone number, monotonically increasing across the project, never reset.slug: short human-readable milestone name. Required — never barephase-1. Example:phase-1-foundation.NN: 2-digit index within the phase, starting at01. Example:specs/phase-1-foundation/01-jwt-authentication.md. Never use other prefixes (e.g.T1-).- phase 디렉토리 안에는
NN-slug.md스펙 파일만 둔다. 디자인 캔버스, 이미지, 데이터 같은 자산은<doc-root>/<repo>/assets/아래로 보내고 스펙 본문에서 경로로 참조한다.
- Flat alternatives (
specs/{name}.md,specs/{subdir}/{name}.md) only for projects with no phase structure. Max 1-depth subdirectories; create subdirs or new phases only on explicit request, or when 5+ specs share a clear category. - Decision records do not live under
specs/. Durable trade-offs →docs/adr/; lightweight decisions →docs/decisions.md. Never createdecisions.md(or00-overview.md) inside aspecs/phase-*dir.
Worked layout example (a phase with multiple specs; one concern per spec): examples.md.
Auto-pick phase + NN when the user does not specify:
Glob specs/phase-*/— no phases → startphase-1-<slug>(derive a short slug from the spec topic).- Use the highest existing phase dir, unless the user signals a new milestone → create
phase-{N+1}-<slug>(slug 가 불분명하면 부모 세션에 묻는다). Glob specs/phase-N-*/*.md— new spec gets highest existingNN+ 1, zero-padded.- Spec belongs to a clearly different milestone? 부모 세션에 phase number + slug 를 묻고, 답이 없으면 non-interactive 기본값(활성 phase 에
NN+1)으로 간다.
Phase 0 — Resolve the input
| Argument shape | Resolution |
|---|---|
| brain-storm/**.md path | Read it. Summary / Motivation / Proposed Approach become the spec source; the idea title becomes the spec H1. |
| Any other markdown path | Read it; its content is the spec source. |
| Bare title or keyword | Glob brain-storm/**/*.md, match H1 (case-insensitive) then filename slug. One match → use it. Multiple → non-interactive 기본값(H1 정확 일치, 없으면 파일명 순 첫 번째). Zero → treat as freestyle. |
| Freestyle text, or no argument | Use conversation context (fork 는 부모 컨텍스트를 물려받는다). Empty → 부모 세션에 SendMessage 로 소스를 요청하고, 답이 없으면 non-interactive 기본값대로 거부 보고. |
From a brain-storm source: Summary seeds Purpose; Proposed Approach bullets seed Requirements (prune to 3-5 concrete items) and the prose Approach. Wireframe and Open Questions are NOT copied — list them under "Carried over" in the report.
- [ ] Input resolved to exactly one source
Phase 1 — Search (mandatory)
Glob specs/**/*.mdto list all spec files.- Extract 3-5 key nouns from the request (skip generic words like "system", "feature", "add", "update").
Grepeach keyword across the found specs. Bashgrep을 대신 쓰지 않는다 — 사용자 셸의 grep 이 ugrep 이라--include가 조용히 실패한 회차가 있다.- Classify: Exact match (same topic) · Related (2+ shared keywords or adjacent topic) · Outdated (references files that no longer exist — verify with
Glob).
No specs/ directory yet → skip to Phase 3.
Phase 2 — Decide
- No related specs → proceed to create.
- Exact match → ask: "이미 동일한 스펙이 존재합니다:
{path}. 업데이트할까요, 새로 생성할까요, 아니면 취소할까요?" - Related specs → show the list and ask: "관련 스펙이 발견되었습니다. 어떻게 진행할까요?"
- Outdated specs → "다음 스펙이 outdated 상태입니다 (참조 파일 없음). 함께 업데이트할까요?"
위 질문 문구는 부모 세션에 SendMessage 로 한 번에 묶어 보내고(질문 4개 이하), 답이 없으면 non-interactive.md 기본값으로 진행하며 Phase 5 보고에 기본값 적용 사실을 적는다.
Phase 3 — Write
- Read templates/spec-template.md and fill it strictly.
- Resolve the destination with the auto-pick rules above; write under
specs/. - Set the
statusfrontmatter —plannedfor a new spec; on update, reflect the real implementation state (wip/done/held/legacy). This is the only field the project roadmap is derived from, so keep it honest. frontmatter 는status와issue두 필드뿐이다.title,created같은 필드를 더하지 않고,issue:는 항상<owner>/<repo>#N정식 형 하나만 쓴다. 기존 스펙 파일을 형식 표본으로 삼지 말고 항상 템플릿을 읽는다 (주석 복사 13건·추가 필드 3건·짧은 형 8건 실측). - Leave
issue:empty on creation. It records the tracking issue (<owner>/<repo>#N) and is written bywriting-taskswhen the spec is decomposed — a spec that has leftplannedwithout anissue:is untracked work, and the structure detector flags it. - On update, preserve every section the user did not ask to change.
Never write a roadmap or index file (specs/README.md, STATUS.md, etc.). The phase roadmap is derived on demand from status frontmatter — so it never drifts and never collides across worktrees:
python3 "$(readlink -f ~/.claude/skills/writing-specs)/../check-doc-structure/check_doc_structure.py" <repo> --roadmap
Section limits (enforced, not suggested): Purpose 1-2 sentences, no bullets · Requirements 3-5 bullets, one concrete requirement each · Approach 2-5 sentences, no code · Verification 2-5 testable bullets.
Phase 4 — Outdated cleanup
For each outdated spec found in Phase 1: identify the broken references, propose specific updates, apply only after user confirmation. Never auto-modify.
Phase 5 — Report
Fill templates/report-template.md and output it as the final message: Action (Created/Updated), File (relative path), Title (spec H1), Search Results (each related/outdated spec, or "No related specs found"), Changes ("New spec created" or update summary), Next Steps (one actionable suggestion).
Non-interactive mode
이 스킬은 항상 fork 라 사용자에게 직접 묻지 못한다. 결정은 non-interactive.md 기본값을 적용하고, 기본값으로 처리한 결정을 전부 Phase 5 보고에 적는다. 스펙을 쓰기 전에 확정이 필요한 항목이 있으면 부모 세션에 SendMessage 로 한 번만 묶어 물어라(질문 4개 이하). 작성 도중 부모가 추가 결정을 보내면 이미 쓴 절을 고쳐 반영하고, 반영한 항목을 보고의 "부모 전달 반영" 줄에 적는다.
호출자에게
이 스킬은 fork 로 백그라운드에서 돈다. 부르고 나서 완료 알림을 기다리지 않고 같은 주제의 이슈 생성이나 구현을 진행하면, 스펙이 도착해도 쓰이지 않는다(포크 17회 중 완주하고도 보고가 묻힌 발동 4회 실측). 스펙이 뒤 작업의 입력이라면 알림을 받고 파일을 확인한 뒤에 다음 단계로 넘어간다. 부모가 이미 스펙에 필요한 맥락을 다 들고 있으면 fork 를 띄우지 말고 이 문서의 Phase 1부터 5까지를 부모 세션에서 그대로 실행한다 — 띄웠다가 중단하면 절 제한과 보고 규격이 함께 사라진다.