@decencia/ch-cli 1.8.2 → 1.11.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.
@@ -2,7 +2,7 @@
2
2
  name: 2t-decencia-channel-change-propagation-v2
3
3
  description: |
4
4
  [2t][v2] 소통채널 변경 전파(Change Propagation) 표준 가이드.
5
- 한 기능을 변경할 때 PRD/spec(ui·logic·dbTableRefs·tasks)/db-schema/db-tables/SQA/workStatus까지
5
+ 한 기능을 변경할 때 PRD/spec(ui·logic·dbTableRefs·points)/db-schema/db-tables/SQA/workStatus까지
6
6
  연관된 모든 곳을 빠짐없이 갱신·동기화하기 위한 영향 분석 + 순차 적용 워크플로우.
7
7
  Use when:
8
8
  (1) 기존 기능을 수정·확장하려고 할 때 (DB 컬럼 추가, 화면 요구사항 변경, 권한 정책 변경 등),
@@ -10,7 +10,7 @@ description: |
10
10
  (3) 명세·DB·SQA가 서로 어긋나 있는 의심이 들 때 (정합성 점검),
11
11
  (4) PRD 갱신 후 하위 spec/db/SQA로 변경분을 흘려보내야 할 때,
12
12
  (5) Sprint 진행 중 도메인 규칙·스키마가 흔들렸을 때 연쇄 갱신이 필요할 때.
13
- version: 1.8.2
13
+ version: 1.11.0
14
14
  ---
15
15
 
16
16
  <!-- ch-version-gate -->
@@ -30,7 +30,7 @@ ch check
30
30
 
31
31
  # 변경 전파(Change Propagation) — PRD ↔ spec ↔ DB ↔ SQA 동기화
32
32
 
33
- 소통채널은 PRD / spec(ui·logic·dbTableRefs·tasks) / db-schema(전체) / db-tables(per-table) / SQA(시트·항목) / workStatus가 서로를 참조한다. 한 곳을 바꾸면 다른 곳도 같이 흔들린다. 이 스킬은 **변경의 진앙(epicenter)을 식별 → 영향 범위를 매트릭스로 산출 → 순차로 안전하게 적용 → 정합성 검증**까지의 표준 절차다.
33
+ 소통채널은 PRD / spec(ui·logic·dbTableRefs·points) / db-schema(전체) / db-tables(per-table) / SQA(시트·항목) / workStatus가 서로를 참조한다. 한 곳을 바꾸면 다른 곳도 같이 흔들린다. 이 스킬은 **변경의 진앙(epicenter)을 식별 → 영향 범위를 매트릭스로 산출 → 순차로 안전하게 적용 → 정합성 검증**까지의 표준 절차다.
34
34
 
35
35
  ---
36
36
 
@@ -44,6 +44,36 @@ ch check
44
44
 
45
45
  ---
46
46
 
47
+ ## 0.5 분류 게이트 — 버그 vs 기획 변경 (맨 먼저)
48
+
49
+ 전파를 시작하기 전에 **무엇을 다루는지 먼저 분류한다**. 둘은 처리 경로가 완전히 다르다.
50
+
51
+ | 구분 | 정의 | 처리 |
52
+ |---|---|---|
53
+ | **버그** | 코드가 **spec(의도)과 어긋남**. 명세는 맞는데 구현이 틀렸다. | **전파하지 않는다.** 코드만 고친다. |
54
+ | **기획 변경** | **spec/의도 자체가 바뀜**. 명세가 옛것이 됐다. | 전파한다(§1~). PRD/spec/DB/SQA를 동기화. |
55
+
56
+ 판별 질문: **"명세대로 동작하면 이 문제가 사라지는가?"**
57
+ - 예 → **버그**. spec은 옳다. 코드만 spec에 맞춘다.
58
+ - 아니오(명세 자체를 바꿔야 함) → **기획 변경**. 전파 대상.
59
+
60
+ ### 버그일 때 (전파 금지)
61
+
62
+ 버그는 캐스케이드하지 않는다. spec/PRD/db-schema는 이미 올바른 의도를 담고 있으니 건드리면 오히려 어긋난다.
63
+
64
+ 1. **코드만 수정** — 구현을 spec에 맞춘다.
65
+ 2. **회귀 SQA** (필요 시) — 그 버그를 다시 잡는 검증 항목을 추가하거나 재실행. 기존 SQA 항목 자체는 바꾸지 않는다(이미 옳으므로).
66
+ 3. **workStatus 1줄** — 해당 spec에 `[FIX] YYYY-MM-DD: <버그 요약> — 코드가 spec과 어긋나 수정` 엔트리만 남긴다.
67
+ 4. 끝. PRD/spec 본문·db-tables·다른 spec은 **건드리지 않는다.**
68
+
69
+ > 헷갈리면 보수적으로 본다: spec을 다시 읽어 "현재 명세가 원하던 동작"이 무엇인지 확인한다. 명세가 그 동작을 이미 기술하고 있으면 버그다.
70
+
71
+ ### 기획 변경일 때
72
+
73
+ §1부터 진행한다(영향 분석 → 매트릭스 → 순차 적용 → 검증).
74
+
75
+ ---
76
+
47
77
  ## 1. 리소스 의존성 지도
48
78
 
