timsquad 3.3.0 → 3.5.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 (196) hide show
  1. package/README.ko.md +288 -0
  2. package/README.md +158 -151
  3. package/dist/commands/compile.d.ts +3 -0
  4. package/dist/commands/compile.d.ts.map +1 -0
  5. package/dist/commands/compile.js +170 -0
  6. package/dist/commands/compile.js.map +1 -0
  7. package/dist/commands/daemon.d.ts.map +1 -1
  8. package/dist/commands/daemon.js +95 -5
  9. package/dist/commands/daemon.js.map +1 -1
  10. package/dist/commands/full.js +1 -0
  11. package/dist/commands/full.js.map +1 -1
  12. package/dist/commands/git/pr.js +6 -5
  13. package/dist/commands/git/pr.js.map +1 -1
  14. package/dist/commands/git/release.js +2 -7
  15. package/dist/commands/git/release.js.map +1 -1
  16. package/dist/commands/improve.js +2 -2
  17. package/dist/commands/improve.js.map +1 -1
  18. package/dist/commands/init.d.ts.map +1 -1
  19. package/dist/commands/init.js +12 -3
  20. package/dist/commands/init.js.map +1 -1
  21. package/dist/commands/log.d.ts.map +1 -1
  22. package/dist/commands/log.js +2 -2
  23. package/dist/commands/log.js.map +1 -1
  24. package/dist/commands/metrics.d.ts.map +1 -1
  25. package/dist/commands/metrics.js +6 -2
  26. package/dist/commands/metrics.js.map +1 -1
  27. package/dist/commands/retro.js +8 -8
  28. package/dist/commands/retro.js.map +1 -1
  29. package/dist/commands/session.js +3 -3
  30. package/dist/commands/session.js.map +1 -1
  31. package/dist/commands/skills.d.ts +12 -0
  32. package/dist/commands/skills.d.ts.map +1 -0
  33. package/dist/commands/skills.js +228 -0
  34. package/dist/commands/skills.js.map +1 -0
  35. package/dist/commands/status.js +1 -1
  36. package/dist/commands/status.js.map +1 -1
  37. package/dist/commands/upgrade.d.ts.map +1 -1
  38. package/dist/commands/upgrade.js +23 -1
  39. package/dist/commands/upgrade.js.map +1 -1
  40. package/dist/daemon/entry.js +3 -3
  41. package/dist/daemon/entry.js.map +1 -1
  42. package/dist/daemon/event-queue.d.ts.map +1 -1
  43. package/dist/daemon/event-queue.js +2 -2
  44. package/dist/daemon/event-queue.js.map +1 -1
  45. package/dist/daemon/index.d.ts +4 -2
  46. package/dist/daemon/index.d.ts.map +1 -1
  47. package/dist/daemon/index.js +214 -52
  48. package/dist/daemon/index.js.map +1 -1
  49. package/dist/daemon/jsonl-watcher.d.ts +1 -0
  50. package/dist/daemon/jsonl-watcher.d.ts.map +1 -1
  51. package/dist/daemon/jsonl-watcher.js.map +1 -1
  52. package/dist/daemon/meta-cache.d.ts +1 -0
  53. package/dist/daemon/meta-cache.d.ts.map +1 -1
  54. package/dist/daemon/meta-cache.js +9 -0
  55. package/dist/daemon/meta-cache.js.map +1 -1
  56. package/dist/daemon/session-notes.d.ts +33 -0
  57. package/dist/daemon/session-notes.d.ts.map +1 -0
  58. package/dist/daemon/session-notes.js +74 -0
  59. package/dist/daemon/session-notes.js.map +1 -0
  60. package/dist/daemon/session-state.d.ts +27 -0
  61. package/dist/daemon/session-state.d.ts.map +1 -0
  62. package/dist/daemon/session-state.js +165 -0
  63. package/dist/daemon/session-state.js.map +1 -0
  64. package/dist/daemon/shutdown.d.ts.map +1 -1
  65. package/dist/daemon/shutdown.js +9 -1
  66. package/dist/daemon/shutdown.js.map +1 -1
  67. package/dist/index.js +4 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/lib/agent-generator.d.ts +4 -0
  70. package/dist/lib/agent-generator.d.ts.map +1 -1
  71. package/dist/lib/agent-generator.js +52 -3
  72. package/dist/lib/agent-generator.js.map +1 -1
  73. package/dist/lib/compile-rules.d.ts +66 -0
  74. package/dist/lib/compile-rules.d.ts.map +1 -0
  75. package/dist/lib/compile-rules.js +114 -0
  76. package/dist/lib/compile-rules.js.map +1 -0
  77. package/dist/lib/compiler.d.ts +105 -0
  78. package/dist/lib/compiler.d.ts.map +1 -0
  79. package/dist/lib/compiler.js +368 -0
  80. package/dist/lib/compiler.js.map +1 -0
  81. package/dist/lib/config.d.ts +1 -0
  82. package/dist/lib/config.d.ts.map +1 -1
  83. package/dist/lib/config.js +8 -1
  84. package/dist/lib/config.js.map +1 -1
  85. package/dist/lib/project.d.ts.map +1 -1
  86. package/dist/lib/project.js +8 -3
  87. package/dist/lib/project.js.map +1 -1
  88. package/dist/lib/skill-generator.d.ts.map +1 -1
  89. package/dist/lib/skill-generator.js +22 -1
  90. package/dist/lib/skill-generator.js.map +1 -1
  91. package/dist/lib/template.d.ts.map +1 -1
  92. package/dist/lib/template.js +6 -0
  93. package/dist/lib/template.js.map +1 -1
  94. package/dist/types/config.d.ts +1 -0
  95. package/dist/types/config.d.ts.map +1 -1
  96. package/dist/types/config.js +12 -1
  97. package/dist/types/config.js.map +1 -1
  98. package/dist/types/project.d.ts +1 -1
  99. package/dist/types/project.d.ts.map +1 -1
  100. package/dist/types/project.js +2 -0
  101. package/dist/types/project.js.map +1 -1
  102. package/package.json +4 -4
  103. package/templates/base/agents/base/tsq-architect.md +2 -2
  104. package/templates/base/agents/overlays/domain/mobile/_common.md +13 -0
  105. package/templates/base/knowledge/checklists/plan-quality.md +31 -0
  106. package/templates/base/knowledge/checklists/stability-verification.md +14 -0
  107. package/templates/base/skills/controller/SKILL.md +111 -0
  108. package/templates/base/skills/controller/references/README.md +35 -0
  109. package/templates/base/skills/controller/rules/README.md +18 -0
  110. package/templates/base/skills/mobile/dart/SKILL.md +69 -0
  111. package/templates/base/skills/mobile/dart/rules/async-patterns.md +112 -0
  112. package/templates/base/skills/mobile/dart/rules/code-style.md +96 -0
  113. package/templates/base/skills/mobile/dart/rules/null-safety.md +84 -0
  114. package/templates/base/skills/mobile/dart/rules/type-system.md +111 -0
  115. package/templates/base/skills/mobile/flutter/SKILL.md +89 -0
  116. package/templates/base/skills/mobile/flutter/ci-cd/SKILL.md +82 -0
  117. package/templates/base/skills/mobile/flutter/ci-cd/references/ci-cd-pipeline.md +314 -0
  118. package/templates/base/skills/mobile/flutter/ci-cd/rules/code-signing.md +106 -0
  119. package/templates/base/skills/mobile/flutter/ci-cd/rules/codemagic-setup.md +116 -0
  120. package/templates/base/skills/mobile/flutter/ci-cd/rules/fastlane-setup.md +105 -0
  121. package/templates/base/skills/mobile/flutter/ci-cd/rules/github-actions.md +112 -0
  122. package/templates/base/skills/mobile/flutter/ci-cd/rules/store-deployment.md +106 -0
  123. package/templates/base/skills/mobile/flutter/ci-cd/rules/versioning.md +107 -0
  124. package/templates/base/skills/mobile/flutter/i18n/SKILL.md +78 -0
  125. package/templates/base/skills/mobile/flutter/i18n/references/i18n-architecture.md +225 -0
  126. package/templates/base/skills/mobile/flutter/i18n/rules/arb-files.md +182 -0
  127. package/templates/base/skills/mobile/flutter/i18n/rules/locale-switching.md +226 -0
  128. package/templates/base/skills/mobile/flutter/i18n/rules/localization-setup.md +137 -0
  129. package/templates/base/skills/mobile/flutter/i18n/rules/plural-gender.md +159 -0
  130. package/templates/base/skills/mobile/flutter/i18n/rules/text-direction.md +199 -0
  131. package/templates/base/skills/mobile/flutter/monitoring/SKILL.md +81 -0
  132. package/templates/base/skills/mobile/flutter/monitoring/references/monitoring-architecture.md +269 -0
  133. package/templates/base/skills/mobile/flutter/monitoring/rules/analytics.md +227 -0
  134. package/templates/base/skills/mobile/flutter/monitoring/rules/crashlytics-setup.md +195 -0
  135. package/templates/base/skills/mobile/flutter/monitoring/rules/logging.md +258 -0
  136. package/templates/base/skills/mobile/flutter/monitoring/rules/performance-monitoring.md +248 -0
  137. package/templates/base/skills/mobile/flutter/monitoring/rules/sentry-integration.md +249 -0
  138. package/templates/base/skills/mobile/flutter/networking/SKILL.md +88 -0
  139. package/templates/base/skills/mobile/flutter/networking/references/api-client-architecture.md +305 -0
  140. package/templates/base/skills/mobile/flutter/networking/rules/caching.md +212 -0
  141. package/templates/base/skills/mobile/flutter/networking/rules/connectivity.md +213 -0
  142. package/templates/base/skills/mobile/flutter/networking/rules/dio-setup.md +159 -0
  143. package/templates/base/skills/mobile/flutter/networking/rules/error-handling.md +209 -0
  144. package/templates/base/skills/mobile/flutter/networking/rules/interceptors.md +205 -0
  145. package/templates/base/skills/mobile/flutter/networking/rules/retrofit-patterns.md +194 -0
  146. package/templates/base/skills/mobile/flutter/push-notifications/SKILL.md +87 -0
  147. package/templates/base/skills/mobile/flutter/push-notifications/references/notification-architecture.md +340 -0
  148. package/templates/base/skills/mobile/flutter/push-notifications/references/platform-setup.md +286 -0
  149. package/templates/base/skills/mobile/flutter/push-notifications/rules/background-processing.md +308 -0
  150. package/templates/base/skills/mobile/flutter/push-notifications/rules/deep-linking.md +217 -0
  151. package/templates/base/skills/mobile/flutter/push-notifications/rules/fcm-setup.md +164 -0
  152. package/templates/base/skills/mobile/flutter/push-notifications/rules/local-notifications.md +262 -0
  153. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-handling.md +210 -0
  154. package/templates/base/skills/mobile/flutter/push-notifications/rules/notification-permissions.md +246 -0
  155. package/templates/base/skills/mobile/flutter/push-notifications/rules/rich-notifications.md +320 -0
  156. package/templates/base/skills/mobile/flutter/references/freezed-patterns.md +162 -0
  157. package/templates/base/skills/mobile/flutter/references/project-structure.md +170 -0
  158. package/templates/base/skills/mobile/flutter/rules/animations.md +112 -0
  159. package/templates/base/skills/mobile/flutter/rules/architecture.md +121 -0
  160. package/templates/base/skills/mobile/flutter/rules/navigation-routing.md +117 -0
  161. package/templates/base/skills/mobile/flutter/rules/performance.md +112 -0
  162. package/templates/base/skills/mobile/flutter/rules/platform-adaptive.md +126 -0
  163. package/templates/base/skills/mobile/flutter/rules/state-management.md +110 -0
  164. package/templates/base/skills/mobile/flutter/rules/testing.md +131 -0
  165. package/templates/base/skills/mobile/flutter/rules/widget-conventions.md +122 -0
  166. package/templates/base/skills/mobile/flutter/security/SKILL.md +86 -0
  167. package/templates/base/skills/mobile/flutter/security/references/mobile-security-checklist.md +168 -0
  168. package/templates/base/skills/mobile/flutter/security/rules/api-key-protection.md +206 -0
  169. package/templates/base/skills/mobile/flutter/security/rules/authentication.md +248 -0
  170. package/templates/base/skills/mobile/flutter/security/rules/data-protection.md +271 -0
  171. package/templates/base/skills/mobile/flutter/security/rules/obfuscation.md +213 -0
  172. package/templates/base/skills/mobile/flutter/security/rules/secure-storage.md +171 -0
  173. package/templates/base/skills/mobile/flutter/security/rules/ssl-pinning.md +197 -0
  174. package/templates/base/skills/stability-verification/SKILL.md +64 -0
  175. package/templates/base/skills/stability-verification/references/release-checklist.md +34 -0
  176. package/templates/base/skills/stability-verification/references/security-fix-patterns.md +112 -0
  177. package/templates/base/skills/stability-verification/rules/verification-layers.md +67 -0
  178. package/templates/base/skills/stability-verification/rules/verification-workflow.md +69 -0
  179. package/templates/base/skills/stability-verification/scripts/verify.sh +294 -0
  180. package/templates/platforms/claude-code/CLAUDE.md.template +25 -0
  181. package/templates/platforms/claude-code/rules/build-gate.md +28 -0
  182. package/templates/platforms/claude-code/rules/completion-verification.md +30 -0
  183. package/templates/platforms/claude-code/rules/context-monitor.md +23 -0
  184. package/templates/platforms/claude-code/rules/plan-review.md +45 -0
  185. package/templates/platforms/claude-code/rules/quality-guards.md +43 -0
  186. package/templates/platforms/claude-code/rules/session-notes.md +18 -0
  187. package/templates/platforms/claude-code/rules/skill-suggest.md +27 -0
  188. package/templates/platforms/claude-code/scripts/build-gate.sh +73 -0
  189. package/templates/platforms/claude-code/scripts/completion-guard.sh +93 -0
  190. package/templates/platforms/claude-code/scripts/phase-guard.sh +79 -0
  191. package/templates/platforms/claude-code/scripts/safe-guard.sh +83 -0
  192. package/templates/platforms/claude-code/scripts/skill-rules.json +85 -0
  193. package/templates/platforms/claude-code/scripts/skill-suggest.sh +105 -0
  194. package/templates/platforms/claude-code/settings.json +111 -3
  195. package/templates/project-types/mobile-app/config.yaml +123 -0
  196. package/templates/project-types/mobile-app/process/workflow.xml +191 -0
