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,234 @@
1
+ ---
2
+ name: security
3
+ description: 보안 검토 및 취약점 탐지 가이드라인
4
+ user-invocable: false
5
+ ---
6
+
7
+ <skill name="security">
8
+ <purpose>OWASP Top 10 기반 보안 취약점 탐지 및 방지 가이드라인</purpose>
9
+
10
+ <owasp-top-10>
11
+ <vulnerability id="1" name="Injection">
12
+ <description>SQL, NoSQL, OS, LDAP Injection</description>
13
+ <example type="bad">
14
+ <![CDATA[
15
+ const query = `SELECT * FROM users WHERE email = '${email}'`;
16
+ ]]>
17
+ </example>
18
+ <example type="good">
19
+ <![CDATA[
20
+ // Parameterized Query
21
+ const query = 'SELECT * FROM users WHERE email = $1';
22
+ await db.query(query, [email]);
23
+
24
+ // ORM 사용
25
+ await userRepository.findOne({ where: { email } });
26
+ ]]>
27
+ </example>
28
+ </vulnerability>
29
+
30
+ <vulnerability id="2" name="Broken Authentication">
31
+ <description>취약한 인증 메커니즘</description>
32
+ <example type="bad">
33
+ <![CDATA[
34
+ if (password.length >= 4) { ... }
35
+ ]]>
36
+ </example>
37
+ <example type="good">
38
+ <![CDATA[
39
+ const passwordPolicy = {
40
+ minLength: 12,
41
+ requireUppercase: true,
42
+ requireLowercase: true,
43
+ requireNumber: true,
44
+ requireSpecialChar: true,
45
+ };
46
+
47
+ const hash = await bcrypt.hash(password, 12);
48
+ ]]>
49
+ </example>
50
+ </vulnerability>
51
+
52
+ <vulnerability id="3" name="Sensitive Data Exposure">
53
+ <description>민감 정보 노출</description>
54
+ <example type="bad">
55
+ <![CDATA[
56
+ logger.info('User login', { email, password });
57
+ return { ...user }; // passwordHash 포함
58
+ ]]>
59
+ </example>
60
+ <example type="good">
61
+ <![CDATA[
62
+ logger.info('User login', { email, password: '***' });
63
+ return userToDto(user); // passwordHash 제외
64
+ ]]>
65
+ </example>
66
+ </vulnerability>
67
+
68
+ <vulnerability id="5" name="Broken Access Control">
69
+ <description>부적절한 접근 제어</description>
70
+ <example type="bad">
71
+ <![CDATA[
72
+ app.get('/api/users/:id', async (req, res) => {
73
+ const user = await userService.getUser(req.params.id);
74
+ return res.json(user);
75
+ });
76
+ ]]>
77
+ </example>
78
+ <example type="good">
79
+ <![CDATA[
80
+ app.get('/api/users/:id', authenticate, authorize('user:read'), async (req, res) => {
81
+ if (req.user.id !== req.params.id && !req.user.isAdmin) {
82
+ throw new ForbiddenError();
83
+ }
84
+ const user = await userService.getUser(req.params.id);
85
+ return res.json(user);
86
+ });
87
+ ]]>
88
+ </example>
89
+ </vulnerability>
90
+
91
+ <vulnerability id="6" name="Security Misconfiguration">
92
+ <description>보안 설정 오류</description>
93
+ <example type="good">
94
+ <![CDATA[
95
+ // 보안 헤더 설정
96
+ app.use(helmet());
97
+ app.use(helmet.contentSecurityPolicy({
98
+ directives: {
99
+ defaultSrc: ["'self'"],
100
+ scriptSrc: ["'self'"],
101
+ },
102
+ }));
103
+
104
+ // CORS 제한
105
+ app.use(cors({
106
+ origin: ['https://allowed-domain.com'],
107
+ credentials: true,
108
+ }));
109
+ ]]>
110
+ </example>
111
+ </vulnerability>
112
+
113
+ <vulnerability id="7" name="Cross-Site Scripting (XSS)">
114
+ <description>XSS 공격</description>
115
+ <example type="bad">
116
+ <![CDATA[
117
+ element.innerHTML = userInput;
118
+ ]]>
119
+ </example>
120
+ <example type="good">
121
+ <![CDATA[
122
+ element.textContent = userInput;
123
+
124
+ // 또는 이스케이프 처리
125
+ import { escape } from 'lodash';
126
+ element.innerHTML = escape(userInput);
127
+ ]]>
128
+ </example>
129
+ </vulnerability>
130
+
131
+ <vulnerability id="8" name="Insecure Deserialization">
132
+ <description>안전하지 않은 역직렬화</description>
133
+ <example type="bad">
134
+ <![CDATA[
135
+ const data = JSON.parse(userInput);
136
+ processData(data);
137
+ ]]>
138
+ </example>
139
+ <example type="good">
140
+ <![CDATA[
141
+ import { z } from 'zod';
142
+ const schema = z.object({
143
+ name: z.string().max(100),
144
+ age: z.number().min(0).max(150),
145
+ });
146
+ const data = schema.parse(JSON.parse(userInput));
147
+ ]]>
148
+ </example>
149
+ </vulnerability>
150
+
151
+ <vulnerability id="9" name="Using Components with Known Vulnerabilities">
152
+ <description>취약한 컴포넌트 사용</description>
153
+ <example type="good">
154
+ <![CDATA[
155
+ # 정기적 취약점 스캔
156
+ npm audit
157
+ npm audit fix
158
+
159
+ # 의존성 업데이트
160
+ npm outdated
161
+ npm update
162
+ ]]>
163
+ </example>
164
+ </vulnerability>
165
+
166
+ <vulnerability id="10" name="Insufficient Logging">
167
+ <description>불충분한 로깅 및 모니터링</description>
168
+ <example type="good">
169
+ <![CDATA[
170
+ logger.warn('Login failed', {
171
+ email,
172
+ ip: req.ip,
173
+ userAgent: req.headers['user-agent'],
174
+ timestamp: new Date().toISOString(),
175
+ });
176
+
177
+ if (failedAttempts >= 5) {
178
+ logger.error('Possible brute force attack', { email, ip: req.ip });
179
+ await lockAccount(email);
180
+ }
181
+ ]]>
182
+ </example>
183
+ </vulnerability>
184
+ </owasp-top-10>
185
+
186
+ <additional-checks>
187
+ <check name="시크릿 관리">
188
+ <must-not>하드코딩: const apiKey = 'sk-1234567890';</must-not>
189
+ <must>환경변수: const apiKey = process.env.API_KEY;</must>
190
+ <must>시크릿 매니저: await secretManager.getSecret('api-key');</must>
191
+ </check>
192
+
193
+ <check name="Rate Limiting">
194
+ <example>
195
+ <![CDATA[
196
+ import rateLimit from 'express-rate-limit';
197
+
198
+ const limiter = rateLimit({
199
+ windowMs: 15 * 60 * 1000, // 15분
200
+ max: 100, // 최대 100 요청
201
+ message: 'Too many requests',
202
+ });
203
+
204
+ app.use('/api/', limiter);
205
+ ]]>
206
+ </example>
207
+ </check>
208
+
209
+ <check name="CSRF 방지">
210
+ <example>
211
+ <![CDATA[
212
+ import csrf from 'csurf';
213
+ app.use(csrf({ cookie: true }));
214
+
215
+ // 폼에 토큰 포함
216
+ <input type="hidden" name="_csrf" value="{{csrfToken}}">
217
+ ]]>
218
+ </example>
219
+ </check>
220
+ </additional-checks>
221
+
222
+ <checklist>
223
+ <item priority="critical">SQL/NoSQL Injection 방지</item>
224
+ <item priority="critical">강력한 인증 구현</item>
225
+ <item priority="critical">민감 정보 보호</item>
226
+ <item priority="critical">접근 제어 구현</item>
227
+ <item priority="high">XSS 방지</item>
228
+ <item priority="high">입력 검증</item>
229
+ <item priority="high">의존성 취약점 확인</item>
230
+ <item priority="medium">보안 로깅 구현</item>
231
+ <item priority="medium">시크릿 안전하게 관리</item>
232
+ <item priority="medium">Rate Limiting 적용</item>
233
+ </checklist>
234
+ </skill>
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: testing
3
+ description: 테스트 작성 규칙 및 패턴 가이드라인
4
+ user-invocable: false
5
+ ---
6
+
7
+ <skill name="testing">
8
+ <purpose>품질 보장을 위한 테스트 작성 가이드라인</purpose>
9
+
10
+ <tdd-cycle>
11
+ <step order="1" name="Red">실패하는 테스트 작성</step>
12
+ <step order="2" name="Green">테스트를 통과하는 최소 코드 작성</step>
13
+ <step order="3" name="Refactor">코드 정리 (테스트는 계속 통과)</step>
14
+ </tdd-cycle>
15
+
16
+ <test-pyramid>
17
+ <level name="Unit" quantity="다수">빠름, 격리됨, 저비용</level>
18
+ <level name="Integration" quantity="중간">컴포넌트 간 연동</level>
19
+ <level name="E2E" quantity="소수">전체 흐름, 고비용</level>
20
+ </test-pyramid>
21
+
22
+ <given-when-then>
23
+ <example>
24
+ <![CDATA[
25
+ describe('UserService', () => {
26
+ describe('createUser', () => {
27
+ it('should create a new user when email is unique', async () => {
28
+ // Given: 테스트 조건 설정
29
+ const userRepository = createMockRepository();
30
+ userRepository.findByEmail.mockResolvedValue(null);
31
+ const service = new UserService(userRepository);
32
+
33
+ // When: 테스트 대상 실행
34
+ const result = await service.createUser({
35
+ email: 'test@example.com',
36
+ password: 'password123',
37
+ });
38
+
39
+ // Then: 결과 검증
40
+ expect(result.email).toBe('test@example.com');
41
+ expect(userRepository.save).toHaveBeenCalledTimes(1);
42
+ });
43
+
44
+ it('should throw error when email already exists', async () => {
45
+ // Given
46
+ const userRepository = createMockRepository();
47
+ userRepository.findByEmail.mockResolvedValue({ id: '1', email: 'test@example.com' });
48
+ const service = new UserService(userRepository);
49
+
50
+ // When & Then
51
+ await expect(
52
+ service.createUser({ email: 'test@example.com', password: 'password123' })
53
+ ).rejects.toThrow('EMAIL_ALREADY_EXISTS');
54
+ });
55
+ });
56
+ });
57
+ ]]>
58
+ </example>
59
+ </given-when-then>
60
+
61
+ <test-categories>
62
+ <category name="Happy Path">
63
+ <description>정상적인 흐름 테스트</description>
64
+ <example>
65
+ <![CDATA[
66
+ it('should return user when valid id is provided', async () => {
67
+ const user = await userService.getUser('valid-id');
68
+ expect(user).toBeDefined();
69
+ expect(user.id).toBe('valid-id');
70
+ });
71
+ ]]>
72
+ </example>
73
+ </category>
74
+ <category name="Edge Cases">
75
+ <description>경계 조건 테스트</description>
76
+ <cases>
77
+ <case>빈 문자열</case>
78
+ <case>최대 길이 입력</case>
79
+ <case>0 값</case>
80
+ <case>null 입력</case>
81
+ </cases>
82
+ </category>
83
+ <category name="Error Cases">
84
+ <description>오류 상황 테스트</description>
85
+ <example>
86
+ <![CDATA[
87
+ it('should throw NotFoundError when user does not exist', async () => {
88
+ await expect(userService.getUser('non-existent-id'))
89
+ .rejects
90
+ .toThrow(NotFoundError);
91
+ });
92
+ ]]>
93
+ </example>
94
+ </category>
95
+ </test-categories>
96
+
97
+ <coverage-standards>
98
+ <metric name="Line Coverage" minimum="80%" recommended="90%"/>
99
+ <metric name="Branch Coverage" minimum="70%" recommended="80%"/>
100
+ <metric name="Function Coverage" minimum="80%" recommended="90%"/>
101
+ </coverage-standards>
102
+
103
+ <naming-pattern>
104
+ <format>should {expected behavior} when {condition}</format>
105
+ <examples>
106
+ <example type="good">should return null when user is not found</example>
107
+ <example type="good">should throw ValidationError when email is invalid</example>
108
+ <example type="good">should increment retry count when request fails</example>
109
+ <example type="bad">test getUser</example>
110
+ <example type="bad">user not found</example>
111
+ <example type="bad">works correctly</example>
112
+ </examples>
113
+ </naming-pattern>
114
+
115
+ <mock-guidelines>
116
+ <when-to-mock>
117
+ <case>외부 서비스 (DB, API, 파일시스템)</case>
118
+ <case>비결정적 요소 (시간, 랜덤)</case>
119
+ <case>느린 작업</case>
120
+ </when-to-mock>
121
+ <example>
122
+ <![CDATA[
123
+ // Repository Mock
124
+ const mockUserRepository: jest.Mocked<UserRepository> = {
125
+ findById: jest.fn(),
126
+ findByEmail: jest.fn(),
127
+ save: jest.fn(),
128
+ delete: jest.fn(),
129
+ };
130
+
131
+ // 시간 Mock
132
+ jest.useFakeTimers();
133
+ jest.setSystemTime(new Date('2024-01-01'));
134
+ ]]>
135
+ </example>
136
+ </mock-guidelines>
137
+
138
+ <checklist>
139
+ <item>Given-When-Then 패턴을 따르는가</item>
140
+ <item>Happy path, Edge case, Error case가 있는가</item>
141
+ <item>테스트 이름이 명확한가</item>
142
+ <item>커버리지 기준을 충족하는가</item>
143
+ <item>Mock이 적절히 사용되었는가</item>
144
+ <item>테스트가 독립적인가 (순서 무관)</item>
145
+ </checklist>
146
+ </skill>