49
79
  ```
@@ -83,7 +113,7 @@ ch check
83
113
  | PRD | Epic 명, spec 제목 (기능 관점만 — 스키마/테이블 미포함) | (텍스트만 — 자동 링크 없음) |
84
114
  | spec | `epic`, `dbTableRefs[]` | `relatedSpec` (SQA), spec.dbTableRefs ↔ table.relatedSpecIds |
85
115
  | db-schema (전체) | 테이블 인벤토리 (텍스트) | (텍스트만) |
86
- | db-tables (per) | `relatedSpecIds[]` | 다른 테이블의 FK 주석 |
116
+ | db-tables (per) | `relatedSpecIds[]`, `stateMachines[].transitions[].specRef` | 다른 테이블의 FK 주석 |
87
117
  | SQA 항목 | `relatedSpec` | — |
88
118
  | spec.workStatus | (자유 텍스트) | — |
89
119
 
@@ -91,16 +121,18 @@ ch check
91
121
 
92
122
  ## 2. 변경 진앙 분류
93
123
 
124
+ > 전제: §0.5에서 **기획 변경**으로 분류된 경우만 여기 온다. 버그면 전파하지 않고 코드만 고친다(§0.5).
125
+
94
126
  먼저 사용자가 바꾸려는 게 **무엇**인지 분류한다. 진앙이 달라지면 전파 경로가 달라진다.
95
127
 
96
128
  | # | 진앙 (변경 시작점) | 1차 영향 | 2차 영향 |
97
129
  |---|---|---|---|
98
- | A | **DB 컬럼/인덱스/securityRules** 변경 | `db-tables/<id>` | db-schema 마이그레이션 노트, 해당 테이블 참조 spec(logic, *이름 참조만*), SQA(데이터 검증 TC). **PRD 무영향**(스키마는 PRD에 없음) |
130
+ | A | **DB 컬럼/인덱스/securityRules/stateMachines** 변경 | `db-tables/<id>` | db-schema 마이그레이션 노트, 해당 테이블 참조 spec(logic, *이름 참조만*), SQA(데이터 검증 TC). **stateMachines 변경 시** 추가로: 상태전이 SQA TC(§sqa-v2 4.3.5), 전이의 `specRef`가 가리키는 spec, spec.logic.stateTransitions와 경계 중복 없는지. **PRD 무영향**(스키마는 PRD에 없음) |
99
131
  | B | **새 테이블 추가/삭제** | `db-tables` 신규/삭제 | db-schema 인벤토리·ERD(테이블 단위), 영향 spec.dbTableRefs, SQA(신규 검증 TC). **PRD 무영향**(새 *기능*이면 그건 C/H 진앙) |
100
132
  | C | **spec 의 UI 요구사항** 변경 | `spec.ui` | 같은 화면 참조하는 다른 spec, SQA(UI 검증 TC), PRD 화면 절 |
101
133
  | D | **spec 의 비즈니스 규칙(logic)** 변경 | `spec.logic` | 의존 spec, SQA(정상/비정상 TC), db-tables(검증 규칙·trigger), PRD 도메인 규칙 절 |
102
134
  | E | **권한/역할 정책** 변경 | 영향 spec.logic, db-tables.securityRules | 모든 영향 spec의 SQA(접근권한 TC), db-schema 보안 일관성 절, PRD 권한 절 |
103
- | F | **Task 분해 또는 points 변경** | `spec.tasks` | Sprint 용량 산정, workStatus |
135
+ | F | **Story Point(points) 변경** | `spec.points` | Sprint 용량 산정, workStatus |
104
136
  | G | **Epic 명/범위 변경** | `spec.epic`, PRD Epic 절 | 모든 같은 Epic 산하 spec 재태깅, Sprint 묶음 |
105
137
  | H | **PRD 본문 변경** | `prd` | 그 PRD에서 파생된 spec, db-schema, SQA 시트 — **전수 리뷰 트리거** |
106
138
 
@@ -108,7 +140,7 @@ ch check
108
140
  - "users 테이블에 last_login_at 추가" → **A**
109
141
  - "회원 등급에 VIP 추가, 권한도 더 줘야 함" → **E** (+ D)
110
142
  - "로그인 화면에 SNS 로그인 버튼 추가" → **C** (+ E if 권한)
111
- - "이 spec 잘게 쪼개고 싶음" → **F**
143
+ - "이 spec 규모(Story Point)를 다시 매김" → **F** (규모가 너무 크면 spec 자체를 여러 Story로 분리 — [[2t-decencia-channel-spec-v2]] §9)
112
144
 
113
145
  ---
114
146
 
@@ -142,10 +174,17 @@ TABLE_ID="<targetTableId>"
142
174
  # 이 테이블을 dbTableRefs에 가진 spec
143
175
  jq -r --arg t "$TABLE_ID" '.data[] | select(.dbTableRefs[]? == $t) | .id + "\t" + .title' /tmp/specs.json
144
176
 
145
- # (구조적 참조) logic.dataFlowRefs / businessRuleRefs 로 이 테이블·컬럼을 가리키는 spec — 컬럼 단위 영향까지 정밀 추적
177
+ # (구조적 참조) logic.dataFlowScenarios[].steps[].refs[] 로 이 테이블을 가리키는 spec — 컬럼 단위 영향까지 정밀 추적
146
178
  jq -r --arg t "$TABLE_ID" '
147
179
  .data[]
148
- | select(((.logic.dataFlowRefs // []) + (.logic.businessRuleRefs // []))[]?.tableId == $t)
180
+ | select([ .logic.dataFlowScenarios[]?.steps[]?.refs[]?.tableId ] | index($t))
181
+ | .id + "\t" + .title' /tmp/specs.json
182
+
183
+ # (컬럼 단위) 특정 field까지 일치하는 단계를 쓰는 spec — 바뀐 *컬럼*을 직접 건드리는 spec만 좁히기
184
+ FIELD="<변경된 컬럼명>"
185
+ jq -r --arg t "$TABLE_ID" --arg f "$FIELD" '
186
+ .data[]
187
+ | select([ .logic.dataFlowScenarios[]?.steps[]?.refs[]? | select(.tableId == $t and .field == $f) ] | length > 0)
149
188
  | .id + "\t" + .title' /tmp/specs.json
150
189
 
151
190
  # (직접 링크) 이 테이블을 dbTableRefs에 가진 SQA 시트 항목 — relatedSpec 우회 없이 1-hop
@@ -162,7 +201,7 @@ for SHEET in $(jq -r '.data[].id' /tmp/sheets.json); do
162
201
  done
163
202
  ```
