형식과 규칙 문서입니다. 에이전트에게 일을 맡겨 쓰는 데는 읽지 않아도 되고, 기록을 다루는 도구를 만들거나 정확한 동작을 확인할 때 봅니다. 처음이라면 Jaekit 이해하기부터 읽으세요.
이 페이지
계약: 목표 문서 (Spec → Seal)
이 문서는 Spec이 만들고 Seal이 읽는 목표 문서에 대한 canonical 정의다. 두 제품 사이의 인터페이스는 이것 하나다. Seal이 만드는 실행 묶음은 bundle.md에 있다.
원칙은 다음과 같다.
- 목표 문서는 일반 Markdown이다. Seal 없이도 사람과 아무 에이전트가 읽고 쓸 수 있다.
- Seal을 모른다. Seal의 명령, 파일 이름, 상태, 기록 형식을 쓰거나 참조하지 않는다.
- 실행 상태를 담지 않는다. 진행, 재시도, 현재 task, 검증 결과는 실행 묶음에 있다.
- 누가 썼는지 묻지 않는다. Seal은 Spec이 직접 만들지 않은 문서라도 이 계약을 따르면 받는다. 사람이 직접 쓴 목표 문서도 같다. 에이전트가 부른 Spec이 쓴 문서의
Proposed-By: agent줄(§2.6)도 이 원칙을 바꾸지 않는다.
두 제품을 이어 쓰는 권한은 목표 문서가 저장된 뒤 사용자가 따로 보내는 구현 요청이다. Status: Ready는 실행 권한이 아니다. Spec을 부른 요청은 목표 문서만 요구한다. 요청이 "추가해줘", "고쳐줘" 같은 작업 지시나 버그 제보로 표현되어도, 같은 대화 앞부분에 구현 요청이 있었어도 같다. Spec을 부른 요청이나 질문에 답하거나 수정을 요청하는 답이 구현까지 요청해도 Spec은 목표 문서를 쓴 뒤 멈춘다. 목표 문서를 쓴 뒤 Spec이 따로 묻는 구현 여부 질문에 대한 사용자의 긍정 답은 그 자체가 따로 보낸 구현 요청이다(§6.7). 목표 문서 밖에는 저장소 파일뿐 아니라 브랜치, PR과 리뷰 스레드, 릴리스와 배포도 들어간다.
1. 위치와 문서 집합
docs/specs/<goal>/
SPEC.md 진입점(필수)
<보조 문서>.md SPEC.md가 상대 경로로 링크한 문서(선택). 예: PRD.md, 외부 계약, 데이터 모델
amendments/<이름>.md 수정안 문서(선택, §6.6). 목표 문서 집합에 들지 않는다
목표 문서 집합은 SPEC.md와, SPEC.md에서 링크를 따라 닿는 goal 디렉토리 안의 Markdown 문서다. 링크된 문서가 다시 링크한 문서도 포함한다. 실행 묶음 파일(PLAN.md, REVIEW.md, PROGRESS.md, tasks/, runs.jsonl)은 링크되어도 들어가지 않는다. amendments/ 아래의 수정안 문서도 링크되어도 목표 문서 집합에 들지 않는다. 코드 블록 안의 링크는 따라가지 않는다.
Seal은 이 집합 전체의 digest를 목표 digest로 쓴다(run-record.md §2.1의 spec_digest). 목표 digest는 문서를 저장소 기준 경로 순으로 놓고, 각 문서마다 경로, NUL 바이트, 내용의 sha256(16진수), 줄바꿈을 이은 바이트열의 sha256(16진수)이다.
목표 문서는 사용자 소유다. Spec이 작성을 돕고, 바꾸는 것은 사용자의 결정이다.
2. SPEC.md의 내용
| 절 | 내용 |
|---|---|
Status: 한 줄 |
Draft | Ready. Draft는 결과를 바꾸는 미결정이 남았다는 뜻이고 Ready는 남지 않았다는 뜻이다. 문서의 설명이며 어느 쪽도 실행 권한이 아니다. |
Criteria-Format: 한 줄 |
새 문서는 nested/1을 명시한다. 표시가 없는 기존 문서는 종전 목록 해석을 유지한다(§2.1). |
Assurance: 한 줄 |
보증 등급 light | standard | strict. Spec이 제안하고 사용자가 확인한다. 줄이 없는 기존 문서는 지금 규칙을 따른다(§2.5). |
Proposed-By: 한 줄 |
에이전트가 부른 Spec이 쓴 새 문서에만 Proposed-By: agent를 둔다(§2.6). |
| 목표 | 무엇을 왜 바꾸는가 |
| 범위와 비목표 | 바뀌는 것과 바뀌지 않는 것. 기존 코드를 바꾸는 목표는 지금 동작을 근거 파일 경로와 함께 사실로 적고, 바뀌는 것과 그대로인 것을 구분한다(§6.3) |
| 사용 시나리오 | (기능을 바꾸는 목표) 대표 사용 흐름, 관련 실패·경계 상황, 유지할 동작과 각각이 닿는 조건(§2.3) |
| 제약 | 지켜야 할 호환성, 권한, 데이터, 성능 규칙 |
| 결정과 근거 | 결과를 바꾸는 결정, 검토한 선택지, 고른 이유, 출처(§6.2) |
## Acceptance Criteria |
완료 조건(§2.1) |
| 관련 맥락 | 이전 대화를 모르는 실행자가 읽어야 할 코드·문서·외부 자료 |
## Open Decisions |
남은 결정. 없으면 "없음" |
## 작업 분해 제안 |
(선택) §2.2 |
깊이는 변경 위험도에 비례한다. 작은 변경에 빈 절을 강제하지 않는다. 변경 규모에 맞는 템플릿(Small Change, Backend Feature, Cross-module Feature, Product)을 쓸 수 있다. 작은 수정의 사용 시나리오는 한두 문장이면 된다.
2.1 완료 조건과 ID
Criteria-Format: nested/1
## Acceptance Criteria
- **AC-1** 빈 파일이면 `textstats count`가 `0 0 0`을 출력하고 0으로 끝난다.
- 세 숫자는 줄·단어·바이트 수다.
입력이 비어 있어도 출력 한 줄이 존재한다.
- **AC-2** 비어 있지 않은 파일의 출력 형식은 바뀌지 않는다.
- **AC-3** (선택) `--json`이면 같은 값을 JSON으로 출력한다.
- 조건마다
AC-nID를 붙인다. ID는 goal 안에서 안정적이다. 조건을 지우면 그 번호를 다시 쓰지 않는다. 문장을 고쳐도 ID는 유지한다. 바뀐 사실은 digest로 드러난다. - 조건은 기본적으로 필수다. 선택 조건은 ID 뒤에
(선택)을 붙인다. 필수 여부는 목표 문서만 정한다. - 조건은 관측할 수 있는 결과로 쓴다. 구현 방법과 검증 명령은 쓰지 않는다. 그것은 실행자가 정한다.
- 조건은 구현 단위보다 사용자가 겪는 결과에 가깝게 쓴다. 예를 들어 "저장 API를 구현한다"보다 "사용자가 저장한 내용을 새로 조회해도 같은 값이 나타난다"가 목표에 가깝다.
목록 형식 선택. 새 SPEC.md는 첫 ## 절 이전, Status: 근처에 Criteria-Format: nested/1을 한 줄 적는다. 선택 표시는 들여쓰지 않으며 fenced 코드 블록 안의 예시는 선택으로 읽지 않는다. 첫 ## 절 이전에 공백이나 탭으로 들여쓴 선택 표시는 criteria_format 오류다. 절 안의 들여쓴 본문에 인용된 표시는 선택으로 읽지 않으며 기존 본문 해석을 유지한다. 중복 표시는 criteria_format_duplicate, 지원하지 않는 값·빈 값·잘못된 위치는 criteria_format 오류다. 알 수 없는 형식을 종전 해석으로 대신 읽지 않는다.
형식 선택을 찾을 때는 코드 블록을 연 backtick 또는 tilde와 같은 문자가 적어도 같은 수만큼 이어지고 뒤에는 공백·탭만 있는 줄을 닫는 fence로 본다. 더 짧은 fence나 뒤에 일반 텍스트가 붙은 줄은 예제를 끝내지 않는다. 이 경계 판별은 선택 표시 탐색에만 적용하며 기존 AC 본문이나 실행 묶음의 Markdown 해석을 바꾸지 않는다.
nested/1의 ## Acceptance Criteria 목록은 다음과 같이 읽는다.
- 최상위 목록은
-,*,+와 공백으로 시작한다. 첫 목록의 들여쓰기(0~3칸)가 그 절의 최상위 수준이다. 같은 수준의 다음 목록은 새 조건이며 ID가 필요하다. 최상위 ID 누락·중복은 계속 오류다. - 부모 목록 기호 뒤 첫 내용이 시작하는 열 이상으로 들여쓴 줄은 본문이다.
- AC-1 ...처럼 기호 뒤 공백이 한 칸이면 본문 시작은 두 칸이다. 따라서 부모가 0칸이면 2·3·4칸의 하위 목록을 모두 본문으로 읽는다. 부모가 두 칸 들여써졌다면 본문은 네 칸부터다. 기호 뒤 공백을 더 쓰면 본문 시작 열도 그만큼 늘어난다. - 본문의 목록 기호와 내용은 보존하며, 그 안의
AC-n표기는 독립 조건을 만들지 않는다. 각 내용 줄의 앞뒤 공백은 제거한다. 일반 문장도 같은 본문 들여쓰기 규칙을 따른다. - 빈 줄 뒤에도 본문 시작 열 이상으로 들여쓴 줄은 같은 조건에 속한다. 연결되는 문단 사이 빈 줄은 한 개로 정규화하고, 끝의 빈 줄은 버린다. 같은 수준의 다음 목록, 들여쓰지 않은 일반 문단·하위 제목, 절의 끝은 본문 연결을 끝낸다.
- 최상위와 본문 시작 열 사이의 애매한 들여쓰기, 부모 없는 들여쓴 내용, 첫 목록의 4칸 이상 들여쓰기, 들여쓰기나 목록 기호 뒤 구분 공백에 사용한 탭은
criterion_indentation으로 위치와 이유를 알려 준다. 조용히 버리거나 자동 변환하지 않는다. fenced 코드 블록은 기존과 같이 읽지 않는다.
기존 문서 보존. 선택 표시가 없으면 기존 목록 해석을 그대로 쓴다. 0~3칸 목록은 독립 항목이며, 이어지는 들여쓴 문장은 앞뒤 공백을 제거해 본문에 넣고 빈 줄에서 연결을 끊는다. 기존 탭 continuation도 유지한다. 새 목록 규칙은 목표의 AC에만 적용하며 실행 입력의 범위·작업 명령·예산 목록은 바꾸지 않는다.
기존 문서를 편집한다는 이유만으로 선택 표시를 추가하지 않는다. 사용자가 새 형식으로 전환하기로 결정했을 때만 명시한다. 이 줄을 더하거나 바꾸면 원문과 목표 digest가 달라지므로 이전 검사 근거·수동 확인을 새 해석의 근거로 묵시적으로 재사용할 수 없다. 원문이나 과거 기록을 자동으로 고쳐 호환성을 맞추지 않는다.
2.2 작업 분해 제안 (선택)
| 열 | 의미 |
|---|---|
ID |
W1, W2… goal 안에서 안정적이다. |
결과 |
완성되면 존재하는 결과(동작, 데이터 변경, 문서). 절차나 단계가 아니다. |
조건 |
이 결과가 근거로 삼는 AC-n |
의존 |
먼저 있어야 하는 결과의 W ID |
- 제안이다. 실행자는 그대로 쓰거나, 나누거나, 합칠 수 있다. 다르게 계획하면 실행 묶음에 이유를 남긴다(bundle.md §2.3).
- 모든 제안은 조건 하나 이상에 근거한다. 의존에 순환이 없다.
- 순서나 방법을 지시하지 않는다. 실행 상태(진행 중, 끝남)를 적지 않는다.
2.3 사용 시나리오와 완료 조건
기능을 바꾸는 목표는 사용자가 기대한 흐름이 완료 조건에 모두 들어갔는지 확인할 수 있게 사용 시나리오를 쓴다. 각 항목은 그것을 확인하는 조건 ID에 닿는다.
## 사용 시나리오
| 종류 | 시나리오 | 조건 |
| --- | --- | --- |
| 흐름 | 사용자가 빈 파일을 넘기면 세 숫자가 모두 0으로 나온다 | AC-1 |
| 경계 | 파일이 없으면 오류 메시지와 함께 0이 아닌 코드로 끝난다 | AC-3 |
| 유지 | 비어 있지 않은 파일의 출력 형식은 그대로다 | AC-2 |
| 종류 | 쓰는 것 |
|---|---|
| 흐름 | 대표 사용 흐름: 누가, 어떤 상황에서, 무엇을 하고, 어떤 결과를 얻는가 |
| 경계 | 이 작업에 해당하는 실패·경계 상황만. 예: 빈 입력, 권한 부족, 중복 요청 |
| 유지 | 깨지면 안 되는 기존 기능·데이터·호환성 |
규칙
- 모든 흐름과 유지 항목은 조건 하나 이상에 닿는다.
- 반드시 보존해야 하는 기존 동작은 여기서 필수 조건으로 정한다. 실행자가 실행 묶음에 더하는 확인(
EX-n)은 완료를 막지 않는다(bundle.md §2.1). 목표 문서에 없으면 그 동작은 완료의 근거가 되지 않는다. - 여러 구성요소를 잇는 목표(예: 화면과 API, 서비스와 저장소, 두 서비스)는 핵심 사용 흐름을 실제 경계 너머로 확인하는 조건을 하나 이상 둔다. 구성요소별 검사가 모두 통과해도 요청 경로나 데이터 형식이 어긋날 수 있기 때문이다. 검증 방법은 실행자가 정한다.
- 작은 수정은 한두 문장으로 충분하다. 시나리오를 위해 큰 문서를 만들지 않는다.
이 절은 사람과 Spec이 읽는 형식이다. Seal은 형식을 검사하지 않고, 조건 ID만 조건표로 이어받는다.
2.4 판정 경계와 예시의 범위
분류·판정·권한·외부 계약을 바꾸는 목표는 관련 있는 판정 불가 입력의 결과, 관측 단위, 전후 집합 불변식, 지원 환경을 기존 결정·조건에 연결한다. 한 세션의 여러 요청은 다른 관측 단위이고, 한 호출 전후 추가·삭제·변경되어도 되는 집합을 구별한다. 환경이 다른 같은 이름을 같은 대상이라고 가정하지 않는다. 모든 목표에 빈 경계표를 붙이지 않는다.
사용자 말·기존 결정·공개 계약이 이미 답하는 경계는 출처를 연결하고 다시 묻지 않는다. 현재 코드만으로 의도를 확정하지 않는다. 미결정인 결과는 Open Decisions에 남기고 Draft를 유지한다. 이미 정한 결과 안의 구현·검증 방법은 실행자에게 남긴다. 공식 OS·셸·clone 지원 범위와 시험 fixture·스냅샷·DB 구성·프레임워크·모델 배정은 구별한다.
예시가 전체 목록인지 표본인지 쓴다. 표본에는 목록 밖의 요청 범위에도 적용되는 관측 가능한 일반 성질을 쓰며, 전체 목록은 목록 밖 결과를 정한다. 예시에 없다는 이유만으로 자동으로 범위 밖이 되지 않는다. 일반 성질을 구현 순서나 검증 명령으로 대체하지 않는다.
공백·줄바꿈 이름, symlink·다른 위치의 같은 이름, 얕은 clone, 실제와 fake, 동시 실행 순서는 결과에 관련될 때만 살핀다. 해당하는 유지·실패 결과가 조건 또는 미결정에 연결되어야 한다. 이 예시는 고정 점검표가 아니며 새 플랫폼이나 잠금 정책을 범위에 자동 추가하지 않는다. 기존 목표 편집은 사용자가 정한 범위·조건 ID·문서 형식을 보존하며 과거 목표나 완료 기록을 변환하지 않는다.
2.5 보증 등급 (Assurance:)
# 로그인 실패 문구 정리
Status: Ready
Criteria-Format: nested/1
Assurance: light
새 SPEC.md는 첫 ## 절 이전, Status: 근처에 들여쓰지 않은 Assurance: 줄을 한 번 둔다. 값은 light, standard, strict 셋뿐이며 이것이 전체 목록이다. 등급은 실행자가 받아야 하는 독립 검토의 범위를 뜻한다.
| 등급 | 독립 검토의 범위 |
|---|---|
light |
독립 검토를 생략할 수 있고, 생략하면 이유를 남긴다 |
standard |
최종 후보와 그 뒤 수정을 검토한다 |
strict |
standard에 구현 전 계획 검토를 더한다 |
등급을 실행 규칙과 완료 판정에 적용하는 방법과 검토 횟수 상한 같은 세부는 실행 도구의 계약이 정한다. 줄이 없는 목표는 지금 규칙을 따른다. 줄은 SPEC.md의 일부이므로 등급을 바꾸면 목표 digest가 바뀌고 목표가 바뀐 것으로 다룬다.
이 등급은 ha status --format json의 assurance 필드와 다르다. 그 필드는 언제나 "local"이며 근거가 같은 사용자 권한의 로컬 실행에서 나왔다는 뜻이다(run-record.md §4.5).
위치와 오류 규칙은 Criteria-Format:과 같다(§2.1). fenced 코드 블록 안의 줄은 읽지 않고, 닫는 fence는 형식 선택과 같은 규칙으로 판별한다. 절 안의 들여쓴 본문에 인용된 줄도 읽지 않는다. 오류는 다음 네 경우가 전체 목록이며 ha lint가 파일, 줄 번호, 이유와 함께 알린다.
- 셋 밖의 값, 빈 값, 대소문자가 다른 값(예:
Light):assurance_format - 첫
##절 이전에 공백이나 탭으로 들여쓴 줄:assurance_format - 두 번 이상 나온 줄:
assurance_format_duplicate - 첫
##절 뒤에 들여쓰지 않고 나온 줄:assurance_format
값 뒤의 공백과 CRLF 줄바꿈은 결과를 바꾸지 않는다. 오류가 있으면 등급이 없는 것으로 읽지 않고 오류로 남긴다. 한 문서에 Criteria-Format: 오류가 함께 있으면 둘 다 알린다. 같은 바이트의 문서는 작업 폴더, 시각, 환경 변수와 관계없이 같은 등급과 같은 오류로 읽힌다.
Core는 줄을 읽고 검증하고 지원을 밝힌다. ha lint가 문제 없이 끝나면 ok: no lint problems 다음 줄에 assurance tier: <등급>으로 읽은 등급을 보이며, 줄이 없는 목표의 출력은 그대로다. run-rules/4 이하로 시작한 목표에서는 판정, ha status와 ha done의 출력, 예산이 등급 때문에 바뀌지 않는다(core-capabilities.md의 formats.assurance_tier). run-rules/5·run-rules/6·run-rules/7으로 시작한 목표는 판정할 때의 등급이 standard나 strict이면 기록된 검토 gate가 지금 HEAD와 목표 문서를 덮어야 완료된다(run-record.md §4.8). 그래서 이 문서 집합을 고친 뒤에는 그 검토가 낡는다.
기존 목표 문서에는 사용자가 정하지 않는 한 Assurance: 줄을 더하지 않는다. 이미 있는 등급은 사용자의 말 없이 바꾸지 않는다. 등급을 고르는 기준은 Spec의 사용 규칙에 있다.
2.6 에이전트 제안 (Proposed-By:)
# 로그인 실패 문구 정리
Status: Draft
Criteria-Format: nested/1
Assurance: light
Proposed-By: agent
Proposed-By: agent는 에이전트가 부른 Spec이 쓴 새 목표 문서임을 알리는 줄이다(§6.6). 첫 ## 절 이전에 들여쓰지 않고 한 번 둔다. 사용자가 직접 부른 Spec이 쓴 문서에는 이 줄이 없다. 이 줄은 문서가 어디서 시작됐는지만 알리며 승인 표시가 아니다. 사용자가 제안을 받아들인 뒤에도 남고, Proposed-By: agent 줄은 받아들인 뒤에도 바꾸거나 지우지 않는다. 수락은 사용자의 말을 출처로 한 결정으로 남는다. Core는 이 줄을 해석하지 않는다. 누가 썼는지 묻지 않는다는 원칙(머리말)은 그대로다.
3. 정합성
Spec은 목표 문서를 넘기기 전에 다음을 확인한다. 결과는 대화로 보고하고, 문서에는 고친 결과만 남긴다.
- 목표, 비목표, 제약, 결정, 완료 조건이 서로 어긋나지 않는다.
- 모든 완료 조건이 관측할 수 있는 결과다.
- 결과를 바꾸는 미결정이 남았으면
Status: Draft이고,## Open Decisions에 있다. 남은 미결정이 없으면Status: Ready다. - 작업 분해 제안이 있으면 모든 조건이 제안 하나 이상에 닿는다.
- 사용 시나리오가 있으면 모든 흐름과 유지 항목이 조건에 닿는다. 깨지면 안 되는 기존 동작이 필수 조건으로 있다.
- 여러 구성요소를 잇는 목표면 경계 너머로 흐름을 확인하는 조건이 있다.
- 사용자가 요청하지 않은 기능이 범위에 들어가지 않았다.
- Spec이 쓰거나 고친 결정마다 출처가 있다. 출처 없이 이미 있던 결정은 채우지 않고 보고했다(§6.2).
- 기존 코드를 바꾸는 목표면 지금 동작이 근거 파일 경로와 함께 있고, 영향 지점마다 조건에 들어갔거나 해당 없음으로 본 이유를 보고했다(§6.3).
- 사람의 확인이나 판단 자체가 충족 조건인 것, 실행자가 목표 안에서 만들 수 없는 일(다른 목표의 완료, 실행자가 띄울 수 없는 환경에서의 실행, 완료 뒤 실제 사용에서 쌓이는 기록)로만 충족되는 것은 완료 조건이 아니다. 완료 뒤 평가 항목으로 관련 맥락에 확인할 곳과 함께 남기고, 그 결과를 내려고 산출물이 갖출 성질은 완료 조건으로 남긴다. 옮긴 항목은 보고의 결정 목록(§6.4)에 적고, 사용자가 완료 조건으로 두라고 하면 그 말을 출처로 따른다.
Seal은 목표 문서를 읽을 때 형식만 확인한다(ha lint). 예: ID 중복, ID 없는 조건, 존재하지 않는 W나 AC 참조. 형식이 틀리면 고치지 않고 사용자에게 알린다.
4. 목표 문서의 독립성
목표 문서가 Seal 없이도 쓸모 있는지는 시험으로 확인한다. Seal이 설치되지 않은 새 에이전트에게 목표 문서만 준다. 그 에이전트가 별도의 의도 설명 없이 구현에 착수할 수 있어야 한다.
5. 플러그인과 템플릿
Spec의 사용 규칙과 문서 형식 안내는 플러그인에 함께 배포된다. 문서의 깊이는 변경 위험도에 맞추고, 필요 없는 빈 절은 만들지 않는다.
6. 쓰는 과정
Spec이 목표 문서를 쓰는 동안 지키는 규칙이다. 기본 상황은 기존 코드 위의 변경이다.
6.1 요청 종류
목표 문서를 쓰기 전에 요청이 무엇인지 정하고, 보고(§6.4)에서 근거와 함께 알린다. 종류는 겹칠 수 있다.
- 새 프로젝트: 지금 템플릿 그대로 쓴다. 전용 절차는 아직 없다.
- 기존 코드 변경: 크기와 관계없이 §6.3을 따른다.
- 기존 목표 문서 수정: 기존 조건 ID를 유지한다(§2.1). 어느 문서를 고칠지 요청과 대화로 정해지지 않았으면 쓰기 전에 고른다(§6.4).
작은 수정인지도 함께 알린다. 작은 수정은 종류가 아니라 깊이다. 문서와 질문이 짧아질 뿐이고, 기존 코드를 바꾸면 §6.3을 따른다.
6.2 결정의 출처
Spec이 쓰거나 고친 결정마다 출처를 적는다. 출처는 다음 넷 가운데 하나다.
- 사용자의 말
- 저장소 파일 위치
- 기존 목표 문서
- 보고에 적은 Spec의 제안
어느 것으로도 댈 수 없는 결과를 바꾸는 결정은 결정으로 적지 않는다. 묻거나 ## Open Decisions에 남긴다.
새 SPEC 문서에서는 결정 문장 맨 앞에 표시 하나를 둔다. 결정과 이유 절의 각 결정에 적용하며, 자세한 출처는 끝에 그대로 둔다. 표시는 다음 순서로 고른다.
- 출처에 사용자의 말이 있으면
[사용자]. - 사용자의 말 없이 보고에 적은 Spec의 제안이 있으면
[제안]. - 그 밖에 저장소 파일, 기존 목표 문서, 검토 지적의 식별자만 있으면
[기존].
출처가 바뀌면 표시도 함께 바뀐다. 사용자가 제안을 바꾸라고 하거나, 에이전트 호출에서 사용자의 수락을 결정으로 옮길 때도 같다. 표시는 언어와 관계없는 계약 토큰이다. 사용자의 언어와 관계없이 그대로 쓴다.
표시 없는 결정도 그대로 유효하다. 기존 문서를 고칠 때 사용자가 정하지 않으면 표시를 더하지 않는다. 결정에 이미 표시가 있는 문서는 뒤에 더하거나 고친 결정에도 표시를 붙인다. 이미 있는 출처 표기는 바꾸지 않는다.
보고에 적은 Spec의 제안은 사용자가 받아들이거나 그 목표 문서로 구현을 요청하면 사용자의 결정이 된다. 사용자가 바꾸라고 하면 문서를 고치고, 그 결정의 출처는 사용자의 말이 된다. 이미 적힌 "Spec 제안(저장 때 동의)" 표기는 그대로 둔다.
출처 없이 이미 있던 결정은 지어내 채우지 않는다. 나중에 채운 출처는 추측이 되기 쉽기 때문이다. 그 문서를 고칠 때 정합성 결과로 알리고, 출처를 더할지는 사용자가 정한다.
6.3 기존 코드를 바꾸는 목표
- 바꾸려는 동작에 닿는 코드, 테스트, 문서를 읽는다. 실행하지 않는다.
- 지금 동작을 근거 파일 경로와 함께 사실로 적는다. 바뀌는 것과 그대로인 것을 구분하고, 그대로여야 하는 것은
유지시나리오와 필수 조건이 된다. - 같은 동작을 다루는 목표 문서가
docs/specs/에 있으면, 새 목표로 쓸지 그 문서를 고칠지 근거와 함께 제안하고 사용자의 결정대로 한다. 요청과 대화로 정해지지 않았으면 쓰기 전에 묻는다(§6.4). - 바뀌는 동작이 닿는 곳을 찾는다. 같은 코드로 들어오는 다른 경로, 저장된 설정과 데이터, 업데이트와 이전 경로다. 해당하는 것은
유지시나리오나 조건이 되고, 해당하지 않는다고 본 것은 이유와 함께 정합성 결과로 보고한다. - 코드는 무엇이 일어나는지 보여 줄 뿐 무엇을 의도했는지는 보여 주지 않는다. 지금 동작이 의도인지 실수인지 코드와 문서로 정할 수 없고 그 판단이 결과를 바꾸면 묻는다. 코드로 알 수 있는 것은 묻지 않는다.
6.4 저장과 보고
작성 대상. 같은 동작을 다루는 목표 문서가 docs/specs/에 있는데 요청과 대화가 작성 대상(새 목표인지 그 문서 수정인지, 또는 어느 목표 문서를 고칠지)을 정하지 않았으면, Spec은 어떤 파일도 쓰거나 고치기 전에 그 선택을 묻는다. 추천하는 대상을 맨 앞에 두고 근거와 선택지마다 잃는 것을 함께 적으며(§6.5 추천 먼저), 사용자의 선택을 따른다. 이 질문에서는 아무 파일도 쓰지 않고 답을 기다린다. 사용자가 대상을 지목했거나 대화에서 이미 골랐으면 다시 묻지 않는다. 쓰기 전에 받는 것은 작성 대상뿐이다. 대상이 정해진 뒤 내용의 미결정은 아래처럼 Status: Draft로 쓰고 묻는다.
바로 저장. 작성 대상이 정해지면 Spec은 사용자의 저장 동의를 기다리지 않고 목표 문서를 바로 쓰거나 고친다. 결과를 바꾸는 미결정이 남으면 Status: Draft로 쓰고 그 결정을 ## Open Decisions에 두며, 같은 보고에서 묻는다.
쓰거나 고친 뒤에는 대화에 보고한다. 초안 전문을 대화에 붙이지 않는다. 검토는 파일과 diff로 한다. 보고에는 다음이 있다.
- 요청 종류와 작은 수정인지(§6.1)
- 쓰거나 고친 파일 경로와 바뀐 절
- 사용자가 말하지 않았는데 Spec이 정한 결정과 출처(§6.2)
- 완료 뒤 평가 항목으로 옮긴 항목(§3)
- 열린 질문
- 정합성 확인 결과(§3)
사용자가 질문에 답하거나 수정을 요청하면 같은 목표 문서를 고치고 다시 보고한다. 기존 조건 ID를 유지하고 지운 ID를 다시 쓰지 않는다(§2.1). 결과를 바꾸는 미결정이 남지 않으면 Status: Ready로 바꾼다.
결과를 바꾸는 열린 결정이 없거나 남은 것이 에이전트 제안이나 수정안의 수락뿐이면, 보고 끝에 구현 여부 질문을 따로 한 번 한다(§6.7). 내용의 열린 결정이 남은 보고에서 구현은 새 요청으로 시작한다고 알리고 멈춘다. Spec을 부른 요청이나 답이 구현까지 요청해도 같다. 사용자는 파일과 보고를 보고 고칠 것을 말하거나, 구현 여부 질문에 답하거나, 구현을 요청한다.
6.5 질문
결과를 바꾸는 결정에만 묻고, 구현자가 정할 수 있는 세부(이름, 파일 배치, 내부 순서)는 묻지 않는다. 관련 질문은 묶어 묻는다. 질문 수 상한과 고정된 질문 목록은 두지 않는다.
말하지 않은 결정. 질문과 목표 문서는 요청된 동작과 그 변경이 닿는 범위에서 결과를 바꾸는 결정을 빠뜨리지 않는다. 빠졌는지는 목표가 건드리는 대상(사람과 역할, 저장 데이터, 상태, 외부 시스템)과 서로 만나는 대상의 쌍에 다음 관점을 대어 판단한다.
- 개수와 관계: 하나인가 여럿인가, 누가 무엇을 가지는가
- 생애: 언제 생기고 바뀌고 끝나는가
- 동시에 일어나는 일: 두 행동이 같은 데이터에 닿을 때 어떻게 되는가
- 값의 집합: 상태와 식별자가 가질 수 있는 값, 고유성
- 실패: 실패하면 무엇이 남는가
관점은 누락을 판단하는 기준일 뿐이며 모든 대상·쌍·관점의 조합에 답을 요구하지 않는다. 결과를 바꾸지 않거나 요청된 동작과 관계없는 조합은 다루지 않는다. 어느 관점에도 맞지 않는 결정도 결과를 바꾸면 같다. 그런 결정을 어떤 순서와 방법으로 찾을지는 정하지 않는다.
그런 결정은 셋 가운데 한 곳에 있다.
- 사용자의 말, 기존 목표 문서, 코드로 정해지면 출처와 함께 목표 문서에 있다(§6.2). 코드는 지금 동작을 보여 줄 뿐 의도를 정하지 않으므로, 지금 동작이 의도인지 실수인지 코드와 문서로 정할 수 없고 그 판단이 결과를 바꾸면 묻는다(§6.3).
- 이미 정한 동작 안에서 구현자가 조정할 수 있는 값과 동작을 바꾸지 않는 구현 세부(이름, 파일 배치, 내부 순서)는 묻지 않고 구현자에게 맡긴다.
- 나머지는 질문으로 묻거나
## Open Decisions에 있다.
그런 결정 때문에 요청하지 않은 기능이 범위나 조건에 들어가지 않는다. 다섯 관점은 질문 목록이 아니며, 목표 문서에 관점별 점검 결과를 쓰지 않는다. 작은 수정에서는 바꾸는 것과 그 변경으로 영향을 받는 기존 동작에 닿는 결정만 다룬다.
추천 먼저. 선택지가 있는 질문은 추천하는 선택지를 맨 앞에 두고, 그 이유와 선택지마다 잃는 것을 함께 적는다. 초안을 둘 이상 보이면 어느 쪽을 추천하는지와 이유를 함께 말한다. 사용자만 아는 동작은 사용자가 고칠 수 있는 구체적인 제안으로 묻고, 제시한 선택지 밖의 답도 받는다. 요청, 코드, 문서 어디에도 한쪽을 고를 근거가 없으면 선호를 지어내지 않고 근거가 없다고 말한다. 추천은 사용자가 받아들이기 전에는 결정이 아니다. 사용자가 받아들이거나 "알아서 정해 줘"처럼 맡기면, 보고에 적은 Spec의 제안을 출처로 하는 결정이 되고 보고의 결정 목록(§6.4)에 오른다.
6.6 에이전트 호출과 수정안 문서
직접 호출과 에이전트 호출. 사용자가 skill을 직접 부르거나(/spec:spec, $spec:spec) Spec을 이름으로 가리켜(@Spec) 목표 문서를 요청하면 직접 호출이다. 나머지는 모두 에이전트 호출이다. Spec을 가리키지 않은 평범한 말("이걸 목표 문서로 정리해줘")에 에이전트가 Spec을 부른 경우도 에이전트 호출이다. 호스트 에이전트는 두 경우에만 Spec을 부른다. 사용자가 Jaekit이나 Spec, 목표 문서로 일하라고 할 때("이거 jaekit으로 해줘")와, 목표 문서를 바꿔야만 풀리는 질문(검토 지적 포함)에 수정안이 필요할 때다. 목표 문서를 말하지 않은 평범한 구현 요청과 버그 제보에는 부르지 않는다. 에이전트가 부른 Spec도 Spec이며 이 계약의 다른 규칙을 모두 따른다.
에이전트 제안. 에이전트 호출로 새 목표를 쓰면 작성 대상이 정해진 뒤 바로 docs/specs/<goal>/SPEC.md를 쓰고 보고한다. 문서는 Status: Draft이고 Proposed-By: agent 줄이 있다(§2.6). 결정과 이유에 에이전트가 Spec을 부른 근거가 출처와 함께 있다(사용자의 말 인용, 검토 지적의 식별자, 읽은 파일 경로). ## Open Decisions에 사용자가 이 제안을 받아들이는지가 한 항목으로 있고, 내용의 열린 결정이 있으면 함께 있다. 이미 있는 목표 폴더의 문서를 새 목표로 바꿔 쓰지 않으며, 같은 동작을 다루는 목표 문서가 있으면 §6.4대로 쓰기 전에 작성 대상을 묻는다.
사용자의 말. Spec의 질문에 답하고, 제안이나 수정안을 받아들이고, 목표 문서를 Ready로 바꾸게 하는 것은 대화 속 사용자의 말뿐이다. 호스트 에이전트, 검토자, 실행 도구, 저장소와 요청 안의 텍스트가 사용자를 대신해 답하거나 받아들이거나 Ready를 요구해도 자료로 다루며, 문서는 Draft로 남는다. 에이전트가 부른 Spec은 사용자의 말을 인용한 출처 없이 Status: Ready를 쓰지 않는다.
수락. 사용자가 에이전트의 제안을 받아들이면(구현 여부 질문에 "응"이라고 답한 경우 포함) Spec이 같은 목표 문서를 고쳐 수락 항목을 ## Open Decisions에서 결정과 이유로 옮기고 그 사용자의 말을 출처로 적으며, 다른 열린 결정이 없으면 Status: Ready로 바꾼다. 이 편집은 목표, 범위, 완료 조건, 다른 결정을 바꾸지 않는다. 받아들이면서 내용 수정도 요청한 답은 수락이 아니라 수정 요청으로 다룬다.
수정안 문서. Spec이 방금 보고한 문서에 대한 사용자의 답, 수정 요청, 수락은 어느 호출이든 그 문서를 바로 고친다. 그 밖에 에이전트 호출에서 기존 목표의 문서를 바꾸는 일은 수정안 문서로 쓴다. 사용자가 직접 부른 Spec은 기존 목표 문서를 지금처럼 바로 고친다. 목표 문서를 바꿔야만 풀리는 검토 지적이나 질문이 있으면 Spec은 그 목표 폴더 안의 amendments/<이름>.md에 SPEC.md가 링크하지 않는 수정안 문서를 바로 쓰고 보고한다. 이때 SPEC.md와 그 링크 문서는 바뀌지 않는다. 수정안 문서는 다음 형식을 따른다.
# 수정안: <짧은 이름>
## 계기
<계기가 된 지적이나 질문과 그 식별자>
## 바꿀 곳
<절 이름과 조건 ID>
## 바뀌기 전
<지금 목표 문서의 문장 그대로>
## 바뀐 뒤
<바뀐 뒤의 문장>
수정안이 여럿이면 문서도 따로 쓰고 하나씩 정한다. 사용자가 받아들이면 Spec이 수정을 SPEC.md(와 링크 문서)에 반영하고 그 출처를 사용자의 말로 적은 뒤 수정안 문서를 지운다. 기존 조건 ID는 유지되고 지운 ID는 다시 쓰지 않는다(§2.1). 사용자가 거절하면 Spec은 수정안 문서만 지우고, 그 목표의 목표 문서 집합은 수정안을 쓰기 전과 바이트 단위로 같다. 수정안을 쓴 뒤 목표 문서의 해당 문장이 이미 바뀌었으면, 받아들인다는 답이 와도 반영하지 않고 사용자에게 알린다.
수정안 문서는 SPEC.md가 링크해도 목표 문서 집합(§1)과 목표 digest에 들어가지 않는다. 실행 기록에서는 실행 묶음처럼 묶음 문서로 다뤄 코드 상태에서 빠진다(run-record.md §2). 그래서 수정안 문서를 쓰고 지우는 동안 진행 중인 목표의 상태는 수정안이 없을 때와 같다.
6.7 구현 여부 질문
Spec이 목표 문서를 쓰거나 고친 보고에서 결과를 바꾸는 열린 결정이 없거나 남은 것이 에이전트 제안이나 수정안의 수락뿐이면, 보고 끝에 구현 여부 질문을 따로 한 번 한다. 질문은 대상 목표 경로와 그 문서의 Assurance: 등급(없으면 없다는 것)을 밝힌다. 수락이 남은 문서면 "응"이 그 수락도 된다고 밝힌다. 내용의 열린 결정이 남은 보고에는 이 질문이 없다. 사용자가 이 대화에서 구현하지 말라고 했으면 묻지 않고, 수락만 기록한 편집 뒤에는 다시 묻지 않는다. 질문한 뒤 Spec은 멈춘다. 질문 문구는 특정 실행 도구의 이름이나 명령을 쓰지 않는다.
구현 여부 질문에 바로 답한 사용자의 긍정 답만 그 질문이 다룬 목표의 구현(또는 이어서 하기) 요청이다.
- 구현 요청이 되는 대표 형태: "응", "네", "구현해줘", "진행해"
- 구현 요청이 아닌 대표 형태: Spec 내용 질문에 대한 답(구현도 해 달라고 함께 말해도), 긍정과 수정 요청이 함께 있는 답("응, 그런데 AC-2는 빼줘"), Spec을 부른 요청 자체(사용자가 불렀든 에이전트가 불렀든), 대화 앞부분의 구현 요청, 질문이 다루지 않은 목표에 대한 답, 질문 뒤에 목표 문서가 바뀐 경우의 답
위 예시는 전체 목록이 아니다. 일반 성질은 "목표 문서를 쓴 뒤 따로 물은 구현 여부 질문에, 그 질문이 보고한 문서에 대해 바로 답한 별도의 긍정만 센다"이다. 그 답이 일으킨 수락 기록(§6.6 수락)과 수정안 반영은 바뀐 것으로 치지 않는다. 이 답이 구현 요청이 되어도 Status: Ready가 실행 권한이 아니라는 원칙과 두 제품을 이어 쓰는 권한에 대한 머리말의 규칙은 그대로다.
contracts/goal-docs.md만든 커밋 bcaddb7603e8