timsquad 2.0.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.
Files changed (181) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +347 -0
  3. package/bin/tsq.js +6 -0
  4. package/dist/commands/feedback.d.ts +3 -0
  5. package/dist/commands/feedback.d.ts.map +1 -0
  6. package/dist/commands/feedback.js +142 -0
  7. package/dist/commands/feedback.js.map +1 -0
  8. package/dist/commands/full.d.ts +3 -0
  9. package/dist/commands/full.d.ts.map +1 -0
  10. package/dist/commands/full.js +87 -0
  11. package/dist/commands/full.js.map +1 -0
  12. package/dist/commands/git/commit.d.ts +3 -0
  13. package/dist/commands/git/commit.d.ts.map +1 -0
  14. package/dist/commands/git/commit.js +88 -0
  15. package/dist/commands/git/commit.js.map +1 -0
  16. package/dist/commands/git/index.d.ts +5 -0
  17. package/dist/commands/git/index.d.ts.map +1 -0
  18. package/dist/commands/git/index.js +5 -0
  19. package/dist/commands/git/index.js.map +1 -0
  20. package/dist/commands/git/pr.d.ts +3 -0
  21. package/dist/commands/git/pr.d.ts.map +1 -0
  22. package/dist/commands/git/pr.js +138 -0
  23. package/dist/commands/git/pr.js.map +1 -0
  24. package/dist/commands/git/release.d.ts +3 -0
  25. package/dist/commands/git/release.d.ts.map +1 -0
  26. package/dist/commands/git/release.js +158 -0
  27. package/dist/commands/git/release.js.map +1 -0
  28. package/dist/commands/git/sync.d.ts +3 -0
  29. package/dist/commands/git/sync.d.ts.map +1 -0
  30. package/dist/commands/git/sync.js +132 -0
  31. package/dist/commands/git/sync.js.map +1 -0
  32. package/dist/commands/init.d.ts +3 -0
  33. package/dist/commands/init.d.ts.map +1 -0
  34. package/dist/commands/init.js +150 -0
  35. package/dist/commands/init.js.map +1 -0
  36. package/dist/commands/log.d.ts +3 -0
  37. package/dist/commands/log.d.ts.map +1 -0
  38. package/dist/commands/log.js +271 -0
  39. package/dist/commands/log.js.map +1 -0
  40. package/dist/commands/metrics.d.ts +3 -0
  41. package/dist/commands/metrics.d.ts.map +1 -0
  42. package/dist/commands/metrics.js +299 -0
  43. package/dist/commands/metrics.js.map +1 -0
  44. package/dist/commands/quick.d.ts +3 -0
  45. package/dist/commands/quick.d.ts.map +1 -0
  46. package/dist/commands/quick.js +136 -0
  47. package/dist/commands/quick.js.map +1 -0
  48. package/dist/commands/retro.d.ts +3 -0
  49. package/dist/commands/retro.d.ts.map +1 -0
  50. package/dist/commands/retro.js +280 -0
  51. package/dist/commands/retro.js.map +1 -0
  52. package/dist/commands/status.d.ts +3 -0
  53. package/dist/commands/status.d.ts.map +1 -0
  54. package/dist/commands/status.js +127 -0
  55. package/dist/commands/status.js.map +1 -0
  56. package/dist/commands/watch.d.ts +3 -0
  57. package/dist/commands/watch.d.ts.map +1 -0
  58. package/dist/commands/watch.js +213 -0
  59. package/dist/commands/watch.js.map +1 -0
  60. package/dist/index.d.ts +3 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +50 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/lib/config.d.ts +34 -0
  65. package/dist/lib/config.d.ts.map +1 -0
  66. package/dist/lib/config.js +108 -0
  67. package/dist/lib/config.js.map +1 -0
  68. package/dist/lib/project.d.ts +47 -0
  69. package/dist/lib/project.d.ts.map +1 -0
  70. package/dist/lib/project.js +191 -0
  71. package/dist/lib/project.js.map +1 -0
  72. package/dist/lib/template.d.ts +33 -0
  73. package/dist/lib/template.d.ts.map +1 -0
  74. package/dist/lib/template.js +151 -0
  75. package/dist/lib/template.js.map +1 -0
  76. package/dist/types/config.d.ts +75 -0
  77. package/dist/types/config.d.ts.map +1 -0
  78. package/dist/types/config.js +66 -0
  79. package/dist/types/config.js.map +1 -0
  80. package/dist/types/feedback.d.ts +59 -0
  81. package/dist/types/feedback.d.ts.map +1 -0
  82. package/dist/types/feedback.js +26 -0
  83. package/dist/types/feedback.js.map +1 -0
  84. package/dist/types/index.d.ts +4 -0
  85. package/dist/types/index.d.ts.map +1 -0
  86. package/dist/types/index.js +5 -0
  87. package/dist/types/index.js.map +1 -0
  88. package/dist/types/project.d.ts +89 -0
  89. package/dist/types/project.d.ts.map +1 -0
  90. package/dist/types/project.js +44 -0
  91. package/dist/types/project.js.map +1 -0
  92. package/dist/utils/colors.d.ts +30 -0
  93. package/dist/utils/colors.d.ts.map +1 -0
  94. package/dist/utils/colors.js +54 -0
  95. package/dist/utils/colors.js.map +1 -0
  96. package/dist/utils/date.d.ts +25 -0
  97. package/dist/utils/date.d.ts.map +1 -0
  98. package/dist/utils/date.js +65 -0
  99. package/dist/utils/date.js.map +1 -0
  100. package/dist/utils/fs.d.ts +49 -0
  101. package/dist/utils/fs.d.ts.map +1 -0
  102. package/dist/utils/fs.js +84 -0
  103. package/dist/utils/fs.js.map +1 -0
  104. package/dist/utils/prompts.d.ts +31 -0
  105. package/dist/utils/prompts.d.ts.map +1 -0
  106. package/dist/utils/prompts.js +95 -0
  107. package/dist/utils/prompts.js.map +1 -0
  108. package/dist/utils/yaml.d.ts +21 -0
  109. package/dist/utils/yaml.d.ts.map +1 -0
  110. package/dist/utils/yaml.js +40 -0
  111. package/dist/utils/yaml.js.map +1 -0
  112. package/package.json +71 -0
  113. package/templates/common/CLAUDE.md.template +254 -0
  114. package/templates/common/claude/agents/tsq-dba.md +290 -0
  115. package/templates/common/claude/agents/tsq-designer.md +304 -0
  116. package/templates/common/claude/agents/tsq-developer.md +118 -0
  117. package/templates/common/claude/agents/tsq-planner.md +90 -0
  118. package/templates/common/claude/agents/tsq-prompter.md +336 -0
  119. package/templates/common/claude/agents/tsq-qa.md +134 -0
  120. package/templates/common/claude/agents/tsq-retro.md +168 -0
  121. package/templates/common/claude/agents/tsq-security.md +190 -0
  122. package/templates/common/claude/skills/architecture/SKILL.md +123 -0
  123. package/templates/common/claude/skills/backend/node/SKILL.md +1015 -0
  124. package/templates/common/claude/skills/coding/SKILL.md +171 -0
  125. package/templates/common/claude/skills/database/prisma/SKILL.md +357 -0
  126. package/templates/common/claude/skills/frontend/nextjs/SKILL.md +279 -0
  127. package/templates/common/claude/skills/frontend/react/SKILL.md +1729 -0
  128. package/templates/common/claude/skills/methodology/bdd/SKILL.md +234 -0
  129. package/templates/common/claude/skills/methodology/ddd/SKILL.md +311 -0
  130. package/templates/common/claude/skills/methodology/tdd/SKILL.md +512 -0
  131. package/templates/common/claude/skills/planning/SKILL.md +90 -0
  132. package/templates/common/claude/skills/security/SKILL.md +234 -0
  133. package/templates/common/claude/skills/testing/SKILL.md +146 -0
  134. package/templates/common/claude/skills/typescript/SKILL.md +435 -0
  135. package/templates/common/config.template.yaml +131 -0
  136. package/templates/common/timsquad/architectures/clean/ARCHITECTURE.md +49 -0
  137. package/templates/common/timsquad/architectures/clean/backend.xml +210 -0
  138. package/templates/common/timsquad/architectures/clean/frontend.xml +148 -0
  139. package/templates/common/timsquad/architectures/fsd/ARCHITECTURE.md +67 -0
  140. package/templates/common/timsquad/architectures/fsd/frontend.xml +288 -0
  141. package/templates/common/timsquad/architectures/hexagonal/ARCHITECTURE.md +60 -0
  142. package/templates/common/timsquad/architectures/hexagonal/backend.xml +300 -0
  143. package/templates/common/timsquad/constraints/competency-framework.xml +501 -0
  144. package/templates/common/timsquad/constraints/ssot-schema.xml +433 -0
  145. package/templates/common/timsquad/feedback/feedback-router.sh +341 -0
  146. package/templates/common/timsquad/feedback/routing-rules.yaml +352 -0
  147. package/templates/common/timsquad/generators/data-design.xml +290 -0
  148. package/templates/common/timsquad/generators/prd.xml +280 -0
  149. package/templates/common/timsquad/generators/requirements.xml +220 -0
  150. package/templates/common/timsquad/generators/service-spec.xml +266 -0
  151. package/templates/common/timsquad/logs/_example.md +81 -0
  152. package/templates/common/timsquad/logs/_template.md +46 -0
  153. package/templates/common/timsquad/patterns/cqrs.xml +127 -0
  154. package/templates/common/timsquad/patterns/event-sourcing.xml +85 -0
  155. package/templates/common/timsquad/patterns/repository.xml +64 -0
  156. package/templates/common/timsquad/process/state-machine.xml +343 -0
  157. package/templates/common/timsquad/process/validation-rules.xml +308 -0
  158. package/templates/common/timsquad/process/workflow-base.xml +202 -0
  159. package/templates/common/timsquad/retrospective/cycle-report.template.md +205 -0
  160. package/templates/common/timsquad/retrospective/metrics/metrics-schema.json +203 -0
  161. package/templates/common/timsquad/retrospective/patterns/failure-patterns.md +199 -0
  162. package/templates/common/timsquad/retrospective/patterns/success-patterns.md +262 -0
  163. package/templates/common/timsquad/retrospective/retrospective-config.xml +294 -0
  164. package/templates/common/timsquad/retrospective/retrospective-state.xml +210 -0
  165. package/templates/common/timsquad/ssot/adr/ADR-000-template.md +121 -0
  166. package/templates/common/timsquad/ssot/adr/ADR-001-example.md +115 -0
  167. package/templates/common/timsquad/ssot/data-design.template.md +132 -0
  168. package/templates/common/timsquad/ssot/deployment-spec.template.md +384 -0
  169. package/templates/common/timsquad/ssot/env-config.template.md +346 -0
  170. package/templates/common/timsquad/ssot/error-codes.template.md +114 -0
  171. package/templates/common/timsquad/ssot/functional-spec.template.md +185 -0
  172. package/templates/common/timsquad/ssot/glossary.template.md +148 -0
  173. package/templates/common/timsquad/ssot/integration-spec.template.md +391 -0
  174. package/templates/common/timsquad/ssot/planning.template.md +94 -0
  175. package/templates/common/timsquad/ssot/prd.template.md +102 -0
  176. package/templates/common/timsquad/ssot/requirements.template.md +117 -0
  177. package/templates/common/timsquad/ssot/security-spec.template.md +309 -0
  178. package/templates/common/timsquad/ssot/service-spec.template.md +194 -0
  179. package/templates/common/timsquad/ssot/test-spec.template.md +264 -0
  180. package/templates/common/timsquad/ssot/ui-ux-spec.template.md +262 -0
  181. package/templates/common/timsquad/state/workspace.xml +217 -0