164
203
 
165
- > 컬럼 단위 정밀 추적: `dataFlowRefs[].field` 로 어느 spec 바뀐 *컬럼*을 직접 쓰는지까지 좁힐 수 있다(끊긴 참조는 서버가 작성 시점에 이미 거부).
204
+ > 컬럼 단위 정밀 추적: `dataFlowScenarios[].steps[].refs[].field` 로 어느 spec 어느 단계가 바뀐 *컬럼*을 직접 쓰는지까지 좁힐 수 있다(끊긴 참조는 서버가 작성 시점에 이미 거부).
166
205
 
167
206
  #### C·D (spec 변경) — 이 spec과 같은 화면·테이블·Epic 묶음 spec 찾기
168
207
 
@@ -256,7 +295,7 @@ ch prd get --json | jq -r '.content' | grep -niE "데이터 모델|컬럼|nullab
256
295
  ```
257
296
  1) db-tables(진앙이면) ← 데이터 모델 변경의 최저층 (스키마 정의의 유일한 거처)
258
297
  2) db-schema 전체 문서 (인벤토리/ERD/마이그레이션 노트) ← 1) 반영, 테이블 단위까지만
259
- 3) spec (logic/ui/dbTableRefs/tasks) ← 1·2) 반영, 컬럼은 이름 참조만
298
+ 3) spec (logic/ui/dbTableRefs/points) ← 1·2) 반영, 컬럼은 이름 참조만
260
299
  4) SQA 시트 항목 ← 3) 의 변경된 동작을 검증
261
300
  5) PRD ← **기능/정책 변화가 있을 때만**. 순수 스키마 변경(A/B)은 PRD 무관
262
301
  6) workStatus ← 각 spec에 "전파 작업 완료" 1줄 추가
@@ -295,7 +334,7 @@ ch db-schema set --file /tmp/dbschema.md --no-version
295
334
 
296
335
  # 3) spec — [[2t-decencia-channel-spec-v2]]
297
336
  ch specs get <specId> --json > /tmp/s.json
298
- # … 편집 (ui/logic/dbTableRefs/tasks) …
337
+ # … 편집 (ui/logic/dbTableRefs/points) …
299
338
  ch specs update <specId> --file /tmp/s.json --no-version
300
339
 
301
340
  # 4) SQA 시트 항목 — [[2t-decencia-channel-sqa-v2]] §3.5
@@ -397,6 +436,17 @@ jq -r '.data[] | select((.logic // "" | tostring) | test("nullable|timestamp\\b|
397
436
  + §6.3 SSOT 역검사로 마무리
398
437
  ```
399
438
 
439
+ ### A': 엔티티 상태머신(stateMachines) 변경
440
+ ```
441
+ 1) ch db-tables update <id> --state-machine sm.json (생애주기 정의 — 여기가 SSOT)
442
+ 2) (db-schema 인벤토리 대개 무변경)
443
+ 3) spec 점검 — 저장 상태가 spec.logic.stateTransitions에 잘못 복제됐으면 제거(화면 전이만 남김)
444
+ 전이의 specRef가 실존 spec을 가리키는지 확인
445
+ 4) SQA — 상태전이 TC 도출(§4.3.5): 전이별 정상/차단·guard 경계
446
+ 5) (PRD 스킵)
447
+ 6) workStatus
448
+ ```
449
+
400
450
  ### C: 화면 요구사항 변경
401
451
  ```
402
452
  1) (DB 변경 없음 → 스킵)
@@ -2,13 +2,13 @@
2
2
  name: 2t-decencia-channel-cli-v2
3
3
  description: |
4
4
  [2t][v2] 소통채널 CLI(`ch`, npm @decencia/ch-cli) 가이드.
5
- Epic-Story-Task 체계와 db-tables 리소스, --work-status, --id 등 신규 플래그를 망라한다.
5
+ Epic-Story 체계(규모=Story Point)와 db-tables 리소스, --work-status, --id, --points 등 플래그를 망라한다.
6
6
  Use when:
7
7
  (1) spec/db-table을 CLI로 다룰 때,
8
- (2) Task에 description/points/completedAt을 기록할 때,
8
+ (2) spec 규모를 Story Point(`--points`)로 매길 때,
9
9
  (3) ch db-tables 명령(list/get/create/update/delete)을 사용할 때,
10
10
  (4) Spec ID를 JIRA 스타일(`LOGIN-001`)로 직접 지정할 때 (`ch specs create --id`).
11
- version: 1.8.2
11
+ version: 1.11.0
12
12
  ---
13
13
 
14
14
  <!-- ch-version-gate -->
@@ -30,12 +30,12 @@ ch check
30
30
 
31
31
  ## 0. Read-before-Write (BLOCKING)
32
32
 
33
- 문서를 `update`/`set`/`upsert`할 때는 반드시 먼저 해당 문서를 `get`으로 받아 **현재 내용을 확인**한 뒤 수정한다. CLI 플래그는 PATCH 의미라 명시한 필드만 덮어쓰지만, JSON 파일(`--tasks`, `--ui`, `--logic`)은 전체 배열/객체를 통째로 교체하므로 누락 시 데이터 손실 위험.
33
+ 문서를 `update`/`set`/`upsert`할 때는 반드시 먼저 해당 문서를 `get`으로 받아 **현재 내용을 확인**한 뒤 수정한다. CLI 플래그는 PATCH 의미라 명시한 필드만 덮어쓰지만, JSON 파일(`--ui`, `--logic`, 그리고 deprecated `--tasks`)은 전체 배열/객체를 통째로 교체하므로 누락 시 데이터 손실 위험. (`--points`는 스칼라라 그 값만 바뀐다.)
34
34
 
35
35
  ```bash