@@ -0,0 +1,248 @@
1
+ ---
2
+ title: Performance Monitoring & Custom Traces
3
+ impact: HIGH
4
+ impactDescription: "성능 미측정 → 느린 화면 방치, ANR/프리징 원인 파악 불가"
5
+ tags: firebase-performance, trace, http-metric, frame, startup
6
+ ---
7
+
8
+ ## Performance Monitoring & Custom Traces
9
+
10
+ **Impact: HIGH (성능 미측정 → 느린 화면 방치, ANR/프리징 원인 파악 불가)**
11
+
12
+ Firebase Performance Monitoring 초기화, custom trace, HTTP metric 수집,
13
+ 프레임 렌더링 모니터링, 앱 시작 시간 추적.
14
+
15
+ ### 의존성
16
+
17
+ ```yaml
18
+ # pubspec.yaml
19
+ dependencies:
20
+ firebase_core: ^3.8.0
21
+ firebase_performance: ^0.10.0
22
+ firebase_performance_dio: ^0.6.0 # dio 사용 시
23
+ ```
24
+
25
+ ### 초기화
26
+
27
+ ```dart
28
+ Future<void> main() async {
29
+ WidgetsFlutterBinding.ensureInitialized();
30
+ await Firebase.initializeApp();
31
+
32
+ // Performance Monitoring은 별도 초기화 불필요 (Firebase init 시 자동 활성화)
33
+ // 디버그 모드에서 비활성화 (선택)
34
+ if (kDebugMode) {
35
+ await FirebasePerformance.instance
36
+ .setPerformanceCollectionEnabled(false);
37
+ }
38
+
39
+ runApp(const ProviderScope(child: MyApp()));
40
+ }
41
+ ```
42
+
43
+ ### Custom Trace
44
+
45
+ **Incorrect (시작/종료 미매칭):**
46
+ ```dart
47
+ final trace = FirebasePerformance.instance.newTrace('load_data');
48
+ await trace.start();
49
+ await fetchData();
50
+ // trace.stop() 누락 → 데이터 미전송, 메모리 누수
51
+ ```
52
+
53
+ **Correct (안전한 trace 래핑):**
54
+ ```dart
55
+ class PerformanceService {
56
+ final FirebasePerformance _performance = FirebasePerformance.instance;
57
+
58
+ /// 안전한 trace 실행 (자동 start/stop)
59
+ Future<T> trace<T>(
60
+ String name,
61
+ Future<T> Function() operation, {
62
+ Map<String, String>? attributes,
63
+ Map<String, int>? metrics,
64
+ }) async {
65
+ final trace = _performance.newTrace(name);
66
+
67
+ // 속성 설정 (최대 5개)
68
+ attributes?.forEach((key, value) {
69
+ trace.putAttribute(key, value);
70
+ });
71
+
72
+ await trace.start();
73
+ try {
74
+ final result = await operation();
75
+
76
+ // 메트릭 설정 (최대 32개)
77
+ metrics?.forEach((key, value) {
78
+ trace.setMetric(key, value);
79
+ });
80
+
81
+ return result;
82
+ } catch (e) {
83
+ trace.putAttribute('error', e.runtimeType.toString());
84
+ rethrow;
85
+ } finally {
86
+ await trace.stop(); // 항상 stop 보장
87
+ }
88
+ }
89
+ }
90
+
91
+ // 사용 예시
92
+ final perfService = ref.read(performanceServiceProvider);
93
+ final matches = await perfService.trace(
94
+ 'fetch_matches',
95
+ () => matchRepository.getMatches(page: 1),
96
+ attributes: {'sport': 'tennis', 'region': 'sg'},
97
+ metrics: {'page': 1},
98
+ );
99
+ ```
100
+
101
+ ### 핵심 플로우 trace 예시
102
+
103
+ ```dart
104
+ /// 앱 시작 시간 추적
105
+ class AppStartupTrace {
106
+ static Trace? _trace;
107
+
108
+ static Future<void> start() async {
109
+ _trace = FirebasePerformance.instance.newTrace('app_startup');
110
+ await _trace?.start();
111
+ }
112
+
113
+ static Future<void> markMilestone(String name) async {
114
+ _trace?.putAttribute(name, DateTime.now().toIso8601String());
115
+ }
116
+
117
+ static Future<void> stop() async {
118
+ await _trace?.stop();
119
+ _trace = null;
120
+ }
121
+ }
122
+
123
+ // main.dart
124
+ Future<void> main() async {
125
+ await AppStartupTrace.start();
126
+ WidgetsFlutterBinding.ensureInitialized();
127
+ await AppStartupTrace.markMilestone('binding_initialized');
128
+
129
+ await Firebase.initializeApp();
130
+ await AppStartupTrace.markMilestone('firebase_initialized');
131
+
132
+ runApp(const ProviderScope(child: MyApp()));
133
+ }
134
+
135
+ // 첫 화면 렌더링 완료 시
136
+ class HomeScreen extends ConsumerStatefulWidget {
137
+ @override
138
+ ConsumerState<HomeScreen> createState() => _HomeScreenState();
139
+
140
+ @override
141
+ void initState() {
142
+ super.initState();
143
+ WidgetsBinding.instance.addPostFrameCallback((_) {
144
+ AppStartupTrace.stop(); // 첫 프레임 렌더링 완료
145
+ });
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### HTTP Metric (Dio Interceptor)
151
+
152
+ ```dart
153
+ /// Dio에 Performance interceptor 추가
154
+ final dio = Dio()
155
+ ..interceptors.add(
156
+ DioFirebasePerformanceInterceptor(), // firebase_performance_dio
157
+ );
158
+
159
+ /// 또는 수동 HTTP metric
160
+ class PerformanceInterceptor extends Interceptor {
161
+ final Map<String, HttpMetric> _metrics = {};
162
+
163
+ @override
164
+ void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
165
+ final metric = FirebasePerformance.instance.newHttpMetric(
166
+ options.uri.toString(),
167
+ _toHttpMethod(options.method),
168
+ );
169
+ metric.start();
170
+ _metrics[options.hashCode.toString()] = metric;
171
+ handler.next(options);
172
+ }
173
+
174
+ @override
175
+ void onResponse(Response response, ResponseInterceptorHandler handler) {
176
+ final metric = _metrics.remove(response.requestOptions.hashCode.toString());
177
+ if (metric != null) {
178
+ metric
179
+ ..responseContentType = response.headers.value('content-type')
180
+ ..httpResponseCode = response.statusCode
181
+ ..responsePayloadSize = response.data.toString().length;
182
+ metric.stop();
183
+ }
184
+ handler.next(response);
185
+ }
186
+
187
+ @override
188
+ void onError(DioException err, ErrorInterceptorHandler handler) {
189
+ final metric = _metrics.remove(err.requestOptions.hashCode.toString());
190
+ if (metric != null) {
191
+ metric.httpResponseCode = err.response?.statusCode;
192
+ metric.putAttribute('error', err.type.name);
193
+ metric.stop();
194
+ }
195
+ handler.next(err);
196
+ }
197
+
198
+ HttpMethod _toHttpMethod(String method) {
199
+ return switch (method.toUpperCase()) {
200
+ 'GET' => HttpMethod.Get,
201
+ 'POST' => HttpMethod.Post,
202
+ 'PUT' => HttpMethod.Put,
203
+ 'DELETE' => HttpMethod.Delete,
204
+ 'PATCH' => HttpMethod.Patch,
205
+ _ => HttpMethod.Get,
206
+ };
207
+ }
208
+ }
209
+ ```
210
+
211
+ ### 프레임 렌더링 모니터링
212
+
213
+ ```dart
214
+ /// 느린 프레임 감지 (개발/QA용)
215
+ class FrameMonitor {
216
+ static void start() {
217
+ if (!kDebugMode) return;
218
+
219
+ WidgetsBinding.instance.addTimingsCallback((timings) {
220
+ for (final timing in timings) {
221
+ // 60fps 기준: 16.67ms 이상이면 느린 프레임
222
+ final buildDuration = timing.buildDuration.inMilliseconds;
223
+ final rasterDuration = timing.rasterDuration.inMilliseconds;
224
+ final totalDuration = timing.totalSpan.inMilliseconds;
225
+
226
+ if (totalDuration > 16) {
227
+ debugPrint(
228
+ 'Slow frame: total=${totalDuration}ms '
229
+ 'build=${buildDuration}ms raster=${rasterDuration}ms',
230
+ );
231
+ }
232
+ }
233
+ });
234
+ }
235
+ }
236
+ ```
237
+
238
+ ### 규칙
239
+
240
+ - Custom trace → `start()`/`stop()` 반드시 매칭 (try-finally 패턴)
241
+ - Trace 이름 → 소문자 + 언더스코어, 100자 이하
242
+ - Trace 속성 → 최대 5개 (key 32자, value 100자)
243
+ - Trace 메트릭 → 최대 32개 (정수값)
244
+ - HTTP metric → dio interceptor 또는 수동 래핑으로 API 응답 시간 수집
245
+ - 앱 시작 trace → main() 시작 ~ 첫 프레임 렌더링 완료
246
+ - 핵심 플로우 (로그인, 검색, 매치 생성) → custom trace 적용
247
+ - 디버그 모드 → `setPerformanceCollectionEnabled(false)` (선택)
248
+ - 프레임 모니터링 → 16ms (60fps) / 8ms (120fps) 초과 시 경고
@@ -0,0 +1,249 @@
1
+ ---
2
+ title: Sentry Integration & Breadcrumbs
3
+ impact: MEDIUM
4
+ impactDescription: "에러 컨텍스트 부족 → 재현 불가, 크래시 원인 추적 지연"
5
+ tags: sentry, breadcrumb, scope, dsn, error-tracking
6
+ ---
7
+
8
+ ## Sentry Integration & Breadcrumbs
9
+
10
+ **Impact: MEDIUM (에러 컨텍스트 부족 → 재현 불가, 크래시 원인 추적 지연)**
11
+
12
+ sentry_flutter 초기화, DSN 환경별 분리, breadcrumbs 자동/수동 기록,
13
+ scope 설정, Crashlytics와의 공존 전략.
14
+
15
+ ### 의존성
16
+
17
+ ```yaml
18
+ # pubspec.yaml
19
+ dependencies:
20
+ sentry_flutter: ^8.12.0
21
+ sentry_dio: ^8.12.0 # dio HTTP breadcrumbs
22
+ ```
23
+
24
+ ### 초기화
25
+
26
+ **Incorrect (하드코딩된 DSN, 기본 설정만):**
27
+ ```dart
28
+ await SentryFlutter.init((options) {
29
+ options.dsn = 'https://key@sentry.io/123'; // 하드코딩 → 환경 구분 불가
30
+ });
31
+ ```
32
+
33
+ **Correct (환경별 DSN + 상세 설정):**
34
+ ```dart
35
+ Future<void> main() async {
36
+ WidgetsFlutterBinding.ensureInitialized();
37
+ await Firebase.initializeApp();
38
+
39
+ await SentryFlutter.init(
40
+ (options) {
41
+ // DSN 환경별 분리 (--dart-define으로 주입)
42
+ options.dsn = const String.fromEnvironment(
43
+ 'SENTRY_DSN',
44
+ defaultValue: '', // 빈 문자열 → Sentry 비활성화
45
+ );
46
+
47
+ // 환경 태그
48
+ options.environment = const String.fromEnvironment(
49
+ 'ENV',
50
+ defaultValue: 'development',
51
+ );
52
+
53
+ // 샘플링 (프로덕션에서 100%는 비용 과다)
54
+ options.tracesSampleRate = kDebugMode ? 1.0 : 0.3;
55
+ options.profilesSampleRate = kDebugMode ? 1.0 : 0.1;
56
+
57
+ // 릴리스 버전 (소스맵 매핑)
58
+ options.release = '${packageInfo.version}+${packageInfo.buildNumber}';
59
+
60
+ // 디버그 모드
61
+ options.debug = kDebugMode;
62
+
63
+ // 자동 breadcrumb 수집
64
+ options.enableAutoNativeBreadcrumbs = true;
65
+ options.enableAutoPerformanceTracing = true;
66
+
67
+ // PII 전송 비활성화
68
+ options.sendDefaultPii = false;
69
+
70
+ // 에러 필터링 (무시할 에러 타입)
71
+ options.beforeSend = (event, hint) {
72
+ // 네트워크 끊김 에러는 무시
73
+ if (event.throwable is SocketException) return null;
74
+ return event;
75
+ };
76
+ },
77
+ appRunner: () => runApp(
78
+ const ProviderScope(child: MyApp()),
79
+ ),
80
+ );
81
+ }
82
+ ```
83
+
84
+ ### Breadcrumbs
85
+
86
+ ```dart
87
+ class SentryBreadcrumbService {
88
+ /// 네비게이션 breadcrumb (수동 — go_router 연동)
89
+ static void navigationBreadcrumb({
90
+ required String from,
91
+ required String to,
92
+ }) {
93
+ Sentry.addBreadcrumb(Breadcrumb(
94
+ type: 'navigation',
95
+ category: 'navigation',
96
+ data: {'from': from, 'to': to},
97
+ ));
98
+ }
99
+
100
+ /// 사용자 액션 breadcrumb
101
+ static void userActionBreadcrumb({
102
+ required String action,
103
+ required String target,
104
+ Map<String, dynamic>? data,
105
+ }) {
106
+ Sentry.addBreadcrumb(Breadcrumb(
107
+ type: 'user',
108
+ category: 'user.action',
109
+ message: '$action on $target',
110
+ data: data,
111
+ ));
112
+ }
113
+
114
+ /// 상태 변경 breadcrumb
115
+ static void stateBreadcrumb({
116
+ required String category,
117
+ required String message,
118
+ Map<String, dynamic>? data,
119
+ }) {
120
+ Sentry.addBreadcrumb(Breadcrumb(
121
+ type: 'info',
122
+ category: category,
123
+ message: message,
124
+ data: data,
125
+ level: SentryLevel.info,
126
+ ));
127
+ }
128
+ }
129
+
130
+ // go_router NavigatorObserver로 자동 네비게이션 breadcrumb
131
+ class SentryNavigatorObserver extends NavigatorObserver {
132
+ @override
133
+ void didPush(Route route, Route? previousRoute) {
134
+ SentryBreadcrumbService.navigationBreadcrumb(
135
+ from: previousRoute?.settings.name ?? 'unknown',
136
+ to: route.settings.name ?? 'unknown',
137
+ );
138
+ }
139
+
140
+ @override
141
+ void didPop(Route route, Route? previousRoute) {
142
+ SentryBreadcrumbService.navigationBreadcrumb(
143
+ from: route.settings.name ?? 'unknown',
144
+ to: previousRoute?.settings.name ?? 'unknown',
145
+ );
146
+ }
147
+ }
148
+ ```
149
+
150
+ ### Scope 설정
151
+
152
+ ```dart
153
+ /// 사용자 정보 + 태그 설정
154
+ class SentryScopeManager {
155
+ /// 로그인 시 사용자 설정
156
+ static Future<void> setUser({
157
+ required String id,
158
+ String? email,
159
+ String? username,
160
+ Map<String, String>? extras,
161
+ }) async {
162
+ Sentry.configureScope((scope) {
163
+ scope.setUser(SentryUser(
164
+ id: id,
165
+ email: email, // PII — sendDefaultPii: true 필요
166
+ username: username,
167
+ ));
168
+ // 태그 (필터링/검색용)
169
+ scope.setTag('subscription_tier', extras?['tier'] ?? 'free');
170
+ scope.setTag('region', extras?['region'] ?? 'unknown');
171
+ // 추가 데이터 (상세 컨텍스트)
172
+ extras?.forEach((key, value) {
173
+ scope.setExtra(key, value);
174
+ });
175
+ });
176
+ }
177
+
178
+ /// 로그아웃 시 정리
179
+ static Future<void> clearUser() async {
180
+ Sentry.configureScope((scope) {
181
+ scope.setUser(null);
182
+ scope.removeTag('subscription_tier');
183
+ scope.removeTag('region');
184
+ });
185
+ }
186
+ }
187
+ ```
188
+
189
+ ### Dio HTTP Breadcrumbs
190
+
191
+ ```dart
192
+ /// sentry_dio로 HTTP 요청/응답 자동 breadcrumb
193
+ final dio = Dio()
194
+ ..addSentry(); // sentry_dio 확장 메서드
195
+
196
+ // 또는 수동 interceptor
197
+ class SentryDioInterceptor extends Interceptor {
198
+ @override
199
+ void onResponse(Response response, ResponseInterceptorHandler handler) {
200
+ Sentry.addBreadcrumb(Breadcrumb(
201
+ type: 'http',
202
+ category: 'http',
203
+ data: {
204
+ 'url': response.requestOptions.uri.toString(),
205
+ 'method': response.requestOptions.method,
206
+ 'status_code': response.statusCode,
207
+ 'duration_ms': response.requestOptions.extra['start_time'] != null
208
+ ? DateTime.now().difference(
209
+ response.requestOptions.extra['start_time'] as DateTime,
210
+ ).inMilliseconds
211
+ : null,
212
+ },
213
+ ));
214
+ handler.next(response);
215
+ }
216
+ }
217
+ ```
218
+
219
+ ### Crashlytics + Sentry 공존 전략
220
+
221
+ ```
222
+ 역할 분리:
223
+ ┌──────────────┬────────────────────────┬──────────────────────┐
224
+ │ │ Firebase Crashlytics │ Sentry │
225
+ ├──────────────┼────────────────────────┼──────────────────────┤
226
+ │ 강점 │ 크래시 집계, 안정성 % │ 에러 컨텍스트, 검색 │
227
+ │ 크래시 │ O (primary) │ O (backup) │
228
+ │ 비치명 에러 │ O (recordError) │ O (captureException) │
229
+ │ Breadcrumbs │ X (log만 가능) │ O (풍부한 컨텍스트) │
230
+ │ 알림 │ Firebase Console │ Slack/Email 통합 │
231
+ │ 비용 │ 무료 │ 무료 (5K 이벤트/월) │
232
+ └──────────────┴────────────────────────┴──────────────────────┘
233
+
234
+ 공존 시 주의:
235
+ - 에러를 양쪽 모두에 전송하면 이벤트 쿼터 소모 → 역할 기반 필터링
236
+ - Crashlytics = 크래시 대시보드 (안정성 %)
237
+ - Sentry = 에러 디버깅 (breadcrumb, context, 검색)
238
+ ```
239
+
240
+ ### 규칙
241
+
242
+ - DSN → `--dart-define`으로 환경별 주입, 하드코딩 금지
243
+ - `tracesSampleRate` → 프로덕션 0.1~0.3 (비용 절감)
244
+ - `beforeSend` → 불필요한 에러 (SocketException 등) 필터링
245
+ - Breadcrumb → 네비게이션, HTTP, 사용자 액션 자동 기록
246
+ - Scope → 로그인/로그아웃 시 사용자 정보 설정/해제
247
+ - PII → `sendDefaultPii: false` 기본, 필요 시 명시적 활성화
248
+ - Crashlytics 공존 → 각 도구의 강점에 맞는 역할 분리
249
+ - 릴리스 → `options.release` 설정 + 소스맵/디버그 심볼 업로드
@@ -0,0 +1,88 @@
1
+ ---
2
+ name: networking
3
+ description: |
4
+ Flutter 네트워크 통신 가이드라인.
5
+ Dio HTTP 클라이언트, Retrofit 코드 생성, 인터셉터 체인,
6
+ 에러 핸들링, 연결 상태 관리, 캐시 전략.
7
+ version: "1.0.0"
8
+ tags: [flutter, dio, retrofit, http, api, interceptor, connectivity]
9
+ user-invocable: false
10
+ ---
11
+
12
+ # Networking
13
+
14
+ Flutter Dio + Retrofit + Interceptor + Connectivity 통합 가이드.
15
+ HTTP 클라이언트 설정부터 오프라인 캐시까지, API 통신 전체 파이프라인.
16
+
17
+ ## Philosophy
18
+
19
+ - API 클라이언트는 서비스 — Feature-first 구조에서 `core/networking/` 중앙화
20
+ - 에러는 도메인 타입 — DioException을 앱 도메인 에러(NetworkFailure)로 변환
21
+ - 오프라인은 기본 — connectivity 감지 + 캐시 + 재시도를 기본 탑재
22
+ - 인터셉터는 레이어 — 인증, 재시도, 로깅, 에러 변환을 체인으로 분리
23
+
24
+ ## Resources
25
+
26
+ 6개 규칙 + 1개 참조. 네트워크 통신 전체를 커버.
27
+
28
+ | Priority | Type | Resource | Description |
29
+ |----------|------|----------|-------------|
30
+ | CRITICAL | rule | [dio-setup](rules/dio-setup.md) | Dio 싱글톤, BaseOptions, Riverpod Provider |
31
+ | CRITICAL | rule | [interceptors](rules/interceptors.md) | Interceptor chain 순서: Auth → Retry → Logging → Error |
32
+ | CRITICAL | rule | [error-handling](rules/error-handling.md) | DioException → NetworkFailure sealed class, Result 패턴 |
33
+ | HIGH | rule | [retrofit-patterns](rules/retrofit-patterns.md) | @RestApi, 코드 생성, freezed 모델 연동 |
34
+ | HIGH | rule | [connectivity](rules/connectivity.md) | connectivity_plus, 오프라인 큐, 자동 재시도 |
35
+ | MEDIUM | rule | [caching](rules/caching.md) | dio_cache_interceptor, ETag, 오프라인 fallback |
36
+ | — | ref | [api-client-architecture](references/api-client-architecture.md) | 디렉토리 구조, Provider 구성, Repository 연동, Mock 테스트 |
37
+
38
+ ## Quick Rules
39
+
40
+ ### Dio 설정
41
+ - `Dio(BaseOptions(...))` 싱글톤 — Riverpod Provider로 전역 관리
42
+ - `connectTimeout: 15s`, `receiveTimeout: 15s` 기본값
43
+ - `baseUrl` 은 환경별 분리 (dev/staging/prod)
44
+ - `contentType: 'application/json'` 기본 헤더
45
+
46
+ ### 인터셉터 체인
47
+ - 순서: Auth → Retry → Logging → Error Transform
48
+ - Auth: 토큰 주입 + 401 시 리프레시
49
+ - Retry: 지수 백오프, 최대 3회, 5xx/timeout만
50
+ - Logging: debug 빌드에서만 활성화
51
+
52
+ ### 에러 핸들링
53
+ - `DioException` → `NetworkFailure` sealed class 변환
54
+ - `connectionTimeout/sendTimeout` → `TimeoutFailure`
55
+ - `badResponse` → `ServerFailure(statusCode, message)`
56
+ - `connectionError` → `NoConnectionFailure`
57
+ - Repository에서 `Result<T, Failure>` 패턴 반환
58
+
59
+ ### Retrofit 코드 생성
60
+ - `@RestApi(baseUrl: '')` — baseUrl은 Dio에서 관리
61
+ - 응답 모델은 freezed + json_serializable
62
+ - `build_runner watch` 로 개발 중 자동 생성
63
+
64
+ ### 연결 상태
65
+ - `connectivity_plus` → 네트워크 상태 실시간 감지
66
+ - 오프라인 시 요청 큐잉, 연결 복구 시 자동 재시도
67
+ - UI에 오프라인 배너 표시
68
+
69
+ ### 캐시
70
+ - `dio_cache_interceptor` → GET 응답 캐시
71
+ - `ETag`/`Last-Modified` 헤더 활용
72
+ - 오프라인 시 캐시 fallback
73
+
74
+ ## Checklist
75
+
76
+ | Priority | Item |
77
+ |----------|------|
78
+ | CRITICAL | Dio 싱글톤 인스턴스 Riverpod Provider로 관리 |
79
+ | CRITICAL | Interceptor 순서: Auth → Retry → Logging → Error Transform |
80
+ | CRITICAL | DioException → 도메인 NetworkFailure 변환 처리 |
81
+ | HIGH | Auth 인터셉터에서 401 → 토큰 리프레시 → 재요청 |
82
+ | HIGH | Retry 인터셉터: 지수 백오프, 5xx/timeout만, 최대 3회 |
83
+ | HIGH | Retrofit @RestApi 응답 타입을 freezed 모델로 정의 |
84
+ | HIGH | connectivity_plus로 오프라인 감지 + UI 배너 |
85
+ | MEDIUM | 오프라인 요청 큐 + 연결 복구 시 자동 재시도 |
86
+ | MEDIUM | GET 응답 캐시 (dio_cache_interceptor) |
87
+ | MEDIUM | 환경별 baseUrl 분리 (dev/staging/prod) |
88
+ | MEDIUM | Release 빌드에서 Logging 인터셉터 비활성화 |