@decencia/ch-cli 1.30.0 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent/2t-decencia-channel-issue-coder.md +1 -1
- package/agent/2t-decencia-channel-pr-merger.md +1 -1
- package/agent/2t-decencia-channel-terraformer.md +1 -1
- package/dist/commands/policies.d.ts +12 -0
- package/dist/commands/policies.d.ts.map +1 -0
- package/dist/commands/policies.js +302 -0
- package/dist/commands/policies.js.map +1 -0
- package/dist/commands/screens.d.ts.map +1 -1
- package/dist/commands/screens.js +103 -23
- package/dist/commands/screens.js.map +1 -1
- package/dist/commands/specs.d.ts +15 -0
- package/dist/commands/specs.d.ts.map +1 -1
- package/dist/commands/specs.js +117 -10
- package/dist/commands/specs.js.map +1 -1
- package/dist/commands/sprints.d.ts +16 -0
- package/dist/commands/sprints.d.ts.map +1 -1
- package/dist/commands/sprints.js +152 -4
- package/dist/commands/sprints.js.map +1 -1
- package/dist/commands/sqa.d.ts +23 -0
- package/dist/commands/sqa.d.ts.map +1 -1
- package/dist/commands/sqa.js +252 -6
- package/dist/commands/sqa.js.map +1 -1
- package/dist/commands/userflows.d.ts +11 -0
- package/dist/commands/userflows.d.ts.map +1 -0
- package/dist/commands/userflows.js +275 -0
- package/dist/commands/userflows.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skill/2t-decencia-channel-change-manager/SKILL.md +1 -1
- package/skill/2t-decencia-channel-change-propagation-v2/SKILL.md +11 -7
- package/skill/2t-decencia-channel-cli-v2/SKILL.md +48 -20
- package/skill/2t-decencia-channel-db-schema-v2/SKILL.md +4 -4
- package/skill/2t-decencia-channel-github-issue/SKILL.md +1 -1
- package/skill/2t-decencia-channel-orchestrator/SKILL.md +1 -1
- package/skill/2t-decencia-channel-policy-v2/SKILL.md +114 -0
- package/skill/2t-decencia-channel-prd-v2/SKILL.md +37 -12
- package/skill/2t-decencia-channel-project-bootstrap/SKILL.md +22 -25
- package/skill/2t-decencia-channel-screen-v2/SKILL.md +135 -0
- package/skill/2t-decencia-channel-spec-v2/SKILL.md +288 -443
- package/skill/2t-decencia-channel-sprint-builder-v2/SKILL.md +11 -3
- package/skill/2t-decencia-channel-sprint-runner/SKILL.md +1 -1
- package/skill/2t-decencia-channel-sqa-v2/SKILL.md +124 -21
- package/skill/2t-decencia-channel-userflow-v2/SKILL.md +125 -0
- package/skill/2t-decencia-channel-work-status-v2/SKILL.md +3 -3
|
@@ -2,16 +2,17 @@
|
|
|
2
2
|
name: 2t-decencia-channel-spec-v2
|
|
3
3
|
description: |
|
|
4
4
|
[2t][v2] 소통채널 기능명세(=Story) 작성/수정 표준.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
스토리 명세 구조(storySpecsEnabled)가 기본이다: 스토리문장(As a/I want/So that)·사전 조건·
|
|
6
|
+
로직 플로우·비즈니스 규칙·화면 연결·DB 참조.
|
|
7
|
+
Story 분해는 INVEST + 수직 분할(§2), Epic은 도메인 단위 코드(AUTH/ORDER 등, §3).
|
|
8
|
+
레거시(ui/logic 섹션) 프로젝트 규칙은 §L 부록 — 기존 프로젝트는 영향받지 않는다.
|
|
9
9
|
Use when:
|
|
10
10
|
(1) spec(=Story)을 작성·수정할 때,
|
|
11
|
-
(2)
|
|
12
|
-
(3)
|
|
13
|
-
(4)
|
|
14
|
-
|
|
11
|
+
(2) Story를 어떻게 쪼갤지(INVEST·수직 분할), Epic을 어떻게 나눌지 정할 때,
|
|
12
|
+
(3) 스토리문장·사전 조건·로직 플로우·비즈니스 규칙을 작성할 때,
|
|
13
|
+
(4) Story 규모를 Story Point(`--points`)로 매길 때,
|
|
14
|
+
(5) 신규 spec ID를 `AUTH-001` 같은 JIRA 스타일로 부여할 때 (`ch specs create --id`).
|
|
15
|
+
version: 1.31.0
|
|
15
16
|
---
|
|
16
17
|
|
|
17
18
|
<!-- ch-version-gate -->
|
|
@@ -35,302 +36,170 @@ CLI 명령은 [[2t-decencia-channel-cli-v2]], DB 테이블 작성은 [[2t-decenc
|
|
|
35
36
|
|
|
36
37
|
---
|
|
37
38
|
|
|
38
|
-
## 0.
|
|
39
|
+
## 0. 두 가지 모드
|
|
39
40
|
|
|
40
|
-
|
|
|
41
|
+
| 모드 | 조건 | 규칙 |
|
|
41
42
|
|---|---|---|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
| **Story** | spec 자체 (1 spec = 1 story) | 명세 1건. CLI 명칭은 `specs` 그대로 |
|
|
45
|
-
| **Story Point** | `spec.points` (number) | Story 규모 점수. Story를 Task로 쪼개지 않고 spec 단위로 매긴다. §3 |
|
|
46
|
-
| **UI** | `spec.ui` (object) | screenRefs·primaryActions·keyInformation·interactionMap. **route·access·figmaNodeId·file은 화면(screens) 소유** — 레거시 프로젝트만 spec.ui에 쓴다 (§4-5) |
|
|
47
|
-
| **Logic** | `spec.logic` (object) | dataFlowScenarios·stateTransitions·businessRules |
|
|
48
|
-
| **DB** | `spec.dbTableRefs` (string[]) | db-tables 컬렉션의 테이블 ID 배열. 양방향 동기화 |
|
|
43
|
+
| **스토리 명세** (기본) | `storySpecsEnabled=true` (신규 프로젝트 기본 ON) | 이 문서 본문(§1~§15) |
|
|
44
|
+
| **레거시** | `storySpecsEnabled` 미설정·false (기존 프로젝트 전부) | §L 부록 + 종전 ui/logic 규칙 |
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
- 기존 프로젝트는 **영향받지 않는다**. 플래그를 켜지 않으면 폼·렌더·규칙 모두 종전 그대로다.
|
|
47
|
+
- 먼저 프로젝트 모드를 확인한다: `ch projects info --json | jq '.storySpecsEnabled'`
|
|
48
|
+
- 화면(screens) 모드(`screensEnabled`)와는 별개 플래그다. 둘 다 켠 프로젝트가 표준이다.
|
|
51
49
|
|
|
52
|
-
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 1. 스토리 명세 구조
|
|
53
|
+
|
|
54
|
+
spec 1건 = Story 1개. 항목과 저장 필드는 다음과 같다.
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
| 항목 | 저장 필드 | 작성 |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Spec ID | `id` | JIRA 스타일 권장 (`AUTH-001`) — §4 |
|
|
59
|
+
| 기능명 | `name` | 목록에 보이는 짧은 이름 |
|
|
60
|
+
| Epic | `epic` | 도메인 단위 코드 — §3 |
|
|
61
|
+
| 디바이스 (복수) | `devices[]` | 프로젝트 스키마 값에서 선택 |
|
|
62
|
+
| 권한 (복수) | `permissions[]` | 프로젝트 스키마 값에서 선택 |
|
|
63
|
+
| 도메인 (복수) | `domains[]` | 프로젝트 스키마 값에서 선택 |
|
|
64
|
+
| 진행상황 | `status` | waiting/designing/developing/testing/completed/onHold |
|
|
65
|
+
| Story Point | `points` | Story 규모 한 값 — §11 |
|
|
66
|
+
| 스토리문장 | `story` | As a / I want / So that 줄글 — §5 |
|
|
67
|
+
| 사전 조건 | `preconditions` | 마크다운 — §6 |
|
|
68
|
+
| 로직 플로우 | `logic.dataFlowScenarios[]` | 시나리오 → 단계(steps)/분기(nodes) — §7 |
|
|
69
|
+
| 비즈니스 규칙 | `logic.businessRules` | 마크다운 불릿 — §8 |
|
|
70
|
+
| 화면 연결 | `ui.screenRefs[]` | 화면 ID 배열 (전용 커맨드) — §9 |
|
|
71
|
+
| 연관 DB 테이블 | `dbTableRefs[]` | 테이블 ID 배열 — §10 |
|
|
72
|
+
| 작업현황 | `workStatus` | 개발 중 누적 일지 — [[2t-decencia-channel-work-status-v2]] |
|
|
73
|
+
|
|
74
|
+
- **관련 Sprint · 관련 SQA · 변경이력**은 파생 뷰다. 서버가 관리하고 spec 작성 대상이 아니다.
|
|
75
|
+
- **스토리 명세에 없는 것**: 기능유형(featureTypes), UI 상세(interactionMap·primaryActions·keyInformation), 화면 상태전이(stateTransitions), 관련 QnA. 레거시 프로젝트에만 남는다(§L).
|
|
76
|
+
- 저장 구조는 레거시와 호환된다. 흐름·규칙·화면 연결은 기존 필드를 그대로 쓰고, 신규 필드는 `story`·`preconditions`·`domains` 뿐이다.
|
|
55
77
|
|
|
56
78
|
---
|
|
57
79
|
|
|
58
|
-
##
|
|
80
|
+
## 2. Story 분해 원칙 — INVEST + 수직 분할
|
|
59
81
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
Story는 **INVEST**를 만족하게 쪼갠다.
|
|
83
|
+
|
|
84
|
+
| 원칙 | 뜻 |
|
|
85
|
+
|---|---|
|
|
86
|
+
| **I**ndependent | 다른 Story 없이도 개발·배포할 수 있다 |
|
|
87
|
+
| **N**egotiable | 구현 방법은 열려 있다. 계약서가 아니라 대화의 재료다 |
|
|
88
|
+
| **V**aluable | 사용자에게 가치가 하나 생긴다 |
|
|
89
|
+
| **E**stimable | Story Point를 매길 수 있을 만큼 구체적이다 |
|
|
90
|
+
| **S**mall | 한 Sprint 안에 끝난다 (points ≤ 8) |
|
|
91
|
+
| **T**estable | SQA로 검증할 수 있다 |
|
|
66
92
|
|
|
67
|
-
|
|
93
|
+
쪼갤 때는 **수직 분할**이다.
|
|
94
|
+
|
|
95
|
+
- 기술 레이어(DB만, API만, 화면만)로 자르지 않는다. **"주문 테이블 생성"은 Story가 아니다.**
|
|
96
|
+
- 한 Story는 화면부터 DB까지 얇게 관통한다. 사용자 가치 단위로 자른다.
|
|
97
|
+
- 경로가 여럿이면 경로별로 쪼갠다. 예: "카드 결제 취소"와 "무통장 취소"는 별개 Story다.
|
|
98
|
+
- 너무 크면(points 13+) Story 자체를 나눈다. Task로 쪼개지 않는다(§14).
|
|
68
99
|
|
|
69
100
|
---
|
|
70
101
|
|
|
71
|
-
##
|
|
102
|
+
## 3. Epic — 도메인 단위로 나눈다
|
|
103
|
+
|
|
104
|
+
Epic은 기능 묶음이 아니라 **도메인** 단위다. 대문자 코드로 통일한다.
|
|
72
105
|
|
|
73
|
-
|
|
|
106
|
+
| 코드 | 도메인 |
|
|
74
107
|
|---|---|
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
|
|
108
|
+
| `AUTH` | 회원, 인증 |
|
|
109
|
+
| `PROD` | 상품, 탐색 |
|
|
110
|
+
| `ORDER` | 주문, 결제 |
|
|
111
|
+
| `POINT` | 포인트, 쿠폰 |
|
|
112
|
+
| `ADMIN` | 관리자 |
|
|
113
|
+
| `SYS` | 공통, 시스템 |
|
|
114
|
+
|
|
115
|
+
- 프로젝트 성격에 맞게 추가한다 (예: `CHAT`, `CAL`, `REPORT`). 코드는 대문자 2~6자.
|
|
116
|
+
- PRD 기능 요구사항의 카테고리 = Epic 코드다 ([[2t-decencia-channel-prd-v2]]).
|
|
117
|
+
- Spec ID prefix도 같은 코드를 쓴다: Epic `AUTH` → `AUTH-001`, `AUTH-002` (§4).
|
|
118
|
+
- spec의 `domains[]`(복수)는 분류 필터고, `epic`(단수)은 소속이다. Story는 Epic 하나에만 속한다.
|
|
82
119
|
|
|
83
120
|
---
|
|
84
121
|
|
|
85
|
-
##
|
|
122
|
+
## 4. Spec ID — 식별자 명명 규칙
|
|
86
123
|
|
|
87
|
-
###
|
|
124
|
+
### 4-1. 두 가지 모드
|
|
88
125
|
|
|
89
126
|
| 모드 | 사용법 | 결과 ID 예 |
|
|
90
127
|
|---|---|---|
|
|
91
128
|
| **자동 (기본)** | `ch specs create --name ...` (`--id` 생략) | `g3pWKfTpO0uSRU2KEiqh` (Firestore auto-id) |
|
|
92
|
-
| **사용자 지정** | `ch specs create --id
|
|
129
|
+
| **사용자 지정** | `ch specs create --id AUTH-001 --name ...` | `AUTH-001` |
|
|
93
130
|
|
|
94
131
|
기존 spec(랜덤 ID)은 그대로 유지된다. 신규 생성 시에만 패턴 선택 가능.
|
|
95
132
|
|
|
96
|
-
###
|
|
133
|
+
### 4-2. 허용 패턴
|
|
97
134
|
|
|
98
135
|
서버 검증 정규식: `^[A-Za-z][A-Za-z0-9_]*-[A-Za-z0-9]+$` (MaxLength 60)
|
|
99
136
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
| 언더스코어 prefix | ✅ | `USER_ADMIN-007` |
|
|
104
|
-
| 소문자 prefix | ✅ | `login-001` (단, 팀 컨벤션상 대문자 권장) |
|
|
105
|
-
| 숫자 prefix 시작 | ❌ | `1LOGIN-001` |
|
|
106
|
-
| 하이픈 없음 | ❌ | `LOGIN001` |
|
|
107
|
-
| 끝부분 특수문자 | ❌ | `LOGIN-001@v2` |
|
|
108
|
-
|
|
109
|
-
### 1.5-3. 네이밍 권장
|
|
110
|
-
|
|
111
|
-
- **prefix = Epic 또는 도메인 약어 (대문자)**, **`-` + 3자리 0패딩 숫자**가 가장 가독성 좋음.
|
|
112
|
-
- 예: `AUTH-001`, `AUTH-002`, `DASHBOARD-001` …
|
|
113
|
-
- prefix는 Epic과 1:1로 묶으면 검색·정렬 모두 편함. (예: Epic "인증" → `AUTH-*`)
|
|
114
|
-
- 숫자 폭은 프로젝트 규모로 결정 (소형 3자리, 대형 4~5자리).
|
|
115
|
-
- 너무 긴 prefix는 피한다 (Firestore 키 길이·URL 가독성).
|
|
116
|
-
|
|
117
|
-
### 1.5-4. 충돌 시 동작
|
|
137
|
+
- ✅ `AUTH-001` · `ORDER-12` · `USER_ADMIN-007` — 영문 시작, 하이픈 1개 필수, 대문자 권장
|
|
138
|
+
- ❌ `1AUTH-001`(숫자 시작) · `AUTH001`(하이픈 없음) · `AUTH-001@v2`(끝 특수문자)
|
|
139
|
+
- 패턴 위반 400, 같은 ID 존재 시 409. (화면 ID 패턴과 다르다 — 화면만 다중 하이픈 완화)
|
|
118
140
|
|
|
119
|
-
-
|
|
120
|
-
- 메시지: `Spec ID "LOGIN-001"가 이미 존재합니다. 다른 ID를 사용하세요.`
|
|
121
|
-
- 패턴 위반 시 **400 Bad Request** (`Spec ID는 "PREFIX-숫자" 형식이어야 합니다 ...`).
|
|
141
|
+
### 4-3. 네이밍 권장
|
|
122
142
|
|
|
123
|
-
|
|
143
|
+
- **prefix = Epic 도메인 코드(§3)**, `-` + 3자리 0패딩 숫자. 예: `AUTH-001`, `ORDER-014`.
|
|
144
|
+
- 숫자 폭은 프로젝트 규모로 결정 (소형 3자리, 대형 4~5자리).
|
|
145
|
+
- Epic의 다음 번호 자동 산정:
|
|
124
146
|
|
|
125
147
|
```bash
|
|
126
|
-
|
|
127
|
-
ch specs create --id LOGIN-001 --name "로그인 화면" \
|
|
128
|
-
--device web --domain user --type 인증 --epic 인증
|
|
129
|
-
|
|
130
|
-
# Epic의 다음 번호 자동 산정 (간단 스크립트)
|
|
131
|
-
NEXT=$(ch specs list --json --per-page 200 \
|
|
148
|
+
NEXT=$(ch specs list --json --per-page 100 \
|
|
132
149
|
| jq -r '[.data[].id | select(test("^AUTH-[0-9]+$"))] | map(split("-")[1] | tonumber) | (max // 0) + 1' \
|
|
133
150
|
| xargs printf '%03d')
|
|
134
|
-
ch specs create --id "AUTH-$NEXT" --name "비밀번호 재설정" ...
|
|
135
|
-
|
|
136
|
-
# 기존 자동 ID 그대로 (--id 생략)
|
|
137
|
-
ch specs create --name "임시 화면" --device web --domain admin --type 관리
|
|
138
|
-
# → id: g3pWKfTpO0uSRU2KEiqh (자동)
|
|
151
|
+
ch specs create --id "AUTH-$NEXT" --name "비밀번호 재설정" ...
|
|
139
152
|
```
|
|
140
153
|
|
|
141
|
-
###
|
|
154
|
+
### 4-4. ID는 불변
|
|
142
155
|
|
|
143
|
-
|
|
144
|
-
- 같은 프로젝트 안에 자동 ID와 사용자 지정 ID가 **공존 허용**. 일관성을 원하면 새 ID는 모두 사용자 지정으로 통일.
|
|
156
|
+
기존 spec ID는 변경 불가(Firestore 문서 키 = ID). 바꾸려면 재생성 + 참조 일괄 갱신 — [[2t-decencia-channel-change-propagation-v2]]. 자동 ID와 사용자 지정 ID의 공존은 허용된다.
|
|
145
157
|
|
|
146
158
|
---
|
|
147
159
|
|
|
148
|
-
##
|
|
149
|
-
|
|
150
|
-
```bash
|
|
151
|
-
# 0) 신규 프로젝트라면 schema 등록 (UI 컬럼 노출에 필수)
|
|
152
|
-
ch projects set-schema --devices "웹,모바일" --domains "본사관리,회원관리" \
|
|
153
|
-
--types "조회,관리" --permissions "관리자,일반"
|
|
160
|
+
## 5. 스토리문장 (story)
|
|
154
161
|
|
|
155
|
-
|
|
156
|
-
ch specs meta --json
|
|
162
|
+
Story가 무엇인지 **As a / I want / So that** 형식의 줄글로 적는다. 필드 나열이 아니라 문장이다.
|
|
157
163
|
|
|
158
|
-
# 2) 기존 spec/db-tables 목록 확인 (중복·연결 대상 파악)
|
|
159
|
-
ch specs list --json --per-page 100
|
|
160
|
-
ch db-tables list --json
|
|
161
|
-
|
|
162
|
-
# 3) 이 명세가 붙을 화면 확인 (화면 모드 프로젝트)
|
|
163
|
-
ch screens list --json
|
|
164
164
|
```
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
---
|
|
169
|
-
|
|
170
|
-
## 3. Story Point — spec 단위 규모
|
|
171
|
-
|
|
172
|
-
Story(=spec)를 Task로 쪼개지 않는다. Story 하나의 규모를 **Story Point 한 값**으로 매긴다. Sprint 용량 산정은 Σ spec.points로 한다([[2t-decencia-channel-sprint-builder-v2]]).
|
|
173
|
-
|
|
174
|
-
### 3-1. 부여 방법
|
|
175
|
-
|
|
176
|
-
```bash
|
|
177
|
-
ch specs create --name "로그인 화면" --device web --domain user --type 인증 --points 5
|
|
178
|
-
ch specs update <specId> --points 8 --no-version
|
|
165
|
+
회원(As a)은 이메일과 비밀번호로 로그인해서(I want)
|
|
166
|
+
매번 재인증 없이 내 프로젝트에 바로 들어가고 싶다(So that).
|
|
179
167
|
```
|
|
180
168
|
|
|
181
|
-
|
|
182
|
-
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
| points | 의미 | 예 (Story 1건) |
|
|
189
|
-
|---|---|---|
|
|
190
|
-
| 1 | 거의 자명. 반나절 이내 | 단순 텍스트/색상 변경 화면 |
|
|
191
|
-
| 2 | 1일 이내 | 기존 화면에 필드 1개 추가 |
|
|
192
|
-
| 3 | 1~2일. 신규 화면/엔드포인트 1개 | 로그인 화면 |
|
|
193
|
-
| 5 | 2~3일. UI + API + 상태 통합 | 회원가입 플로우 |
|
|
194
|
-
| 8 | 1주~. 큰 모듈, 모르는 영역 포함 | 결제 연동 화면 |
|
|
195
|
-
| 13 | 너무 큼 — **Story를 쪼개라** (spec 분리) | — |
|
|
196
|
-
|
|
197
|
-
### 3-3. 너무 크면 Story를 나눈다
|
|
198
|
-
|
|
199
|
-
13 이상이면 한 Sprint에 못 끝낸다. Task로 쪼개는 게 아니라 **spec 자체를 여러 Story로 나눈다**(§9).
|
|
200
|
-
|
|
201
|
-
### 3-4. tasks 필드는 deprecated
|
|
202
|
-
|
|
203
|
-
과거의 `spec.tasks[]`(체크리스트)는 deprecated다. 신규 spec에는 쓰지 않는다. `--tasks`는 하위호환으로 계속 통과되지만 사용 시 경고가 뜬다. 기존 spec의 tasks는 그대로 두거나, 규모가 필요하면 `--points`로 대체한다.
|
|
169
|
+
규칙:
|
|
170
|
+
- **누가 / 무엇을 / 왜**가 한 문단에 다 들어간다. 형식 라벨("As a:")은 달지 않는다 — 자연스러운 줄글로 녹인다.
|
|
171
|
+
- 1~3문장. 길어지면 Story가 큰 것이다 — §2로 돌아가 쪼갠다.
|
|
172
|
+
- 구현 방식(테이블·API·컴포넌트)은 적지 않는다. 가치와 의도만 적는다.
|
|
173
|
+
- ❌ `로그인 기능. 이메일/비밀번호. Firebase Auth 사용`
|
|
174
|
+
- ⭕ `회원은 이메일과 비밀번호로 로그인해서, 재인증 없이 자신의 프로젝트에 바로 접근하고 싶다.`
|
|
204
175
|
|
|
205
176
|
---
|
|
206
177
|
|
|
207
|
-
##
|
|
208
|
-
|
|
209
|
-
### 4-0. 필드 소유 규칙 (화면 모드)
|
|
210
|
-
|
|
211
|
-
| | 화면(screens) 소유 | spec.ui 소유 |
|
|
212
|
-
|---|---|---|
|
|
213
|
-
| 필드 | `route` · `access` · `figmaNodeId` · `file` | `screenRefs` · `primaryActions` · `keyInformation` · `interactionMap` |
|
|
214
|
-
|
|
215
|
-
- spec.ui에 남는 것은 전부 **"이 기능이 화면에서 무엇을 하는가"**다. 화면 자체의 속성은 하나도 안 남는다.
|
|
216
|
-
- **figmaNodeId는 화면 소유다.** Figma 노드는 프레임 하나 = 화면 하나를 가리킨다.
|
|
217
|
-
- **file도 화면 소유다.** 기능은 원래 여러 파일에 걸친다(페이지+컴포넌트+API+훅). 단일 문자열로는 부정확하고 코드가 바뀌면 곧 stale해진다. **화면 = 페이지 = 파일 하나**는 안정적인 1:1이다. 기능이 어느 컴포넌트에 있는지는 코드 검색이 정확하다.
|
|
218
|
-
- **interactionMap은 레거시가 아니다.** 기능이 소유한다. 기능을 다른 화면으로 옮기면 인터랙션도 따라간다. 한 화면에 기능 셋이 붙으면 인터랙션도 셋이 합쳐진다. 화면 상세는 이걸 모아서 보여줄 뿐이다(집계 뷰이지 저장소가 아니다).
|
|
219
|
-
- **primaryActions와 interactionMap은 역할이 다르다.** primaryActions는 핵심 액션 **요약**이다. interactionMap은 **요소 단위 상세 표**다. 요약이 상세를 대체하지 못한다.
|
|
220
|
-
- 레거시 프로젝트(`screensEnabled` 미설정·false)는 종전대로 `route`·`access`·`figmaNodeId`·`file`을 spec.ui에 쓴다.
|
|
221
|
-
|
|
222
|
-
### 4-1. ui.json 구조 (화면 모드 — 기본)
|
|
223
|
-
|
|
224
|
-
```json
|
|
225
|
-
{
|
|
226
|
-
"interactionMap": "## 인터랙션\n\n| 요소 | 타입 | 액션 | 동작 |\n|---|---|---|---|\n| 이메일 input | input | onBlur | 형식 검증 |\n| 비밀번호 input | input + 토글 | 클릭 | 숨김/표시 |\n| 로그인 버튼 | 버튼 | 클릭 | 비동기 인증 |\n| 회원가입 링크 | 링크 | 클릭 | → /register |\n\n### 다이얼로그/바텀시트\n- 잠금 안내: 5회 실패 시 표시\n",
|
|
227
|
-
"screenRefs": ["SCR-LOGIN"],
|
|
228
|
-
"primaryActions": "- 이메일·비밀번호로 로그인한다\n- 비밀번호 재설정으로 이동한다",
|
|
229
|
-
"keyInformation": "- 로그인 실패 사유\n- 남은 시도 횟수"
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
**신규 spec에는 `route`·`access`·`figmaNodeId`·`file`을 넣지 않는다.** 이 4개는 화면 소유다(§4-0). 화면은 `screenRefs`로 참조만 한다(§4-5).
|
|
234
|
-
|
|
235
|
-
**레거시 예시** — `screensEnabled` 미설정·false 프로젝트만. 이 경우에만 `route`·`access`·`figmaNodeId`·`file`을 spec.ui에 쓴다.
|
|
236
|
-
|
|
237
|
-
```json
|
|
238
|
-
{
|
|
239
|
-
"figmaNodeId": "12345:67890",
|
|
240
|
-
"route": "/login",
|
|
241
|
-
"file": "src/app/login/page.tsx",
|
|
242
|
-
"access": "public",
|
|
243
|
-
"interactionMap": "## 인터랙션\n..."
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
`screenRefs`·`primaryActions`·`keyInformation`은 **화면(screens) 기능을 켠 프로젝트**에서만 쓴다(§4-5).
|
|
178
|
+
## 6. 사전 조건 (preconditions)
|
|
248
179
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
| 필드 | 작성 규칙 |
|
|
252
|
-
|---|---|
|
|
253
|
-
| `figmaNodeId` | **[레거시]** Figma URL의 `?node-id=` 값. `-` → `:` 치환. 예: `12345:67890`. 화면 모드의 SSOT는 `screens.figmaNodeId`다 |
|
|
254
|
-
| `route` | **[레거시]** 화면 경로. `screensEnabled=true` 프로젝트에서는 **쓰지 않는다**. route의 SSOT는 `screens.route`다 |
|
|
255
|
-
| `file` | **[레거시]** 진입 컴포넌트 파일 경로(저장소 기준). 예: `src/app/login/page.tsx`. 화면 모드의 SSOT는 `screens.file`(페이지 파일)이다 |
|
|
256
|
-
| `access` | **[레거시]** `public`, `auth`, `admin`, `role:센터장` 등 자유 문자열. 화면 모드에서는 `screens.access`가 SSOT다 |
|
|
257
|
-
| `interactionMap` | **모든 인터랙티브 요소**를 마크다운 표로. 다이얼로그/바텀시트/키보드 단축키는 별도 절. **레거시가 아니다** — 화면 모드에서도 계속 쓴다. `primaryActions`(요약)가 이 상세 표를 대체하지 못한다 |
|
|
258
|
-
| `screenRefs[]` | 이 명세가 걸리는 **화면 ID 배열**. 서버가 `screen.relatedSpecIds`와 양방향으로 맞춘다. **`ui.json`에는 생성 때만 넣는다** — 수정은 전용 커맨드 `ch specs screen-refs`로 (§4-5) |
|
|
259
|
-
| `primaryActions` | 이 명세가 화면에서 하게 하는 **핵심 액션**. 마크다운 목록. 예: `- 프로젝트를 만든다` |
|
|
260
|
-
| `keyInformation` | 화면에 **반드시 보여야 하는 정보**. 마크다운 목록. 예: `- 남은 예산` |
|
|
261
|
-
|
|
262
|
-
### 4-3. interactionMap 표 권장 컬럼
|
|
180
|
+
이 Story가 성립하기 위해 **먼저 참이어야 하는 것**을 마크다운 불릿으로 적는다.
|
|
263
181
|
|
|
264
182
|
```markdown
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
| 폴더 만들기 | 버튼 | 탭 | → /folders/create |
|
|
269
|
-
| 이미지 카드 | 카드 | 탭 | → /images/:id |
|
|
270
|
-
| 이미지 카드 | 카드 | 롱프레스 | 선택 모드 진입 |
|
|
271
|
-
| 공유 토글 | 스위치 | ON/OFF | is_shared 변경 + 참여자 UI 표시 |
|
|
272
|
-
| ⋮ 메뉴 | 아이콘 | 탭 | 바텀시트(관리/삭제) |
|
|
183
|
+
- 회원가입(AUTH-001)이 완료된 계정이 존재한다
|
|
184
|
+
- 이메일 인증이 끝난 상태다
|
|
185
|
+
- (외부) PG 가맹 심사가 승인되어 있다
|
|
273
186
|
```
|
|
274
187
|
|
|
275
188
|
규칙:
|
|
276
|
-
-
|
|
277
|
-
-
|
|
278
|
-
-
|
|
279
|
-
|
|
280
|
-
### 4-4. 비화면 명세
|
|
281
|
-
|
|
282
|
-
화면이 없는 기능 명세(Cron, 배치, 외부 연동)는 `ui` 통째 생략. 대신 `logic`에 집중.
|
|
283
|
-
|
|
284
|
-
### 4-5. 화면(screens) 연결 — screensEnabled 프로젝트만
|
|
285
|
-
|
|
286
|
-
**참조 원칙 — db-tables와 같은 구조다.** 정의는 전용 리소스에 있고, spec은 링크만 건다.
|
|
287
|
-
|
|
288
|
-
| | 정의의 SSOT | spec은 |
|
|
289
|
-
|---|---|---|
|
|
290
|
-
| DB | `db-tables` (컬럼·타입·인덱스) | `dbTableRefs[]`로 참조만 |
|
|
291
|
-
| 화면 | `screens` (route·목적·권한·상태) | `ui.screenRefs[]`로 참조만 |
|
|
292
|
-
|
|
293
|
-
프로젝트에 `screensEnabled`가 켜져 있으면 화면 정보의 SSOT가 **screens 리소스**로 바뀐다. 꺼져 있거나 필드 자체가 없으면(기존 프로젝트 전부) 지금까지처럼 `ui.route`가 화면 정보다 — 동작이 달라지지 않는다.
|
|
294
|
-
|
|
295
|
-
| 모드 | 화면 정보 | ui에 쓰는 것 |
|
|
296
|
-
|---|---|---|
|
|
297
|
-
| `screensEnabled` 미설정·false (레거시 기본) | `ui.route`·`access`·`figmaNodeId`·`file`·`interactionMap` | 기존 5필드만 |
|
|
298
|
-
| `screensEnabled=true` | screens 리소스 (route·access·figmaNodeId·file) | `interactionMap` + `screenRefs`·`primaryActions`·`keyInformation` (**route·access·figmaNodeId·file 없음**) |
|
|
299
|
-
|
|
300
|
-
**신규 spec 규칙**: `ui.route`를 쓰지 마라. 붙일 화면이 없으면 `ch screens create`(또는 웹 화면 탭)로 **먼저 화면을 만들고** 그 ID를 `screenRefs`에 넣는다.
|
|
301
|
-
|
|
302
|
-
**화면과 spec은 N:1이다.** 한 화면에 여러 spec이 붙는 것이 정상이다. `screenRefs`가 배열인 이유는 한 spec이 여러 화면에 걸칠 수도 있어서지, 화면과 spec이 1:1이라는 뜻이 아니다. **spec 수만큼 화면을 만들지 마라** — 그러면 화면이 route의 복사본이 된다.
|
|
303
|
-
|
|
304
|
-
**`screenRefs`는 서버 관리 필드다.** 규칙 두 개만 기억하면 된다.
|
|
305
|
-
|
|
306
|
-
1. **생성 때는 `ui.json`에 넣어도 된다.** 서버가 중복을 걷어내고 각 화면의 `relatedSpecIds`에 이 spec을 더한다. 없는 화면 ID를 넣으면 생성은 성공하되 그 화면은 동기화에서 빠지고 서버 로그에 경고가 남는다.
|
|
307
|
-
2. **수정 때는 `ui.json`으로 못 바꾼다.** 일반 `ch specs update --ui`가 보낸 `screenRefs`는 서버가 **무시하고 기존 값을 지킨다**. **에러가 안 난다** — 저장된 줄 알고 넘어가기 쉽다. 화면 연결 변경은 전용 경로(`PATCH /specs/:id/screen-refs`)로만 한다. CLI는 `ch specs screen-refs <specId> --screens <ids>`, 웹은 화면 피커가 그 경로를 쓴다. **교체 시맨틱**이라 보낸 목록이 연결 전부가 되고, `--screens ""`이면 모두 해제된다. 없는 화면 ID를 보내면 400이다.
|
|
308
|
-
|
|
309
|
-
이 규칙 덕분에 `--ui`로 `interactionMap` 한 줄만 고쳐 저장해도 화면 연결이 날아가지 않는다. `primaryActions`·`keyInformation`도 `ui.json`에 키가 없으면 기존 값이 보존된다(빈 문자열을 명시하면 지워진다). 그래도 안전한 습관은 §0의 Read-before-Write다 — `ch specs get <id> --json`으로 받아 편집한 뒤 보낸다.
|
|
189
|
+
- 1 조건 = 1 불릿. 짧은 단문으로.
|
|
190
|
+
- 다른 Story가 선행이면 Spec ID로 가리킨다 (`AUTH-001`).
|
|
191
|
+
- 개발팀이 통제 못 하는 외부 조건은 `(외부)`를 붙인다.
|
|
192
|
+
- 흐름 안에서 검증하는 조건(비밀번호 형식 등)은 여기가 아니라 로직 플로우·비즈니스 규칙에 적는다.
|
|
310
193
|
|
|
311
194
|
---
|
|
312
195
|
|
|
313
|
-
##
|
|
196
|
+
## 7. 로직 플로우 (dataFlowScenarios)
|
|
314
197
|
|
|
315
|
-
|
|
198
|
+
동작 경로를 **시나리오 단위**로 적는다. 정상·예외를 따로 나누지 않는다 — **실패·차단·이탈 경로는 흐름 안의 분기로 포함**한다(분기가 있으면 nodes/edges, 경로가 아예 다른 여정이면 별도 시나리오).
|
|
316
199
|
|
|
317
|
-
|
|
318
|
-
{
|
|
319
|
-
"dataFlowScenarios": [],
|
|
320
|
-
"stateTransitions": "...",
|
|
321
|
-
"businessRules": "..."
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
`dataFlowScenarios`는 배열(아래 §5-2). `stateTransitions`·`businessRules`는 마크다운 문자열. 필요한 것만 채운다.
|
|
326
|
-
|
|
327
|
-
### 5-2. dataFlowScenarios — 시나리오 → 순서 있는 단계 → 참조
|
|
328
|
-
|
|
329
|
-
데이터 흐름을 **시나리오 단위**로 적는다. 한 시나리오는 하나의 동작 경로다(예: "로그인 성공", "비밀번호 5회 실패로 잠금").
|
|
330
|
-
|
|
331
|
-
시나리오는 두 방식 중 **하나**로 쓴다. 흐름이 선형이면 **순서 있는 단계(steps)**로 적는다. 분기(성공/실패, 조건 갈림)가 있으면 **분기 그래프(nodes/edges)**로 적는다(§5-2-1). 각 단계·노드는 그 지점이 건드리는 테이블·컬럼을 `refs`로 가리킨다. steps와 nodes가 **둘 다 비면 무효**다.
|
|
200
|
+
시나리오는 두 방식 중 하나로 쓴다. 선형이면 **순서 있는 단계(steps)**, 분기가 있으면 **분기 그래프(nodes/edges)**(§7-2). 각 단계·노드는 건드리는 테이블·컬럼을 `refs`로 가리킨다. steps와 nodes가 둘 다 비면 무효다.
|
|
332
201
|
|
|
333
|
-
|
|
202
|
+
### 7-1. 선형 흐름 (steps)
|
|
334
203
|
|
|
335
204
|
```json
|
|
336
205
|
{
|
|
@@ -355,55 +224,54 @@ Story 하나를 끝내는 데 드는 규모를 매긴다.
|
|
|
355
224
|
]
|
|
356
225
|
}
|
|
357
226
|
]
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
"name": "비밀번호 5회 실패로 잠금",
|
|
230
|
+
"description": "비밀번호를 5회 연속 틀리면 계정을 1시간 잠그고 로그인 시도를 차단한다.",
|
|
231
|
+
"steps": [
|
|
232
|
+
{ "description": "5회째 실패를 감지하면 계정 문서에 잠금 해제 시각을 기록한다.",
|
|
233
|
+
"refs": [ { "tableId": "tbl_users", "field": "lockedUntil", "direction": "WRITE" } ] },
|
|
234
|
+
{ "description": "잠금 중 로그인 시도에는 남은 잠금 시간을 안내하고 인증을 수행하지 않는다." }
|
|
235
|
+
]
|
|
358
236
|
}
|
|
359
237
|
]
|
|
360
238
|
}
|
|
361
239
|
```
|
|
362
240
|
|
|
363
|
-
|
|
241
|
+
구조:
|
|
364
242
|
|
|
365
243
|
| 필드 | 타입 | 의미 |
|
|
366
244
|
|---|---|---|
|
|
367
245
|
| `dataFlowScenarios[]` | 배열 | 시나리오 목록 |
|
|
368
|
-
| `…[].name` | 문자열(필수) |
|
|
369
|
-
| `…[].description` | 문자열(선택) | 시나리오
|
|
370
|
-
| `…[].steps[]` | 배열(선택) |
|
|
371
|
-
| `…steps[].description` | 문자열(필수) | 그 단계에서 일어나는
|
|
372
|
-
| `…steps[].refs[]` | 배열(선택) | 이 단계가
|
|
373
|
-
| `…[].nodes[]` | 배열(선택) | 분기
|
|
374
|
-
| `…[].edges[]` | 배열(선택) | 분기 그래프의 간선(§5-2-1) |
|
|
375
|
-
|
|
376
|
-
> steps와 nodes는 **둘 중 하나**만 채운다. 둘 다 비면 무효다.
|
|
377
|
-
|
|
378
|
-
#### 작성 원칙 — 완결 문장으로
|
|
246
|
+
| `…[].name` | 문자열(필수) | 동작 경로 이름. 예: `로그인 성공` |
|
|
247
|
+
| `…[].description` | 문자열(선택) | 시나리오 요약 완결 문장 1~2개 |
|
|
248
|
+
| `…[].steps[]` | 배열(선택) | 순서 있는 단계. 배열 순서 = 실행 순서. nodes를 쓰면 생략 |
|
|
249
|
+
| `…steps[].description` | 문자열(필수) | 그 단계에서 일어나는 일 (완결 문장) |
|
|
250
|
+
| `…steps[].refs[]` | 배열(선택) | 이 단계가 R/W하는 테이블·컬럼 참조 (§10-2) |
|
|
251
|
+
| `…[].nodes[]` / `…[].edges[]` | 배열(선택) | 분기 그래프 (§7-2). steps를 쓰면 생략 |
|
|
379
252
|
|
|
380
|
-
|
|
381
|
-
-
|
|
382
|
-
- ❌
|
|
383
|
-
-
|
|
384
|
-
-
|
|
385
|
-
-
|
|
386
|
-
- 외부 API 호출도 한 단계로 적는다(예: "결제 서버에 승인을 요청한다"). 그 단계가 우리 테이블을 건드리면 `refs`로 표시한다.
|
|
387
|
-
- 🔒 컬럼은 **이름으로만** 가리킨다 — `type`/`nullable` 등 정의는 적지 않는다(§6-2 SSOT 규칙). 기계 추적 가능한 참조는 `refs`로 구조화한다(§6-2-1).
|
|
253
|
+
작성 원칙 — **완결 문장으로**:
|
|
254
|
+
- `description`은 6하원칙을 담은 완결 문장으로 쓴다. 필드명·단어 나열 금지, "누가:" 같은 소제목·라벨 금지.
|
|
255
|
+
- ❌ `이메일/비밀번호 → Firebase Auth → ID Token, users.lastLoginAt WRITE`
|
|
256
|
+
- ⭕ `회원이 이메일과 비밀번호로 로그인하면, 인증 서버가 자격 증명을 확인한 뒤 마지막 로그인 시각을 갱신한다.`
|
|
257
|
+
- 한 단계 = 데이터가 한 번 이동·변형하는 매듭(외부 API 호출도 한 단계). 너무 잘게 쪼개지 않는다.
|
|
258
|
+
- 🔒 컬럼은 **이름으로만** 가리킨다. type/nullable 등 정의는 적지 않는다 (§10-1 SSOT 규칙).
|
|
388
259
|
|
|
389
|
-
###
|
|
260
|
+
### 7-2. 분기 그래프 (nodes/edges)
|
|
390
261
|
|
|
391
|
-
흐름이 조건에 따라 갈리면
|
|
262
|
+
흐름이 조건에 따라 갈리면 steps 대신 노드·간선으로 그린다. 성공·실패가 한 그래프에서 갈리면 시나리오 하나로 묶는다.
|
|
392
263
|
|
|
393
|
-
- **node
|
|
394
|
-
- **edge
|
|
395
|
-
-
|
|
396
|
-
- 각 노드도 steps처럼 `refs`로 그 지점이 R/W하는 테이블·컬럼을 가리킨다(§6-2-1).
|
|
264
|
+
- **node**: `id`(시나리오 안 유일, 필수) · `kind`(`step` 기본 · `decision` 조건 갈림 · `terminal` 종료) · `description`(완결 문장, 필수) · `refs[]`(선택, §10-2)
|
|
265
|
+
- **edge**: `from`/`to`(노드 id, 필수) · `label`(분기 조건 — decision에서 갈라지는 edge마다 붙인다)
|
|
266
|
+
- 선형이면 steps, 분기면 nodes/edges — 둘 중 하나로만(둘 다 비면 무효). 상태 컬럼을 WRITE하는 ref는 `toState`로 목표 상태를 명시한다(§10-2).
|
|
397
267
|
|
|
398
268
|
```json
|
|
399
269
|
{
|
|
400
270
|
"dataFlowScenarios": [
|
|
401
271
|
{
|
|
402
272
|
"name": "주문 결제 처리",
|
|
403
|
-
"description": "재고를 확인한 뒤 결제를 승인하거나 품절로 중단한다.",
|
|
404
273
|
"nodes": [
|
|
405
|
-
{ "id": "n1", "kind": "step", "description": "주문 상태와 재고를 확인한다.",
|
|
406
|
-
"refs": [{ "tableId": "tbl_orders", "field": "status", "direction": "READ" }] },
|
|
274
|
+
{ "id": "n1", "kind": "step", "description": "주문 상태와 재고를 확인한다." },
|
|
407
275
|
{ "id": "d1", "kind": "decision", "description": "재고가 있는가?" },
|
|
408
276
|
{ "id": "n2", "kind": "step", "description": "결제를 승인하고 주문을 결제완료로 바꾼다.",
|
|
409
277
|
"refs": [{ "tableId": "tbl_orders", "field": "status", "direction": "WRITE", "toState": "paid" }] },
|
|
@@ -421,235 +289,212 @@ Story 하나를 끝내는 데 드는 규모를 매긴다.
|
|
|
421
289
|
}
|
|
422
290
|
```
|
|
423
291
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
| 필드 | 필수 | 의미 |
|
|
427
|
-
|---|---|---|
|
|
428
|
-
| `nodes[].id` | 필수 | 노드 식별자. 한 시나리오 안에서 유일 |
|
|
429
|
-
| `nodes[].kind` | 선택 | `step`(기본)·`decision`·`terminal` |
|
|
430
|
-
| `nodes[].description` | 필수 | 그 지점에서 일어나는 일(완결 문장) |
|
|
431
|
-
| `nodes[].refs[]` | 선택 | 그 지점이 R/W하는 테이블·컬럼(§6-2-1) |
|
|
432
|
-
| `edges[].from`/`to` | 필수 | 잇는 두 노드의 `id`. 같은 시나리오 nodes에 실재해야 함 |
|
|
433
|
-
| `edges[].label` | 선택 | 분기 조건. decision에서 갈리는 edge에 붙인다 |
|
|
434
|
-
|
|
435
|
-
규칙:
|
|
436
|
-
- **선형이면 steps, 분기가 있으면 nodes/edges. 둘 중 하나로 작성한다**(둘 다 비면 무효).
|
|
437
|
-
- decision 노드에서 나가는 edge마다 `label`로 조건을 밝힌다.
|
|
438
|
-
- 상태 컬럼을 WRITE하는 node ref는 `toState`로 목표 상태를 명시한다(§6-2-1).
|
|
439
|
-
|
|
440
|
-
### 5-3. stateTransitions — 화면 상태 전이 표
|
|
441
|
-
|
|
442
|
-
`logic.stateTransitions`는 **UI/화면의 휘발성 상태 전이 전용**이다. 화면이 떠 있는 동안만 존재하고 새로고침하면 사라지는 상태만 적는다. 예: `idle → loading → error → success`, 모달 열림/닫힘, 폼 단계.
|
|
443
|
-
|
|
444
|
-
**엔티티의 생애주기는 여기 적지 않는다.** DB에 저장되는 상태(주문 `pending→paid→delivered`, 배포 `대기→진행→완료`, 계정 `active→locked` 등)는 `db-table.stateMachines`가 SSOT다 — [[2t-decencia-channel-db-schema-v2]] §5-4. spec은 그 상태를 *이용하는* 흐름만 `dataFlowScenarios`·`businessRules`로 이름 참조한다.
|
|
445
|
-
|
|
446
|
-
두 개념은 이렇게 갈린다:
|
|
447
|
-
|
|
448
|
-
| 구분 | 어디에 | 무엇 |
|
|
449
|
-
|---|---|---|
|
|
450
|
-
| **화면 전이** (휘발성) | `spec.logic.stateTransitions` (여기, prose 표) | idle/loading/error, 모달 열림/닫힘, 폼 단계 |
|
|
451
|
-
| **엔티티 상태** (저장됨) | `db-tables.stateMachines` (구조화, SSOT) | 주문 status, 배포 phase, 계정 상태 |
|
|
452
|
-
|
|
453
|
-
엔티티 상태를 건드리는 흐름은 `dataFlowScenarios`의 ref로 잇는다 — 상태 컬럼을 WRITE하는 ref에 `toState`를 붙인다(§6-2-1). 같은 엔티티 전이를 stateTransitions 표에도 적으면 **중복·불일치**다. 저장되는 상태는 db-tables에만.
|
|
292
|
+
### 7-3. 저장되는 엔티티 상태는 여기 아니다
|
|
454
293
|
|
|
455
|
-
|
|
294
|
+
주문 `pending→paid`, 계정 `active→locked` 같은 **저장되는 상태의 정의**는 `db-table.stateMachines`가 SSOT다 — [[2t-decencia-channel-db-schema-v2]] §5-4. 흐름은 그 상태를 *이용*만 한다. 상태 컬럼을 WRITE하는 ref에 `toState`를 붙여 링크한다.
|
|
456
295
|
|
|
457
|
-
|
|
458
|
-
| 현재 상태 | 이벤트 | 다음 상태 | 사이드이펙트 |
|
|
459
|
-
|---|---|---|---|
|
|
460
|
-
| idle | submit | loading | spinner 표시 |
|
|
461
|
-
| loading | success | authenticated | 토큰 저장 + /dashboard 이동 |
|
|
462
|
-
| loading | error | error | 에러 메시지 표시 |
|
|
463
|
-
| error | retry | loading | spinner |
|
|
464
|
-
| authenticated | logout | idle | 토큰 제거 |
|
|
465
|
-
```
|
|
466
|
-
|
|
467
|
-
규칙:
|
|
468
|
-
- 컬럼: 현재 / 이벤트 / 다음 / 사이드이펙트 (이펙트 컬럼은 옵션)
|
|
469
|
-
- 가드 조건은 이벤트 컬럼에 `(조건)` 또는 별도 컬럼
|
|
470
|
-
- 진입 시점 = idle (또는 init)을 명시
|
|
471
|
-
- 저장되는 엔티티 상태(주문/계정/배포 등)는 여기 말고 `db-table.stateMachines`에
|
|
296
|
+
---
|
|
472
297
|
|
|
473
|
-
|
|
298
|
+
## 8. 비즈니스 규칙 (businessRules)
|
|
474
299
|
|
|
475
|
-
번호/불릿
|
|
300
|
+
도메인 규칙을 번호/불릿 마크다운으로 적는다.
|
|
476
301
|
|
|
477
302
|
```markdown
|
|
478
303
|
- 비밀번호 5회 연속 실패 시 해당 계정 1시간 잠금 (lockedUntil 필드)
|
|
479
304
|
- 휴면 계정(마지막 로그인 후 30일 경과)은 이메일 재인증 후 로그인 허용
|
|
480
305
|
- 동일 계정 동시 세션 3개 초과 시 가장 오래된 세션 강제 만료
|
|
481
306
|
- 비밀번호 정책: 영문 + 숫자 + 특수문자 포함 8자 이상
|
|
482
|
-
- 어드민 권한 계정은 IP 화이트리스트 통과 시에만 로그인
|
|
483
307
|
```
|
|
484
308
|
|
|
485
309
|
규칙:
|
|
486
|
-
- 1 규칙 = 1
|
|
487
|
-
-
|
|
488
|
-
-
|
|
489
|
-
- 🔒 enum/상태값의 **허용값 집합 정의는 적지 않는다**(db-tables가 SSOT)
|
|
310
|
+
- 1 규칙 = 1 불릿. 임계값·기간·조건은 구체 수치로.
|
|
311
|
+
- 큰 규칙은 별도 Story로 분리 가능.
|
|
312
|
+
- **여러 Story·영역에 걸치는 횡단 정책**(알림 매트릭스, 크레딧 기준, 타임아웃 처리 등)은 여기가 아니라 **정책정의서**에 적고 연관 Spec ID로 잇는다 — [[2t-decencia-channel-policy-v2]]. 복붙 금지.
|
|
313
|
+
- 🔒 enum/상태값의 **허용값 집합 정의는 적지 않는다** (db-tables가 SSOT). 그 값을 *이용한 규칙*만 적는다. (예: ❌ "status는 draft|published|archived" / ⭕ "published 상태에서만 외부 노출")
|
|
314
|
+
- 규칙이 어떤 테이블·컬럼을 검사·갱신하는지는 그 규칙이 작동하는 시나리오의 `refs`로 가리킨다 (§7).
|
|
490
315
|
|
|
491
316
|
---
|
|
492
317
|
|
|
493
|
-
##
|
|
318
|
+
## 9. 화면 연결 (ui.screenRefs)
|
|
494
319
|
|
|
495
|
-
|
|
320
|
+
화면 정의의 SSOT는 **screens 리소스**다 (route·목적·권한·figmaNodeId·file·상태). spec은 `screenRefs`로 링크만 건다.
|
|
496
321
|
|
|
497
|
-
1
|
|
498
|
-
|
|
499
|
-
|
|
322
|
+
- **화면과 spec은 N:1이다.** 한 화면에 여러 spec이 붙는 것이 정상이다. spec 수만큼 화면을 만들지 마라.
|
|
323
|
+
- 붙일 화면이 없으면 `ch screens create`로 **먼저 화면을 만들고** 그 ID를 넣는다.
|
|
324
|
+
- 화면이 없는 기능(Cron·배치·외부 연동)은 화면 연결을 비워둔다.
|
|
500
325
|
|
|
501
|
-
|
|
326
|
+
**`screenRefs`는 서버 관리 필드다.** 규칙 두 개:
|
|
502
327
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
| **spec** | "이 화면/기능이 어느 테이블의 어떤 컬럼을 R/W하는가" — `logic.dataFlowScenarios`의 단계 `refs`로 **이름으로 참조만** |
|
|
506
|
-
| **db-tables** | 컬럼 정의, 인덱스, 보안 규칙, 역할 매트릭스 (스키마의 SSOT) |
|
|
328
|
+
1. **생성 때는 `ui.json`에 넣어도 된다.** 서버가 중복을 걷어내고 화면의 `relatedSpecIds`와 양방향으로 맞춘다. 단, 없는 화면 ID는 생성을 막지 않는다 — 저장은 되되 동기화에서 빠지고 서버 로그에 경고만 남는다. 엄격 검증(400)은 전용 커맨드 쪽이다(아래 2).
|
|
329
|
+
2. **수정 때는 `--ui`로 못 바꾼다.** 서버가 무시하고 기존 값을 지킨다 — **에러가 안 난다.** 변경은 전용 커맨드로만: `ch specs screen-refs <specId> --screens <ids>`. **교체 시맨틱**이라 보낸 목록이 연결 전부가 되고, `--screens ""`이면 모두 해제된다. 없는 화면 ID는 400.
|
|
507
330
|
|
|
508
|
-
|
|
331
|
+
---
|
|
509
332
|
|
|
510
|
-
|
|
511
|
-
컬럼 `type`·`nullable`·`default`·길이·`enum 허용값`, 인덱스, 보안규칙/RLS 본문, 역할 매트릭스.
|
|
333
|
+
## 10. DB — dbTableRefs로 테이블 연결
|
|
512
334
|
|
|
513
|
-
-
|
|
514
|
-
- enum/상태값의 **정의**(허용값 집합)는 db-tables. businessRules는 그 값을 이용한 **규칙**만 적는다. (예: ❌ "status는 draft|published|archived" / ⭕ "published 상태에서만 외부 노출")
|
|
515
|
-
- 같은 정보를 db-tables와 spec 양쪽에 쓰면 스키마 변경 시 반드시 어긋난다. 의심되면 정의는 지우고 `dbTableRefs` 링크로 대체.
|
|
335
|
+
### 10-1. 흐름과 SSOT 규칙
|
|
516
336
|
|
|
517
|
-
|
|
337
|
+
테이블 먼저 만들고(`ch db-tables create`) spec에서 `--db-tables tbl_a,tbl_b`로 연결한다. 서버가 `spec.dbTableRefs` ↔ `table.relatedSpecIds`를 양방향 동기화한다.
|
|
518
338
|
|
|
519
|
-
|
|
339
|
+
- **spec** = 이 Story가 어느 테이블·컬럼을 R/W하는가 — 흐름의 `refs`로 **이름 참조만**
|
|
340
|
+
- **db-tables** = 컬럼 정의·인덱스·보안 규칙·상태머신 (스키마의 SSOT — [[2t-decencia-channel-db-schema-v2]])
|
|
341
|
+
- **spec에 적지 않는 것**: 컬럼 `type`·`nullable`·`default`·enum 허용값, 인덱스, 보안규칙 본문, 역할 매트릭스. 양쪽에 쓰면 변경 시 반드시 어긋난다.
|
|
520
342
|
|
|
521
|
-
|
|
522
|
-
{
|
|
523
|
-
"dataFlowScenarios": [
|
|
524
|
-
{
|
|
525
|
-
"name": "로그인 성공",
|
|
526
|
-
"steps": [
|
|
527
|
-
{
|
|
528
|
-
"description": "인증에 성공하면 마지막 로그인 시각을 갱신한다.",
|
|
529
|
-
"refs": [
|
|
530
|
-
{ "tableId": "tbl_users", "field": "lastLoginAt", "direction": "WRITE", "note": "로그인 성공 시 갱신" },
|
|
531
|
-
{ "tableId": "tbl_sessions", "direction": "READ" }
|
|
532
|
-
]
|
|
533
|
-
}
|
|
534
|
-
]
|
|
535
|
-
}
|
|
536
|
-
]
|
|
537
|
-
}
|
|
538
|
-
```
|
|
343
|
+
### 10-2. 구조적 참조 (`steps[].refs[]` · `nodes[].refs[]`)
|
|
539
344
|
|
|
540
|
-
|
|
541
|
-
- **상태 전이 링크 (`toState`)** — `field`가 그 테이블 db-table의 `stateMachines[].columnRef`와 같으면, 그 ref는 그 엔티티의 **상태머신 접근**이다. WRITE면 `toState`로 목표 상태를 밝힌다. `toState` 값은 그 머신 `states[].value` 중 하나여야 한다. 예: `{ "tableId": "tbl_orders", "field": "status", "direction": "WRITE", "toState": "paid" }`
|
|
542
|
-
- 상태 **정의**(허용 상태·전이)는 db-tables.stateMachines가 SSOT다. ref는 tableId+field+toState **링크만** 저장한다 — 상태 목록을 spec에 베끼지 않는다. `ch specs get`이 그 머신 요약을 읽기 시점에 인라인해 준다([[2t-decencia-channel-db-schema-v2]] §5-4).
|
|
543
|
-
- 서버가 **실존 검증**: 없는 tableId → 400, 컬럼 정의가 있는 테이블에 없는 field → 400(끊긴 참조 차단).
|
|
544
|
-
- CLI는 `--logic logic.json`으로 통째 전달(별도 플래그 없음). 웹은 spec 상세의 DB 섹션에서 칩+피커로 편집.
|
|
545
|
-
- **여전히 정의는 금지** — refs는 가리키기만 한다. type/nullable/enum 허용값 등은 db-tables.
|
|
546
|
-
- 레거시 spec의 `dataFlowRefs`/`businessRuleRefs`(logic 최상위 배열)는 하위호환으로 읽히지만, 신규·수정 시에는 해당 컬럼을 건드리는 **단계·노드의 refs**로 옮긴다.
|
|
345
|
+
각 단계·노드가 R/W하는 테이블·컬럼을 기계가 추적 가능하게 가리킨다. 정의는 금지 — refs는 가리키기만 한다.
|
|
547
346
|
|
|
548
|
-
|
|
347
|
+
- `tableId` **필수**(실제 db-tables id) · `field` 선택 · `direction` 선택(`READ|WRITE|READWRITE`) · `note` 선택. 서버가 실존 검증(없는 tableId·field → 400).
|
|
348
|
+
- **`toState`** — `field`가 그 테이블 `stateMachines[].columnRef`와 같으면 상태머신 접근이다. WRITE면 목표 상태를 `toState`로 밝힌다(값은 그 머신 `states[].value` 중 하나).
|
|
349
|
+
- 레거시 `dataFlowRefs`/`businessRuleRefs`(logic 최상위)는 하위호환으로 읽히지만, 신규·수정 시에는 단계·노드의 refs로 옮긴다.
|
|
350
|
+
|
|
351
|
+
### 10-3. 신규 테이블이 필요한 경우 순서
|
|
549
352
|
|
|
550
353
|
```bash
|
|
551
|
-
# 1) 테이블 먼저
|
|
552
|
-
ch
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
--indexes ./users-indexes.json \
|
|
556
|
-
--security ./users-security.json
|
|
557
|
-
# → {"id": "tbl_xyz123"}
|
|
558
|
-
|
|
559
|
-
# 2) spec 생성/연결
|
|
560
|
-
ch specs create --name "로그인" ... --db-tables tbl_xyz123
|
|
354
|
+
ch db-tables create --name users --columns ./cols.json ... # 1) 테이블 먼저 → {"id":"tbl_xyz"}
|
|
355
|
+
ch specs create --name "로그인" ... --db-tables tbl_xyz # 2) spec 생성 시 연결
|
|
356
|
+
# 추가 연결: ch specs get <id> --json 으로 현재 목록을 받아 합쳐 통째로 보낸다
|
|
357
|
+
ch specs update <specId> --db-tables tbl_xyz,tbl_new --no-version
|
|
561
358
|
```
|
|
562
359
|
|
|
563
|
-
|
|
360
|
+
---
|
|
361
|
+
|
|
362
|
+
## 11. Story Point — spec 단위 규모
|
|
363
|
+
|
|
364
|
+
Story를 Task로 쪼개지 않는다. Story 하나의 규모를 **Story Point 한 값**으로 매긴다. Sprint 용량 = Σ spec.points ([[2t-decencia-channel-sprint-builder-v2]]).
|
|
564
365
|
|
|
565
366
|
```bash
|
|
566
|
-
|
|
567
|
-
ch specs
|
|
568
|
-
# 결과에 새 ID를 쉼표로 이어서:
|
|
569
|
-
ch specs update <specId> --db-tables tbl_xyz,tbl_new --no-version
|
|
367
|
+
ch specs create --name "로그인" ... --points 5
|
|
368
|
+
ch specs update <specId> --points 8 --no-version
|
|
570
369
|
```
|
|
571
370
|
|
|
572
|
-
|
|
371
|
+
- `--points <n>` — 0 이상 숫자. 보통 피보나치(1/2/3/5/8/13).
|
|
372
|
+
- 진행률(%) 개념은 없다. 완료 여부는 `status`로. **연관 SQA 통과 = 완료.**
|
|
573
373
|
|
|
574
|
-
|
|
374
|
+
| points | 의미 | 예 (Story 1건) |
|
|
375
|
+
|---|---|---|
|
|
376
|
+
| 1 | 거의 자명. 반나절 이내 | 단순 텍스트/색상 변경 |
|
|
377
|
+
| 2 | 1일 이내 | 기존 화면에 필드 1개 추가 |
|
|
378
|
+
| 3 | 1~2일. 신규 화면/엔드포인트 1개 | 로그인 |
|
|
379
|
+
| 5 | 2~3일. UI + API + 상태 통합 | 회원가입 플로우 |
|
|
380
|
+
| 8 | 1주~. 큰 모듈, 모르는 영역 포함 | 결제 연동 |
|
|
381
|
+
| 13 | 너무 큼 — **Story를 쪼개라** (§2) | — |
|
|
575
382
|
|
|
576
|
-
|
|
383
|
+
`tasks[]`는 deprecated다. 신규 spec에 쓰지 않는다 (`--tasks`는 하위호환 통과 + 경고).
|
|
577
384
|
|
|
578
385
|
---
|
|
579
386
|
|
|
580
|
-
##
|
|
387
|
+
## 12. 🚨 BLOCKING: Read-before-Write
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
ch specs get <specId> --json > /tmp/spec.json # 1) 현재 상태 백업
|
|
391
|
+
# 편집 (logic JSON 통째 교체 위험)
|
|
392
|
+
ch specs update <specId> --logic /tmp/logic.json --no-version
|
|
393
|
+
ch specs get <specId> --json | jq '.logic' # 3) 검증
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
`--logic` / `--ui` / `--work-status`는 **각 필드 자체를 통째로 교체**한다. 일부만 바꾸려면 반드시 전체를 가져와 편집 후 전송. (`--points`·`--story` 같은 스칼라는 그 값만 바뀐다.)
|
|
581
397
|
|
|
582
|
-
|
|
398
|
+
---
|
|
583
399
|
|
|
584
|
-
|
|
585
|
-
- **화면 모드**(`screensEnabled=true`): `interactionMap`/`screenRefs`/`primaryActions`/`keyInformation` (route·access·figmaNodeId·file은 screens에 있다 — §4-0)
|
|
586
|
-
- **레거시**(`screensEnabled` 미설정·false): `figmaNodeId`/`route`/`file`/`access`/`interactionMap` 5필드
|
|
587
|
-
- `logic.dataFlowScenarios` 필수 (화면이 다루는 주요 동작 경로별 시나리오)
|
|
588
|
-
- `points` 화면 하나의 규모 (보통 3~8)
|
|
589
|
-
- `dbTableRefs` 화면이 R/W하는 테이블 전부
|
|
400
|
+
## 13. 작성 절차 체크리스트 (스토리 명세)
|
|
590
401
|
|
|
591
|
-
|
|
402
|
+
신규 Story 1건을 만들 때:
|
|
592
403
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
404
|
+
1. `ch specs meta --json` → 스키마 옵션(디바이스·도메인·권한) 확인
|
|
405
|
+
2. `ch specs list --json` → 기존 Story·Epic 코드·다음 번호 확인
|
|
406
|
+
3. **Story 분해 검증** — INVEST·수직 분할(§2). 경로가 여럿이면 미리 쪼갠다
|
|
407
|
+
4. **Epic 코드 결정**(§3) → **Spec ID 결정**(§4, `EPIC-NNN`)
|
|
408
|
+
5. `points` 산정 (§11, 보통 3~8)
|
|
409
|
+
6. 스토리문장(§5)·사전 조건(§6) 작성
|
|
410
|
+
7. `logic.json` 작성 — 로직 플로우 시나리오(실패·차단 분기 포함, §7), businessRules(§8)
|
|
411
|
+
8. 필요한 테이블 확인 → 없으면 `ch db-tables create` 먼저 (§10)
|
|
412
|
+
9. 붙을 화면 확인 — `ch screens list --json`. 없으면 `ch screens create` 먼저 (§9)
|
|
413
|
+
10. 생성:
|
|
597
414
|
|
|
598
|
-
|
|
415
|
+
```bash
|
|
416
|
+
ch specs create --id AUTH-001 --name "로그인" \
|
|
417
|
+
--device web,app --domain 회원 --permission 비회원 \
|
|
418
|
+
--epic AUTH --points 5 \
|
|
419
|
+
--story "회원은 이메일과 비밀번호로 로그인해서, 재인증 없이 자신의 프로젝트에 바로 접근하고 싶다." \
|
|
420
|
+
--preconditions ./preconditions.md \
|
|
421
|
+
--logic ./logic.json \
|
|
422
|
+
--db-tables tbl_users,tbl_sessions \
|
|
423
|
+
--ui ./ui.json # {"screenRefs":["SCR-LOGIN"]} — 생성 때만
|
|
424
|
+
```
|
|
599
425
|
|
|
600
|
-
|
|
601
|
-
-
|
|
602
|
-
|
|
603
|
-
- 다른 spec이 이걸 import한다는 점을 `note`에 명시
|
|
426
|
+
11. `ch specs get <id> --json`으로 결과 검증
|
|
427
|
+
12. SQA 항목은 [[2t-decencia-channel-sqa-v2]]에 따라 별도 등록
|
|
428
|
+
13. 개발 착수 후 진행 일지는 [[2t-decencia-channel-work-status-v2]]
|
|
604
429
|
|
|
605
430
|
---
|
|
606
431
|
|
|
607
|
-
##
|
|
432
|
+
## 14. 분해 크기 검증
|
|
608
433
|
|
|
609
|
-
너무 큰
|
|
434
|
+
너무 큰 Story는 분리한다.
|
|
610
435
|
|
|
611
436
|
| 지표 | 한계 | 조치 |
|
|
612
437
|
|---|---|---|
|
|
613
|
-
| `points`
|
|
614
|
-
|
|
|
615
|
-
|
|
|
616
|
-
|
|
|
438
|
+
| `points` | 13+ | Sprint 1회로 못 끝남 → Story 분리 |
|
|
439
|
+
| 시나리오 수 | 6+ | 경로가 다중일 가능성 → 경로별 분리 (§2) |
|
|
440
|
+
| 스토리문장 | 3문장 초과 | 가치가 여러 개 섞임 → 분리 |
|
|
441
|
+
| 서로 다른 화면 3개 이상에 걸침 | 기능이 너무 넓다 | 분리 검토 |
|
|
617
442
|
|
|
618
|
-
>
|
|
619
|
-
> 레거시 프로젝트(route 방식)에서는 종전 규칙(**1 화면 = 1 spec**)이 그대로 유효하다.
|
|
443
|
+
> 한 화면에 Story 여러 개가 붙는 것은 정상이다 (N:1 — §9). 화면이 여러 개라고 무조건 쪼개지 않는다.
|
|
620
444
|
|
|
621
445
|
---
|
|
622
446
|
|
|
623
|
-
##
|
|
624
|
-
|
|
625
|
-
- `--ui
|
|
626
|
-
-
|
|
627
|
-
-
|
|
628
|
-
-
|
|
629
|
-
-
|
|
630
|
-
-
|
|
631
|
-
-
|
|
632
|
-
-
|
|
633
|
-
-
|
|
634
|
-
- `tasks`는 deprecated. `--tasks`는 하위호환으로만 통과된다(사용 시 경고).
|
|
635
|
-
- **스키마 정의를 logic에 베껴 넣기** — 컬럼 type/nullable/index/보안규칙/enum 허용값은 db-tables에만. spec은 이름 참조 + `dbTableRefs` 링크만(§6-2).
|
|
636
|
-
- **dataFlow를 단어 나열로 적기** — 시나리오의 단계 `description`은 화살표·필드명 나열이 아니라 완결 문장으로(§5-2). "누가/어디에" 소제목도 달지 않는다.
|
|
447
|
+
## 15. 흔한 함정
|
|
448
|
+
|
|
449
|
+
- `--logic`/`--ui` JSON은 통째 교체 → 일부 수정도 **반드시 get 후 편집** (§12).
|
|
450
|
+
- **스토리문장을 필드 나열로 적기** — As a/I want/So that이 녹은 줄글이어야 한다 (§5).
|
|
451
|
+
- **기술 레이어로 Story 쪼개기** — "주문 테이블 생성"은 Story가 아니다. 수직 분할 (§2).
|
|
452
|
+
- **`ui.screenRefs`를 `--ui`로 바꾸려 하기** — 수정 때는 서버가 조용히 무시한다. `ch specs screen-refs`로만 (§9).
|
|
453
|
+
- `dbTableRefs`를 손으로 JSON에 넣어도 서버가 무시 (`--db-tables` 또는 웹 picker만).
|
|
454
|
+
- **스키마 정의를 흐름·규칙에 베껴 넣기** — 컬럼 type/enum 허용값/보안규칙은 db-tables에만 (§10-1).
|
|
455
|
+
- **흐름을 단어 나열로 적기** — 완결 문장으로 (§7-1).
|
|
456
|
+
- **실패·차단 경로 누락** — 해피패스만 적고 끝내지 않는다. 실패·차단·이탈은 분기(nodes/edges의 실패 가지)나 별도 시나리오로 반드시 포함한다.
|
|
457
|
+
- 스토리 명세 프로젝트에 interactionMap·stateTransitions·기능유형을 쓰지 않는다 — 레거시 전용(§L).
|
|
637
458
|
|
|
638
459
|
---
|
|
639
460
|
|
|
640
|
-
##
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
1.
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
461
|
+
## L. 부록 — 레거시 모드 (storySpecsEnabled 미설정·false)
|
|
462
|
+
|
|
463
|
+
기존 프로젝트 전부가 여기 해당한다. **종전 규칙이 그대로 유효하다.** 아래는 요약이고, 세부는 종전과 같다.
|
|
464
|
+
|
|
465
|
+
### L-1. 구조
|
|
466
|
+
|
|
467
|
+
- `content` — 개요 1~3줄 + 권한 요약
|
|
468
|
+
- `domain` 단수 + `featureTypes[]`(기능유형) 사용
|
|
469
|
+
- `ui.*` — 화면·상호작용 메타데이터 (L-2)
|
|
470
|
+
- `logic.*` — dataFlowScenarios + stateTransitions + businessRules
|
|
471
|
+
- 관련 QnA 탭 유지
|
|
472
|
+
- Spec ID·points·dbTableRefs·workStatus 규칙은 본문과 동일
|
|
473
|
+
|
|
474
|
+
### L-2. ui 필드 소유 규칙
|
|
475
|
+
|
|
476
|
+
| | 화면 모드(`screensEnabled=true`) | 레거시 화면 정보 |
|
|
477
|
+
|---|---|---|
|
|
478
|
+
| ui에 쓰는 것 | `screenRefs`·`primaryActions`·`keyInformation`·`interactionMap` | `route`·`access`·`figmaNodeId`·`file`·`interactionMap` |
|
|
479
|
+
| 화면(screens) 소유 | `route`·`access`·`figmaNodeId`·`file` | — (screens 미사용) |
|
|
480
|
+
|
|
481
|
+
- `interactionMap`은 레거시가 아니라 **기능 소유**다. 모든 인터랙티브 요소를 마크다운 표(요소/타입/액션/동작)로 적는다. 같은 요소에 여러 액션이면 행을 나눈다. 모달·바텀시트 안의 요소도 별도 행.
|
|
482
|
+
- `primaryActions`(핵심 액션 요약)·`keyInformation`(필수 표시 정보)은 마크다운 목록. 요약이 interactionMap 상세를 대체하지 못한다.
|
|
483
|
+
- `ui.json` 부분 갱신 함정: 키가 없으면 보존, 빈 문자열이면 삭제. Read-before-Write 필수 (§12).
|
|
484
|
+
- 화면 없는 기능은 `ui` 통째 생략.
|
|
485
|
+
|
|
486
|
+
### L-3. stateTransitions — 화면 상태 전이 표
|
|
487
|
+
|
|
488
|
+
**UI 휘발성 상태 전용**이다 (idle/loading/error, 모달 열림/닫힘, 폼 단계). 표로 적는다: 현재 상태 / 이벤트 / 다음 상태 / 사이드이펙트.
|
|
489
|
+
|
|
490
|
+
저장되는 엔티티 상태(주문 status 등)는 여기 적지 않는다 — `db-table.stateMachines`가 SSOT (§7-3).
|
|
491
|
+
|
|
492
|
+
### L-4. 명세 유형별 패턴
|
|
493
|
+
|
|
494
|
+
- **화면 명세**: ui 채움(모드별 L-2) + dataFlowScenarios 필수 + dbTableRefs
|
|
495
|
+
- **기능 명세**(Cron·배치·연동): ui 생략, dataFlowScenarios + businessRules 필수
|
|
496
|
+
- **공통 명세**(인증·알림·업로드): ui 생략, businessRules 중심, 다른 spec이 import함을 `note`에 명시
|
|
497
|
+
|
|
498
|
+
### L-5. 레거시 → 스토리 명세 전환
|
|
499
|
+
|
|
500
|
+
자동 마이그레이션은 없다 — 프로젝트 단위 opt-in을 사람이 결정한다. 전환해도 기존 ui/logic 데이터는 보존되며(새 폼이 안 보여줄 뿐), 화면 모드 이관(`ch screens migrate`)과는 별개 절차다.
|