36
36
  ch specs get <specId> --json > /tmp/before.json # 1) 현재 상태 백업/확인
37
- # 2) JSON 편집 (tasks/ui/logic 등)
38
- ch specs update <specId> --tasks /tmp/tasks.json # 3) 수정 적용
37
+ # 2) JSON 편집 (ui/logic 등)
38
+ ch specs update <specId> --logic /tmp/logic.json # 3) 수정 적용
39
39
  ```
40
40
 
41
41
  ## 1. spec 명령
@@ -47,7 +47,7 @@ ch specs create \
47
47
  --device web,app --domain user --type 인증 --permission 비회원 \
48
48
  --content "이메일/비밀번호 로그인" \
49
49
  --epic 인증 \
50
- --tasks ./tasks.json \
50
+ --points 5 \
51
51
  --ui ./ui.json \
52
52
  --logic ./logic.json \
53
53
  --db-tables tbl_users,tbl_sessions \
@@ -61,13 +61,27 @@ ch specs create --id LOGIN-001 \
61
61
 
62
62
  # 수정: 지정한 플래그만 PATCH. --no-version 시 버전 기록 생략.
63
63
  ch specs update <specId> --epic 인증 --no-version
64
- ch specs update <specId> --tasks ./tasks.json
64
+ ch specs update <specId> --points 8 --no-version
65
65
  ch specs update <specId> --work-status ./status.md
66
66
  ```
67
67
 
68
68
  > `--id`의 자세한 네이밍 규칙·예외·자동 다음 번호 산정 스크립트는 [[2t-decencia-channel-spec-v2]] §1.5 참조.
69
69
 
70
- ### Task JSON (`--tasks`)
70
+ ### Story Point (`--points`)
71
+
72
+ Story(=spec)를 Task로 쪼개지 않는다. 규모는 spec 단위 Story Point 한 값으로 매긴다.
73
+
74
+ ```bash
75
+ ch specs create --name "로그인" --device web --domain user --type 인증 --points 5
76
+ ch specs update <specId> --points 8 --no-version
77
+ ```
78
+
79
+ - `--points <n>` — 0 이상의 숫자(보통 피보나치 1/2/3/5/8/13). 진행률(%)·완료율은 없다 — 완료 여부는 `status`로.
80
+ - Sprint 용량 = Σ spec.points ([[2t-decencia-channel-sprint-builder-v2]]). 규모 산정 가이드는 [[2t-decencia-channel-spec-v2]] §3.
81
+
82
+ ### Task JSON (`--tasks`) — [deprecated]
83
+
84
+ `spec.tasks[]`(체크리스트)는 deprecated다. 신규 spec은 `--points`를 쓴다. `--tasks`는 하위호환으로 계속 통과되지만 사용 시 경고가 뜬다. 기존 spec 유지용으로만 참고.
71
85
 
72
86
  ```json
73
87
  [
@@ -75,7 +89,6 @@ ch specs update <specId> --work-status ./status.md
75
89
  "id": "",
76
90
  "title": "로그인 폼 구현",
77
91
  "done": false,
78
- "assignee": "박준하",
79
92
  "points": 5,
80
93
  "description": "## 세부\n- React Hook Form\n- Yup validation"
81
94
  },
@@ -83,17 +96,12 @@ ch specs update <specId> --work-status ./status.md
83
96
  "id": "",
84
97
  "title": "유효성 검증",
85
98
  "done": true,
86
- "points": 2,
87
99
  "completedAt": "2026-05-29T08:00:00.000Z"
88
100
  }
89
101
  ]
90
102
  ```
91
103
 
92
- 서버 자동 처리:
93
- - `id` 비어 있으면 UUID 부여
94
- - `done: true` & `completedAt` 미지정 → 현재 ISO 부여
95
- - `done: false` → `completedAt` 제거
96
- - 전체 progress(%) 자동 재계산
104
+ 서버 자동 처리(하위호환): `id` 비어 있으면 UUID 부여, `done: true` & `completedAt` 미지정 → 현재 ISO 부여, `done: false` → `completedAt` 제거.
97
105
 
98
106
  ### UI JSON (`--ui`)
99
107
 
@@ -109,21 +117,33 @@ ch specs update <specId> --work-status ./status.md
109
117
 
110
118
  ### Logic JSON (`--logic`)
111
119
 
120
+ 신구조 — `dataFlowScenarios`(시나리오 → 순서 있는 단계 → 단계별 refs):
121
+
112
122
  ```json
113
123
  {
114
- "dataFlow": "이메일/비밀번호 → Firebase Auth → ID Token",
115
- "dataFlowRefs": [
116
- { "tableId": "tbl_users", "field": "lastLoginAt", "direction": "WRITE", "note": "로그인 시 갱신" }
124
+ "dataFlowScenarios": [
125
+ {
126
+ "name": "로그인 성공",
127
+ "description": "회원이 이메일과 비밀번호로 로그인에 성공하면 인증 토큰을 발급하고 마지막 로그인 시각을 갱신한다.",
128
+ "steps": [
129
+ { "description": "클라이언트가 입력한 이메일과 비밀번호를 인증 서버로 전송한다." },
130
+ {
131
+ "description": "인증에 성공하면 사용자 문서의 마지막 로그인 시각을 갱신한다.",
132
+ "refs": [
133
+ { "tableId": "tbl_users", "field": "lastLoginAt", "direction": "WRITE", "note": "로그인 성공 시 갱신" }
134
+ ]
135
+ }
136
+ ]
137
+ }
117
138
  ],
118
139
  "stateTransitions": "| 현재 | 입력 | 다음 |\n|---|---|---|\n| idle | submit | loading |",
119
- "businessRules": "- 5회 실패 시 1시간 잠금",
120
- "businessRuleRefs": [
121
- { "tableId": "tbl_users", "field": "lockedUntil" }
122
- ]
140
+ "businessRules": "- 5회 실패 시 1시간 잠금"
123
141
  }
124
142
  ```