@@ -0,0 +1,512 @@
1
+ ---
2
+ name: tdd
3
+ description: Test-Driven Development 방법론 가이드라인
4
+ user-invocable: false
5
+ ---
6
+
7
+ <skill name="tdd">
8
+ <purpose>품질 중심 개발을 위한 TDD 방법론 가이드라인 (Kent Beck 기법 포함)</purpose>
9
+
10
+ <philosophy>
11
+ <principle>테스트가 설계를 이끈다</principle>
12
+ <principle>작은 단계로 진행한다</principle>
13
+ <principle>리팩토링은 테스트가 보장한다</principle>
14
+ <principle>불확실할 때는 더 작은 단계로</principle>
15
+ </philosophy>
16
+
17
+ <cycle>
18
+ <step order="1" name="Red" duration="1-2min">
19
+ <action>실패하는 테스트 작성</action>
20
+ <rules>
21
+ <must>테스트가 실패하는 것을 확인</must>
22
+ <must>한 번에 하나의 기능만 테스트</must>
23
+ <must>테스트 이름으로 의도 명확히 표현</must>
24
+ <must-not>구현 코드 먼저 작성</must-not>
25
+ </rules>
26
+ </step>
27
+
28
+ <step order="2" name="Green" duration="3-5min">
29
+ <action>테스트를 통과하는 최소 코드 작성</action>
30
+ <rules>
31
+ <must>테스트 통과만을 목표</must>
32
+ <must>가장 단순한 구현 선택</must>
33
+ <must-not>미래 요구사항 미리 구현</must-not>
34
+ <must-not>완벽한 코드 추구 (아직)</must-not>
35
+ </rules>
36
+ </step>
37
+
38
+ <step order="3" name="Refactor" duration="5-10min">
39
+ <action>코드 정리 (테스트는 계속 통과)</action>
40
+ <rules>
41
+ <must>중복 제거</must>
42
+ <must>네이밍 개선</must>
43
+ <must>구조 개선</must>
44
+ <must>테스트 실행하며 진행</must>
45
+ <must-not>기능 변경</must-not>
46
+ </rules>
47
+ </step>
48
+ </cycle>
49
+
50
+ <!-- Kent Beck의 TDD 기법들 -->
51
+ <techniques>
52
+ <technique name="Fake It ('Til You Make It)">
53
+ <description>하드코딩으로 먼저 통과시킨 후, 점진적으로 일반화</description>
54
+ <when>구현 방법이 불확실할 때, 작은 단계로 진행하고 싶을 때</when>
55
+ <example>
56
+ <![CDATA[
57
+ // Step 1: 테스트 작성
58
+ it('should add two numbers', () => {
59
+ expect(add(2, 3)).toBe(5);
60
+ });
61
+
62
+ // Step 2: Fake It - 하드코딩으로 통과
63
+ function add(a: number, b: number): number {
64
+ return 5; // ✅ 테스트 통과!
65
+ }
66
+
67
+ // Step 3: 테스트 추가로 일반화 강제
68
+ it('should add different numbers', () => {
69
+ expect(add(1, 1)).toBe(2);
70
+ });
71
+
72
+ // Step 4: 이제 일반화 필요
73
+ function add(a: number, b: number): number {
74
+ return a + b; // ✅ 모든 테스트 통과
75
+ }
76
+ ]]>
77
+ </example>
78
+ </technique>
79
+
80
+ <technique name="Triangulation">
81
+ <description>2개 이상의 테스트로 일반화를 강제. 하나의 예시로는 부족할 때 사용</description>
82
+ <when>일반화 방향이 불명확할 때, 엣지 케이스 발견하고 싶을 때</when>
83
+ <example>
84
+ <![CDATA[
85
+ // 할인 계산기 예시
86
+
87
+ // Test 1: 기본 케이스
88
+ it('should return 10% discount for $150', () => {
89
+ expect(calculateDiscount(150)).toBe(15);
90
+ });
91
+
92
+ // 이 시점에서 하드코딩 가능: return 15;
93
+
94
+ // Test 2: 삼각측량 - 다른 값으로 일반화 강제
95
+ it('should return 10% discount for $200', () => {
96
+ expect(calculateDiscount(200)).toBe(20);
97
+ });
98
+
99
+ // 이제 일반화 필수
100
+ function calculateDiscount(total: number): number {
101
+ return total * 0.1;
102
+ }
103
+
104
+ // Test 3: 경계 조건 삼각측량
105
+ it('should return 0 discount for $100 or less', () => {
106
+ expect(calculateDiscount(100)).toBe(0);
107
+ expect(calculateDiscount(50)).toBe(0);
108
+ });
109
+
110
+ // 최종 구현
111
+ function calculateDiscount(total: number): number {
112
+ if (total <= 100) return 0;
113
+ return total * 0.1;
114
+ }
115
+ ]]>
116
+ </example>
117
+ </technique>
118
+
119
+ <technique name="Obvious Implementation">
120
+ <description>구현이 명확하면 바로 작성. Fake It이 과할 때</description>
121
+ <when>구현 방법이 명확할 때, 자신감이 있을 때</when>
122
+ <example>
123
+ <![CDATA[
124
+ // 구현이 명확한 경우
125
+ it('should return full name', () => {
126
+ const user = { firstName: 'John', lastName: 'Doe' };
127
+ expect(getFullName(user)).toBe('John Doe');
128
+ });
129
+
130
+ // Obvious Implementation - 바로 작성
131
+ function getFullName(user: { firstName: string; lastName: string }): string {
132
+ return `${user.firstName} ${user.lastName}`;
133
+ }
134
+
135
+ // 주의: 자신감이 떨어지면 Fake It으로 돌아가기
136
+ // 테스트 실패하면 → 더 작은 단계로 분해
137
+ ]]>
138
+ </example>
139
+ </technique>
140
+
141
+ <technique name="One to Many">
142
+ <description>먼저 단일 항목으로 구현, 그 다음 컬렉션으로 확장</description>
143
+ <when>컬렉션/배열 처리가 필요할 때</when>
144
+ <example>
145
+ <![CDATA[
146
+ // Step 1: 단일 항목
147
+ it('should calculate total for one item', () => {
148
+ const item = { price: 100, quantity: 2 };
149
+ expect(calculateTotal([item])).toBe(200);
150
+ });
151
+
152
+ function calculateTotal(items: Item[]): number {
153
+ const item = items[0];
154
+ return item.price * item.quantity;
155
+ }
156
+
157
+ // Step 2: 여러 항목으로 확장
158
+ it('should calculate total for multiple items', () => {
159
+ const items = [
160
+ { price: 100, quantity: 2 },
161
+ { price: 50, quantity: 1 },
162
+ ];
163
+ expect(calculateTotal(items)).toBe(250);
164
+ });
165
+
166
+ function calculateTotal(items: Item[]): number {
167
+ return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
168
+ }
169
+
170
+ // Step 3: 빈 배열 엣지 케이스
171
+ it('should return 0 for empty cart', () => {
172
+ expect(calculateTotal([])).toBe(0);
173
+ });
174
+ // reduce는 이미 빈 배열 처리함 ✅
175
+ ]]>
176
+ </example>
177
+ </technique>
178
+
179
+ <technique name="Assert First">
180
+ <description>테스트 작성 시 assertion부터 시작, 역방향으로 구성</description>
181
+ <when>테스트 구조가 불명확할 때</when>
182
+ <example>
183
+ <![CDATA[
184
+ // Step 1: Assertion 먼저
185
+ expect(result).toEqual({ id: '1', status: 'completed' });
186
+
187
+ // Step 2: result는 어디서? → 함수 호출
188
+ const result = completeOrder(order);
189
+ expect(result).toEqual({ id: '1', status: 'completed' });
190
+
191
+ // Step 3: order는? → Given 구성
192
+ const order = createOrder({ id: '1', status: 'pending' });
193
+ const result = completeOrder(order);
194
+ expect(result).toEqual({ id: '1', status: 'completed' });
195
+
196
+ // 최종 테스트
197
+ it('should complete pending order', () => {
198
+ // Given
199
+ const order = createOrder({ id: '1', status: 'pending' });
200
+
201
+ // When
202
+ const result = completeOrder(order);
203
+
204
+ // Then
205
+ expect(result).toEqual({ id: '1', status: 'completed' });
206
+ });
207
+ ]]>
208
+ </example>
209
+ </technique>
210
+
211
+ <technique name="Starter Test">
212
+ <description>가장 단순한 테스트로 시작하여 동작하는 코드 확보</description>
213
+ <when>어디서 시작할지 모를 때, 새 기능 시작할 때</when>
214
+ <example>
215
+ <![CDATA[
216
+ // 주문 시스템 시작하기
217
+
218
+ // Bad: 너무 복잡한 첫 테스트
219
+ it('should process order with discount, tax, and shipping', () => { ... });
220
+
221
+ // Good: 가장 단순한 케이스로 시작
222
+ it('should create an order', () => {
223
+ const order = createOrder({ userId: '1' });
224
+ expect(order).toBeDefined();
225
+ expect(order.userId).toBe('1');
226
+ });
227
+
228
+ // 점진적 확장
229
+ it('should add item to order', () => { ... });
230
+ it('should calculate subtotal', () => { ... });
231
+ it('should apply discount', () => { ... });
232
+ it('should calculate tax', () => { ... });
233
+ it('should calculate shipping', () => { ... });
234
+ ]]>
235
+ </example>
236
+ </technique>
237
+
238
+ <technique name="Test Data Builder">
239
+ <description>테스트 데이터 생성을 빌더 패턴으로 추상화</description>
240
+ <when>테스트마다 비슷한 객체 생성이 반복될 때</when>
241
+ <example>
242
+ <![CDATA[
243
+ // Test Data Builder
244
+ class UserBuilder {
245
+ private user: Partial<User> = {
246
+ id: 'default-id',
247
+ email: 'test@example.com',
248
+ name: 'Test User',
249
+ role: 'user',
250
+ };
251
+
252
+ withId(id: string) {
253
+ this.user.id = id;
254
+ return this;
255
+ }
256
+
257
+ withEmail(email: string) {
258
+ this.user.email = email;
259
+ return this;
260
+ }
261
+
262
+ withRole(role: 'admin' | 'user') {
263
+ this.user.role = role;
264
+ return this;
265
+ }
266
+
267
+ build(): User {
268
+ return this.user as User;
269
+ }
270
+ }
271
+
272
+ // 사용 - 테스트 의도가 명확해짐
273
+ it('should allow admin to delete users', () => {
274
+ const admin = new UserBuilder().withRole('admin').build();
275
+ const targetUser = new UserBuilder().withId('target-123').build();
276
+
277
+ expect(deleteUser(admin, targetUser.id)).toBe(true);
278
+ });
279
+
280
+ it('should prevent regular user from deleting users', () => {
281
+ const regularUser = new UserBuilder().withRole('user').build();
282
+ const targetUser = new UserBuilder().withId('target-123').build();
283
+
284
+ expect(() => deleteUser(regularUser, targetUser.id))
285
+ .toThrow('FORBIDDEN');
286
+ });
287
+ ]]>
288
+ </example>
289
+ </technique>
290
+
291
+ <technique name="Tidying">
292
+ <description>리팩토링 전 작은 정리. 변수 추출, 메서드 추출 등</description>
293
+ <when>리팩토링 단계에서, 코드가 지저분할 때</when>
294
+ <example>
295
+ <![CDATA[
296
+ // Before: 지저분한 코드 (테스트는 통과)
297
+ function processOrder(order) {
298
+ if (order.items.reduce((s, i) => s + i.price * i.qty, 0) > 100 && order.user.level === 'gold') {
299
+ return order.items.reduce((s, i) => s + i.price * i.qty, 0) * 0.9;
300
+ }
301
+ return order.items.reduce((s, i) => s + i.price * i.qty, 0);
302
+ }
303
+
304
+ // Tidying Step 1: 변수 추출
305
+ function processOrder(order) {
306
+ const subtotal = order.items.reduce((s, i) => s + i.price * i.qty, 0);
307
+ const isGoldMember = order.user.level === 'gold';
308
+ const qualifiesForDiscount = subtotal > 100 && isGoldMember;
309
+
310
+ if (qualifiesForDiscount) {
311
+ return subtotal * 0.9;
312
+ }
313
+ return subtotal;
314
+ }
315
+
316
+ // Tidying Step 2: 함수 추출
317
+ function calculateSubtotal(items: Item[]): number {
318
+ return items.reduce((sum, item) => sum + item.price * item.qty, 0);
319
+ }
320
+
321
+ function processOrder(order) {
322
+ const subtotal = calculateSubtotal(order.items);
323
+ const qualifiesForDiscount = subtotal > 100 && order.user.level === 'gold';
324
+
325
+ return qualifiesForDiscount ? subtotal * 0.9 : subtotal;
326
+ }
327
+
328
+ // 테스트 계속 통과 확인 후 다음 정리
329
+ ]]>
330
+ </example>
331
+ </technique>
332
+
333
+ <technique name="Learning Test">
334
+ <description>외부 라이브러리/API 학습용 테스트. 이해도 확인 및 문서화</description>
335
+ <when>새 라이브러리 사용 전, API 동작 확인할 때</when>
336
+ <example>
337
+ <![CDATA[
338
+ // Prisma 학습 테스트
339
+ describe('Prisma Learning Tests', () => {
340
+ it('should create and find user', async () => {
341
+ // 내가 이해한 대로 동작하는지 확인
342
+ const user = await prisma.user.create({
343
+ data: { email: 'test@test.com', name: 'Test' },
344
+ });
345
+
346
+ const found = await prisma.user.findUnique({
347
+ where: { id: user.id },
348
+ });
349
+
350
+ expect(found).toEqual(user);
351
+ });
352
+
353
+ it('should return null for non-existent user', async () => {
354
+ // findUnique는 null을 반환하나? 에러를 던지나?
355
+ const found = await prisma.user.findUnique({
356
+ where: { id: 'non-existent' },
357
+ });
358
+
359
+ expect(found).toBeNull(); // null 반환 확인!
360
+ });
361
+
362
+ it('should throw on duplicate email', async () => {
363
+ await prisma.user.create({
364
+ data: { email: 'dup@test.com', name: 'First' },
365
+ });
366
+
367
+ // 중복 이메일은 어떤 에러?
368
+ await expect(
369
+ prisma.user.create({
370
+ data: { email: 'dup@test.com', name: 'Second' },
371
+ })
372
+ ).rejects.toThrow(); // PrismaClientKnownRequestError
373
+ });
374
+ });
375
+ ]]>
376
+ </example>
377
+ </technique>
378
+ </techniques>
379
+
380
+ <real-world-example>
381
+ <title>로그인 기능 TDD로 구현하기</title>
382
+ <scenario>
383
+ <![CDATA[
384
+ // === 1단계: Starter Test ===
385
+ it('should create auth service', () => {
386
+ const authService = new AuthService(mockUserRepo);
387
+ expect(authService).toBeDefined();
388
+ });
389
+
390
+ // === 2단계: Happy Path ===
391
+ it('should return token for valid credentials', async () => {
392
+ // Given
393
+ const mockUser = new UserBuilder()
394
+ .withEmail('user@test.com')
395
+ .withPassword(await hash('password123'))
396
+ .build();
397
+ mockUserRepo.findByEmail.mockResolvedValue(mockUser);
398
+
399
+ // When
400
+ const result = await authService.login('user@test.com', 'password123');
401
+
402
+ // Then
403
+ expect(result.token).toBeDefined();
404
+ expect(result.user.email).toBe('user@test.com');
405
+ });
406
+
407
+ // === 3단계: Error Cases (삼각측량) ===
408
+ it('should throw for non-existent user', async () => {
409
+ mockUserRepo.findByEmail.mockResolvedValue(null);
410
+
411
+ await expect(
412
+ authService.login('nobody@test.com', 'password')
413
+ ).rejects.toThrow('INVALID_CREDENTIALS');
414
+ });
415
+
416
+ it('should throw for wrong password', async () => {
417
+ const mockUser = new UserBuilder()
418
+ .withPassword(await hash('correct-password'))
419
+ .build();
420
+ mockUserRepo.findByEmail.mockResolvedValue(mockUser);
421
+
422
+ await expect(
423
+ authService.login('user@test.com', 'wrong-password')
424
+ ).rejects.toThrow('INVALID_CREDENTIALS');
425
+ });
426
+
427
+ // === 4단계: Edge Cases ===
428
+ it('should throw for locked account', async () => {
429
+ const lockedUser = new UserBuilder()
430
+ .withStatus('locked')
431
+ .build();
432
+ mockUserRepo.findByEmail.mockResolvedValue(lockedUser);
433
+
434
+ await expect(
435
+ authService.login('locked@test.com', 'password')
436
+ ).rejects.toThrow('ACCOUNT_LOCKED');
437
+ });
438
+
439
+ // === 5단계: 구현 (테스트가 이끈 설계) ===
440
+ class AuthService {
441
+ constructor(private userRepo: UserRepository) {}
442
+
443
+ async login(email: string, password: string): Promise<LoginResult> {
444
+ const user = await this.userRepo.findByEmail(email);
445
+
446
+ if (!user) {
447
+ throw new AuthError('INVALID_CREDENTIALS');
448
+ }
449
+
450
+ if (user.status === 'locked') {
451
+ throw new AuthError('ACCOUNT_LOCKED');
452
+ }
453
+
454
+ const isValid = await compare(password, user.password);
455
+ if (!isValid) {
456
+ throw new AuthError('INVALID_CREDENTIALS');
457
+ }
458
+
459
+ const token = this.generateToken(user);
460
+ return { token, user: this.toUserDto(user) };
461
+ }
462
+ }
463
+ ]]>
464
+ </scenario>
465
+ </real-world-example>
466
+
467
+ <anti-patterns>
468
+ <anti-pattern name="Test After">
469
+ <description>구현 후 테스트 작성</description>
470
+ <problem>테스트가 설계에 영향 못 줌, 테스트 커버리지 낮음</problem>
471
+ <solution>테스트 먼저, 실패 확인 후 구현</solution>
472
+ </anti-pattern>
473
+ <anti-pattern name="Too Big Step">
474
+ <description>한 번에 너무 많은 기능 테스트</description>
475
+ <problem>실패 원인 파악 어려움, 피드백 지연</problem>
476
+ <solution>Starter Test로 시작, 점진적 확장</solution>
477
+ </anti-pattern>
478
+ <anti-pattern name="Test the Implementation">
479
+ <description>구현 상세를 테스트 (private 메서드, 내부 상태)</description>
480
+ <problem>리팩토링 시 테스트 깨짐, 유지보수 비용 증가</problem>
481
+ <solution>동작(behavior)을 테스트, public API만 검증</solution>
482
+ </anti-pattern>
483
+ <anti-pattern name="Skipping Refactor">
484
+ <description>Green 후 Refactor 단계 생략</description>
485
+ <problem>기술 부채 누적, 코드 품질 저하</problem>
486
+ <solution>매 사이클마다 Refactor 단계 필수</solution>
487
+ </anti-pattern>
488
+ </anti-patterns>
489
+
490
+ <integration-with-ssot>
491
+ <principle>SSOT 명세 → 테스트 케이스 → 구현</principle>
492
+ <workflow>
493
+ <step>1. service-spec.md에서 API 명세 확인</step>
494
+ <step>2. 명세 기반 테스트 케이스 작성 (Starter Test)</step>
495
+ <step>3. 테스트 실패 확인 (Red)</step>
496
+ <step>4. 명세대로 구현 (Green - Fake It 또는 Obvious)</step>
497
+ <step>5. 리팩토링 (Refactor - Tidying)</step>
498
+ <step>6. 삼각측량으로 엣지 케이스 추가</step>
499
+ </workflow>
500
+ </integration-with-ssot>
501
+
502
+ <checklist>
503
+ <item priority="critical">테스트 먼저 작성했는가</item>
504
+ <item priority="critical">테스트가 실패하는 것을 확인했는가</item>
505
+ <item priority="critical">최소 코드로 통과했는가 (Fake It OK)</item>
506
+ <item priority="critical">리팩토링 단계를 거쳤는가</item>
507
+ <item priority="high">삼각측량으로 일반화했는가</item>
508
+ <item priority="high">엣지 케이스를 테스트했는가</item>
509
+ <item priority="medium">테스트 이름이 의도를 설명하는가</item>
510
+ <item priority="medium">Test Data Builder를 사용했는가</item>
511
+ </checklist>
512
+ </skill>
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: planning
3
+ description: 기획 및 요건 정의 가이드라인
4
+ user-invocable: false
5
+ ---
6
+
7
+ <skill name="planning">
8
+ <purpose>기획 문서 작성 및 요건 정의를 위한 가이드라인</purpose>
9
+
10
+ <principles>
11
+ <principle name="SSOT 구조 준수">
12
+ 상위 문서부터 작성: PRD (Why) → Planning (Overview) → Requirements (What)
13
+ 각 문서는 다음 단계의 입력이 됨
14
+ </principle>
15
+ </principles>
16
+
17
+ <documents>
18
+ <document name="prd.md">
19
+ <role>왜 만드는지, 목표, 성공 지표</role>
20
+ <sections>
21
+ <section required="true">배경 및 목적: 왜 이 제품/기능이 필요한가</section>
22
+ <section required="true">목표 사용자: 누가 사용하는가</section>
23
+ <section required="true">핵심 가치: 어떤 문제를 해결하는가</section>
24
+ <section required="true">성공 지표: 어떻게 성공을 측정할 것인가</section>
25
+ <section required="true">스코프: 포함/제외 범위</section>
26
+ </sections>
27
+ </document>
28
+ <document name="planning.md">
29
+ <role>전체 계획, 마일스톤, 일정</role>
30
+ </document>
31
+ <document name="requirements.md">
32
+ <role>기능/비기능 요건 목록</role>
33
+ </document>
34
+ <document name="functional-spec.md">
35
+ <role>기능 시나리오, 예외처리</role>
36
+ </document>
37
+ </documents>
38
+
39
+ <prd-example>
40
+ <![CDATA[
41
+ ## 1. 배경 및 목적
42
+ 현재 로그인 프로세스에서 이탈률이 30%에 달함.
43
+ 소셜 로그인 도입으로 가입/로그인 허들을 낮추고자 함.
44
+
45
+ ## 2. 목표 사용자
46
+ - 처음 방문하는 신규 사용자
47
+ - 비밀번호를 자주 잊어버리는 사용자
48
+
49
+ ## 3. 핵심 가치
50
+ - 3초 내 로그인 완료
51
+ - 비밀번호 기억 부담 제거
52
+
53
+ ## 4. 성공 지표
54
+ - 로그인 이탈률 30% → 10%
55
+ - 가입 전환율 20% 향상
56
+
57
+ ## 5. 스코프
58
+ 포함: Google, Apple 소셜 로그인
59
+ 제외: Facebook, Twitter (Phase 2)
60
+ ]]>
61
+ </prd-example>
62
+
63
+ <requirements-guide>
64
+ <classification>
65
+ <type name="FR">기능 요건 - 시스템이 해야 하는 것</type>
66
+ <type name="NFR">비기능 요건 - 성능, 보안, 확장성 등</type>
67
+ </classification>
68
+ <principles>
69
+ <principle>SMART: Specific, Measurable, Achievable, Relevant, Time-bound</principle>
70
+ <principle>검증 가능한 형태로 작성</principle>
71
+ <principle>우선순위 명시 (Must/Should/Could/Won't)</principle>
72
+ </principles>
73
+ <template>
74
+ <![CDATA[
75
+ | ID | 분류 | 요건 | 우선순위 | 검증 방법 |
76
+ |----|-----|-----|---------|----------|
77
+ | FR-001 | 인증 | 사용자는 이메일/비밀번호로 로그인할 수 있다 | Must | 테스트 |
78
+ | NFR-001 | 성능 | 로그인 응답 시간 < 500ms | Must | 부하 테스트 |
79
+ ]]>
80
+ </template>
81
+ </requirements-guide>
82
+
83
+ <checklist>
84
+ <item>목표가 명확히 정의되었는가</item>
85
+ <item>스코프가 명확히 구분되었는가</item>
86
+ <item>모든 요건에 우선순위가 있는가</item>
87
+ <item>요건이 검증 가능한가</item>
88
+ <item>이해관계자 승인을 받았는가</item>
89
+ </checklist>
90
+ </skill>