125
143
 
126
- `dataFlowRefs` / `businessRuleRefs`는 **db-tables의 tableId(+선택 field)를 가리키는 구조적 참조**다(SSOT — 스키마 정의는 db-tables에만). 서버가 실존 검증: 없는 tableId/컬럼이면 400. `direction`은 `READ|WRITE|READWRITE`. SQA 항목도 동일 취지로 `--db-tables`(쉼표 구분)를 가진다 — [[2t-decencia-channel-sqa-v2]].
144
+ - `dataFlowScenarios[]`=시나리오, 각 시나리오의 `steps[]`=순서 있는 단계, 각 단계의 `refs[]`=**db-tables의 tableId(+선택 field)를 가리키는 구조적 참조**(SSOT — 스키마 정의는 db-tables에만). 서버가 실존 검증: 없는 tableId/컬럼이면 400. `direction`은 `READ|WRITE|READWRITE`.
145
+ - 단계 `description`은 필드명 나열이 아니라 **완결 문장**으로 — [[2t-decencia-channel-spec-v2]] §5.
146
+ - 하위호환: 레거시 `dataFlow`(문자열)/`dataFlowRefs[]`/`businessRuleRefs[]`도 그대로 수용된다(점진 마이그레이션). SQA 항목도 동일 취지로 `--db-tables`(쉼표 구분)를 가진다 — [[2t-decencia-channel-sqa-v2]].
127
147
 
128
148
  ### dbTableRefs (`--db-tables`)
129
149
 
@@ -197,6 +217,7 @@ ch db-tables delete <tableId> # 참조 spec의 dbTableRefs도
197
217
 
198
218
  ## 4. 흔한 함정
199
219
 
200
- - `--tasks` JSON 통째 교체이므로 일부만 수정해도 **전체 배열 전달 필수**. 반드시 `ch specs get`으로 받아 편집.
220
+ - `--ui`/`--logic`(및 deprecated `--tasks`) JSON 통째 교체이므로 일부만 수정해도 **전체 전달 필수**. 반드시 `ch specs get`으로 받아 편집. (`--points`는 스칼라라 안전.)
221
+ - Story를 Task로 쪼개지 않는다 — 규모는 `--points`로 매긴다. `--tasks`는 deprecated(하위호환).
201
222
  - `--db-tables ""`는 모두 해제. 단일 ID 유지하려면 그 ID만 명시.
202
223
  - `relatedSpecIds`를 JSON에 명시해도 서버가 무시 (자동 관리 영역).
@@ -8,7 +8,7 @@ description: |
8
8
  (2) DB 테이블 단위로 컬럼·인덱스·보안규칙을 등록·수정할 때,
9
9
  (3) 새 테이블을 추가하면서 전체 인벤토리도 함께 업데이트할 때,
10
10
  (4) spec.dbTableRefs와 양방향 동기화가 필요한 작업 시.
11
- version: 1.8.2
11
+ version: 1.11.0
12
12
  ---
13
13
 
14
14
  <!-- ch-version-gate -->
@@ -33,7 +33,7 @@ ch check
33
33
  | 리소스 | CLI | 저장 단위 | 담당 |
34
34
  |---|---|---|---|
35
35
  | **전체 스펙 문서** (`/db-schema`) | `ch db-schema get/set/versions` | 프로젝트당 **마크다운 1건** | 데이터 모델 개요·ERD·테이블 인벤토리·네이밍·마이그레이션 노트 |
36
- | **테이블 문서** (`/db-tables/{tableId}`) | `ch db-tables list/get/create/update/delete` | 테이블당 **JSON 1건** | 컬럼 정의·인덱스·보안 규칙·역할 매트릭스 |
36
+ | **테이블 문서** (`/db-tables/{tableId}`) | `ch db-tables list/get/create/update/delete` | 테이블당 **JSON 1건** | 컬럼 정의·인덱스·보안 규칙·역할 매트릭스·엔티티 상태머신 |
37
37
 
38
38
  > 둘은 보완 관계다. 전체 스펙 문서는 **읽는 사람을 위한 설계 의도**, 테이블 문서는 **기계가 검증·동기화할 수 있는 정형 데이터**.
39
39
 
@@ -46,7 +46,7 @@ PRD·spec·SQA 등 다른 어떤 산출물에도 스키마를 **정의**하지
46
46
 
47
47
  | 구분 | 무엇인가 | 살 수 있는 곳 |
48
48
  |---|---|---|
49
- | **스키마 정의** | 컬럼 `type`·`nullable`·`default`·길이·`enum 허용값`, 인덱스, 보안규칙, 역할 매트릭스 | **`db-tables`(per-table)만** |
49
+ | **스키마 정의** | 컬럼 `type`·`nullable`·`default`·길이·`enum 허용값`, 인덱스, 보안규칙, 역할 매트릭스, **엔티티 상태머신(stateMachines)** | **`db-tables`(per-table)만** |
50
50
  | **스키마 개요** | 테이블 *이름*·한 줄 설명·관계(ERD)·네이밍/정책 | `db-schema`(전체 문서) — 테이블 *단위*까지만, 컬럼 정의는 적지 않음 |
51
51
  | **스키마 참조** | "이 테이블/컬럼을 R/W한다"는 *이름 언급* + 링크 | spec·SQA — **이름으로 참조만**, 정의는 절대 금지. [[2t-decencia-channel-spec-v2]] |
52
52
  | (없음) | — | **PRD에는 스키마/테이블 목록을 두지 않는다.** 기능 관점만. [[2t-decencia-channel-prd-v2]] |
@@ -70,7 +70,7 @@ ch db-tables get <tableId> --json > /tmp/tbl.json
70
70
  ch db-tables update <tableId> --columns /tmp/cols.json
71
71
  ```
72
72
 
73
- `--content` 통째 교체, `--columns/--indexes/--security` 각각 통째 교체. 기존 내용 보존하려면 반드시 `get` 후 편집.
73
+ `--content` 통째 교체, `--columns/--indexes/--security/--state-machine` 각각 통째 교체. 기존 내용 보존하려면 반드시 `get` 후 편집.
74
74
 
75
75
  ---
76
76
 
@@ -202,15 +202,17 @@ ch db-schema set --content /tmp/schema.md
202
202
  ## 4. CLI 명령
203
203
 
204
204
  ```bash
205
- ch db-tables list # 표: name | id | columns | indexes | security | specs
206
- ch db-tables get <tableId> --json
207
- ch db-tables create --name users --description "회원" \
208
- --columns ./columns.json --indexes ./indexes.json --security ./security.json
209
- ch db-tables update <tableId> --security ./new-security.json
205
+ ch db-tables list # 표: name | id | columns | indexes | stateMachines | security | specs
206
+ ch db-tables get <tableId> --json # stateMachines 포함 전체 반환
207
+ ch db-tables create --name orders --description "주문" \
208
+ --columns ./columns.json --indexes ./indexes.json --security ./security.json \
209
+ --state-machine ./state-machines.json
210
+ ch db-tables update <tableId> --state-machine ./state-machines.json
210
211
  ch db-tables delete <tableId> # 참조 spec의 dbTableRefs도 서버에서 자동 정리
211
212
  ```
212
213
 
213
214
  `relatedSpecIds`는 사용자 입력 금지(서버 자동 관리).
215
+ `--columns/--indexes/--security/--state-machine`은 각각 파일 통째 교체 — 부분 수정은 `get` 후 편집.
214
216
 
215
217
  ## 5. JSON 템플릿
216
218
 
@@ -273,13 +275,72 @@ description 활용 권장:
273
275
 
274
276
  `read/write/delete`는 `true | false | 문자열 조건(예: "own", "auth.uid == doc.userId")` 자유 입력.
275
277
 
276
- ### 5-4. 테이블 단위 작성 체크리스트
278
+ ### 5-4. state-machines.json 엔티티 상태머신
279
+
280
+ 엔티티가 **저장되는 상태 컬럼**을 가지면(주문 `status`, 배포 `phase` 등) 그 생애주기를 여기에 정의한다. 화면의 휘발성 UI 전이(idle/loading 등)는 여기 말고 spec.logic.stateTransitions에 적는다 — [[2t-decencia-channel-spec-v2]] §5-3.
281
+
282
+ `StateMachine[]` — 한 테이블에 상태머신 여러 개 가능(컬럼별).
283
+
284
+ ```json
285
+ [
286
+ {
287
+ "name": "주문 상태",
288
+ "columnRef": "status",
289
+ "description": "주문 문서 status 컬럼의 생애주기",
290
+ "states": [
291
+ {"value": "pending", "label": "대기", "initial": true},
292
+ {"value": "paid", "label": "결제완료"},
293
+ {"value": "shipped", "label": "배송중"},
294
+ {"value": "delivered", "label": "배송완료", "terminal": true},
295
+ {"value": "cancelled", "label": "취소", "terminal": true}
296
+ ],
297
+ "transitions": [
298
+ {"from": "pending", "to": "paid", "event": "pay"},
299
+ {"from": "paid", "to": "shipped", "event": "ship", "specRef": "ORDER-002"},
300
+ {"from": "shipped", "to": "delivered", "event": "deliver"},
301
+ {"from": "pending", "to": "cancelled", "event": "cancel", "guard": "미결제 상태만"}
302
+ ]
303
+ }
304
+ ]
305
+ ```
306
+
307
+ 필드:
308
+
309
+ | 필드 | 필수 | 의미 |
310
+ |---|---|---|
311
+ | `name` | 필수 | 상태머신 이름 |
312
+ | `columnRef` | 선택 | 이 상태가 담기는 컬럼 이름. columns에 실재해야 함(ch-api 검증) |
313
+ | `states[].value` | 필수 | 상태값. 머신 안에서 유일 |
314
+ | `states[].label` | 선택 | 표시명 |
315
+ | `states[].initial` | 선택 | 진입 상태. 머신당 **1개 이하** |
316
+ | `states[].terminal` | 선택 | 종료 상태 |
317
+ | `transitions[].from`/`to` | 필수 | states의 `value` 중 하나(참조 무결성) |
318
+ | `transitions[].event` | 필수 | 전이를 일으키는 이벤트/액션 |
319
+ | `transitions[].guard` | 선택 | 전이 조건 |
320
+ | `transitions[].effect` | 선택 | 전이 사이드이펙트 |
321
+ | `transitions[].specRef` | 선택 | 이 전이를 구현하는 기능명세 ID. 실존해야 함(없으면 경고, 저장은 허용) |
322
+
323
+ 규칙:
324
+ - `transitions`는 필수지만 **빈 배열 허용**(상태만 있고 전이 미정의 가능).
325
+ - shape·참조 무결성 검증은 **ch-api(SSOT)** 가 한다 — CLI는 파일을 그대로 전달(pass-through).
326
+ - 상태값 enum을 columns의 description에도 이중 정의하지 말 것. 생애주기의 SSOT는 stateMachines다.
327
+
328
+ #### spec dataFlow와의 상호참조 (`columnRef` ↔ `toState`)
329
+
330
+ spec은 이 상태머신을 **`columnRef`로 잇는다**. spec의 dataFlow ref에서 `field`가 이 머신의 `columnRef`와 같으면, 그 ref는 이 엔티티의 상태 접근이다. 그 ref가 WRITE면 `toState`에 목표 상태(이 머신 `states[].value` 중 하나)를 적는다 — [[2t-decencia-channel-spec-v2]] §6-2-1.
331
+
332
+ - **링크 방향**: db-tables가 상태를 **정의**(states·transitions)하고, spec은 `tableId` + `field`(=columnRef) + `toState`로 **참조**만 한다.
333
+ - `ch specs get`은 이 머신 요약(states·transitions)을 spec ref 옆에 **읽기 시점에 인라인**해 준다. 그래서 상태 목록을 spec에 베낄 필요가 없다.
334
+ - 상태 enum을 columns.description이나 spec에 이중 정의하지 말 것 — 생애주기의 SSOT는 stateMachines다.
335
+
336
+ ### 5-5. 테이블 단위 작성 체크리스트
277
337
 
278
338
  - [ ] 모든 PK/FK가 columns에 명시되어 있는가
279
339
  - [ ] 시간 필드(createdAt/updatedAt/deletedAt) 컨벤션 준수
280
340
  - [ ] 비정규화 캐시는 description에 출처 명시
281
341
  - [ ] 보안 규칙의 roles 매트릭스가 실제 content와 일치
282
342
  - [ ] 자주 쓰는 쿼리에 맞는 인덱스 있음
343
+ - [ ] 상태 컬럼이 있으면 stateMachines로 생애주기를 정의했는가(initial 1개·transition from/to가 states에 존재)
283
344
  - [ ] 전체 스펙 문서 §4 인벤토리에도 등록되었는가
284
345
 
285
346
  ---
@@ -310,7 +371,8 @@ description 활용 권장:
310
371
 
311
372
  ## 8. 흔한 함정
312
373
 
313
- - columns/indexes/securityRules JSON은 **전체 교체**. 일부 수정 시 반드시 `get` 후 편집.
374
+ - columns/indexes/securityRules/stateMachines JSON은 **전체 교체**. 일부 수정 시 반드시 `get` 후 편집.
375
+ - 상태 컬럼의 생애주기(주문 status 등)를 spec.logic.stateTransitions에 적으면 SSOT 위반 — 저장되는 엔티티 상태는 db-tables.stateMachines가 SSOT. spec의 stateTransitions는 화면 휘발성 전이만.
314
376
  - 전체 스펙 문서의 `--content`도 통째 교체. 작은 수정도 `get` 후 부분 편집해 통째 전송.
315
377
  - `relatedSpecIds`를 JSON에 명시해도 서버가 무시 (자동 관리 영역).
316
378
  - 새 테이블을 만들고 전체 스펙 인벤토리 갱신을 잊으면, 문서 읽는 사람이 그 테이블의 존재를 모른다.
@@ -2,7 +2,7 @@
2
2
  name: 2t-decencia-channel-github-issue
3
3
  description: gh CLI로 현재 repo에 GitHub 이슈를 작성한다. 이슈 1개=유형 1개(feat/fix/bug), 표준 본문 양식(작업내용·관련 명세 백링크·완료조건)으로 발행. "이슈 만들어줘"/"깃헙 이슈로 등록해줘" 요청 시, 또는 소통채널 기획·명세 작업 후 처리할 일들을 GitHub 이슈로 옮길 때 사용. 멱등성/중복방지·확인단계는 다루지 않음(요청대로 바로 생성).
4
4
  allowed-tools: Bash(gh:*) Bash(git:*) Read Write
5
- version: 1.8.2
5
+ version: 1.11.0
6
6
  ---
7
7
 
8
8
  <!-- ch-version-gate -->
@@ -2,7 +2,7 @@
2
2
  name: 2t-decencia-channel-orchestrator
3
3
  description: 소통채널 작업의 단일 진입점(디스패처). 사용자 요청을 듣고 7종(bootstrap·terraformer·change-manager·github-issue·issue-coder·pr-merger·sprint-runner) 중 적절한 곳으로 라우팅하고, 필요하면 여러 단계를 순차 오케스트레이션한다. 대화형 스킬은 메인 세션에서 Skill로, 자율 에이전트는 Agent로 위임. "소통채널 작업 해줘"·"이거 어떻게 처리하지?"처럼 무엇부터 할지 모를 때, 또는 신규기획/코드편입/기획변경/이슈/구현/머지/스프린트실행 어디로든 시작할 때.
4
4
  allowed-tools: Skill Agent Read Bash(ch:*) Bash(gh:*) Bash(git:*)
5
- version: 1.8.2
5
+ version: 1.11.0
6
6
  ---
7
7
 
8
8
  <!-- ch-version-gate -->
@@ -2,12 +2,12 @@
2
2
  name: 2t-decencia-channel-prd-v2
3
3
  description: |
4
4
  [2t][v2] 소통채널 PRD 작성/수정 가이드.
5
- Epic-Story-Task 체계와의 연결성에 초점 (PRD의 Epic 절 → spec의 --epic).
5
+ Epic-Story 체계와의 연결성에 초점 (PRD의 Epic 절 → spec의 --epic).
6
6
  Use when:
7
7
  (1) PRD를 작성하거나 갱신할 때,
8
- (2) PRD 구성에서 Epic/스토리/태스크 구조를 후속 spec 작성에 매핑할 때,
8
+ (2) PRD 구성에서 Epic/스토리 구조를 후속 spec 작성에 매핑할 때,
9
9
  (3) Read-before-Write 규칙으로 PRD content를 안전하게 수정해야 할 때.
10
- version: 1.8.2
10
+ version: 1.11.0
11
11
  ---
12
12
 
13
13
  <!-- ch-version-gate -->
@@ -25,15 +25,19 @@ ch check
25
25
  구버전 스킬·CLI 사용을 막기 위한 게이트다. 건너뛰지 말 것.
26
26
 
27
27
 
28
- # PRD — Epic-Story-Task 매핑 가이드
28
+ # PRD — Epic-Story 매핑 가이드
29
29
 
30
- ## 🔒 PRD의 범위 — 기능 관점만 (스키마/구현 금지)
30
+ ## 🔒 PRD의 범위 — 개요만 (상세 설명·스키마·구현 금지)
31
31
 
32
- PRD는 **무엇을 왜 만드는가(기능·가치·범위)**만 다룬다. **데이터 모델·테이블 목록·컬럼·스키마는 PRD 두지 않는다.**
32
+ PRD는 **무엇을 왜 만드는가(기능·가치·범위) 개요**만 다룬다. 상세한 동작 설명·데이터 흐름·예외 처리는 PRD 아니라 **spec**에 둔다.
33
33
 
34
- - DB 스키마의 SSOT는 `db-schema`/`db-tables`다 [[2t-decencia-channel-db-schema-v2]]. PRD 베껴 넣으면 변경 양쪽이 어긋난다.
34
+ - **PRD = 개요, spec = 상세.** PRD에는 Epic/Story가 무엇을 위한 것인지 짧게 적는다. "어떻게 동작하는가"의 구체 서술은 spec(`logic.dataFlowScenarios`·`businessRules`)에 둔다.
35
+ - **데이터 모델·테이블 목록·컬럼·스키마는 PRD에 두지 않는다.** DB 스키마의 SSOT는 `db-schema`/`db-tables`다 — [[2t-decencia-channel-db-schema-v2]]. PRD에 베껴 넣으면 변경 시 양쪽이 어긋난다.
35
36
  - 구현 방식(테이블 구조, 인덱스, 저장 위치 등)을 **암시조차 하지 않는다**. 데이터 요구사항은 "어떤 정보가 필요하다" 수준의 *기능 서술*로만 적고, 정형 모델은 db-schema/spec이 책임진다.
36
- - 독자가 데이터 모델을 보고 싶으면 DB 스키마 문서(웹 "DB 스키마" 탭)로 간다 PRD가 그 사본을 들고 있지 않는다.
37
+ - 같은 설명을 PRD와 spec 양쪽에 적으면 변경 어긋난다. **상세 설명은 spec 곳에만** 둔다.
38
+ - 독자가 동작 상세를 보고 싶으면 spec으로, 데이터 모델을 보고 싶으면 DB 스키마 문서(웹 "DB 스키마" 탭)로 간다 — PRD가 그 사본을 들고 있지 않는다.
39
+
40
+ > spec의 상세 설명은 **완결 문장**으로 쓴다(필드명·단어 나열 금지) — [[2t-decencia-channel-spec-v2]] §5. PRD는 그 상세를 옮겨 적지 않고 한 줄 개요로만 가리킨다.
37
41
 
38
42
  ## 0. Read-before-Write
39
43
 
@@ -45,7 +49,7 @@ ch prd get --json > /tmp/prd.json # 1) 현재 본문 추출
45
49
  ch prd set --content /tmp/prd-edited.md # 2) 통째 교체 (버전 기록됨)
46
50
  ```
47
51
 
48
- ## 1. 권장 목차 (Epic-Story-Task 추적성)
52
+ ## 1. 권장 목차 (Epic-Story 추적성)
49
53
 
50
54
  ```markdown
51
55
  # 프로젝트명 PRD
@@ -2,7 +2,7 @@
2
2
  name: 2t-decencia-channel-project-bootstrap
3
3
  description: 신규 기획을 사용자와 대화하며 step-by-step으로 소통채널 프로젝트로 만드는 오케스트레이터 스킬. (1)대화로 PRD 완성 → 승인 → (2)페이지명세(spec)+DB스키마 → 승인 → (3)SQA 시트. 각 단계 사용자 승인 게이트, 작성 디테일은 기존 2t-decencia-channel-*-v2 스킬을 적극 재사용. "새 기획 만들어줘"·"프로젝트 처음부터 세팅" 류 요청 시.
4
4
  allowed-tools: Bash(ch:*) Bash(git:*) Read Write
5
- version: 1.8.2
5
+ version: 1.11.0
6
6
  ---
7
7
 
8
8
  <!-- ch-version-gate -->
@@ -38,7 +38,7 @@ ch check
38
38
  - **이미 프로젝트가 있으면 생략**: cwd의 `.ch-project`가 있거나 사용자가 기존 프로젝트를 지정하면 그걸 사용.
39
39
  - 없으면 새로 생성:
40
40
  - `ch projects create` 로 생성.
41
- - **schema 설정**(⚠️ 빠지면 웹 UI가 빈 칼럼으로 보임) + **specVersion=2**(Epic-Story-Task).
41
+ - **schema 설정**(⚠️ 빠지면 웹 UI가 빈 칼럼으로 보임) + **specVersion=2**(Epic-Story·Story Point).
42
42
  - cwd에 `.ch-project`(`{"projectId":"..."}`) 기록 → 이후 issue-coder/머저가 사용.
43
43
 
44
44
  ## 1. PRD (대화형)
@@ -48,7 +48,7 @@ ch check
48
48
 
49
49
  ## 2. 페이지명세(spec) + DB 스키마
50
50
  *(PRD 승인 후에만)*
51
- - **페이지명세 = spec**(ui 메타 포함): `2t-decencia-channel-spec-v2`로 PRD의 Epic/Story → spec 작성(ui: route/file/figmaNodeId·logic·dbTableRefs·tasks).
51
+ - **페이지명세 = spec**(ui 메타 포함): `2t-decencia-channel-spec-v2`로 PRD의 Epic/Story → spec 작성(ui: route/file/figmaNodeId·logic·dbTableRefs·points).
52
52
  - **DB 스키마**: `2t-decencia-channel-db-schema-v2`로 db-schema(ERD/정책) + db-tables(컬럼·인덱스·보안규칙). `spec.dbTableRefs` ↔ `dbTable.relatedSpecIds` 양방향.
53
53
  - → **[게이트] 사용자 확인.** OK면 3단계.
54
54