@wooojin/forgen 0.4.8 → 0.4.9

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 (122) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/assets/dev-guide/be/README.md +226 -0
  3. package/assets/dev-guide/be/adapters/build-agents-md.sh +63 -0
  4. package/assets/dev-guide/be/principles/common.md +433 -0
  5. package/assets/dev-guide/be/principles/go.md +469 -0
  6. package/assets/dev-guide/be/principles/node.md +388 -0
  7. package/assets/dev-guide/be/skills/go/be-build/SKILL.md +262 -0
  8. package/assets/dev-guide/be/skills/go/be-perf/SKILL.md +308 -0
  9. package/assets/dev-guide/be/skills/go/be-review/SKILL.md +119 -0
  10. package/assets/dev-guide/be/skills/go/be-security/SKILL.md +362 -0
  11. package/assets/dev-guide/be/skills/node/be-build/SKILL.md +239 -0
  12. package/assets/dev-guide/be/skills/node/be-perf/SKILL.md +272 -0
  13. package/assets/dev-guide/be/skills/node/be-review/SKILL.md +118 -0
  14. package/assets/dev-guide/be/skills/node/be-security/SKILL.md +355 -0
  15. package/assets/dev-guide/be/sources/12factor/INDEX.md +53 -0
  16. package/assets/dev-guide/be/sources/api-design/INDEX.md +56 -0
  17. package/assets/dev-guide/be/sources/ddia/INDEX.md +55 -0
  18. package/assets/dev-guide/be/sources/go-runtime/INDEX.md +62 -0
  19. package/assets/dev-guide/be/sources/node-runtime/INDEX.md +60 -0
  20. package/assets/dev-guide/be/sources/otel/INDEX.md +53 -0
  21. package/assets/dev-guide/be/sources/owasp-api/INDEX.md +52 -0
  22. package/assets/dev-guide/be/sources/postgres/INDEX.md +55 -0
  23. package/assets/dev-guide/be/sources/sre-book/INDEX.md +48 -0
  24. package/assets/dev-guide/fe/README.md +197 -0
  25. package/assets/dev-guide/fe/adapters/build-agents-md.sh +63 -0
  26. package/assets/dev-guide/fe/adapters/refresh.sh +68 -0
  27. package/assets/dev-guide/fe/principles/common.md +160 -0
  28. package/assets/dev-guide/fe/principles/react.md +183 -0
  29. package/assets/dev-guide/fe/principles/vue.md +196 -0
  30. package/assets/dev-guide/fe/skills/react/fe-build/SKILL.md +139 -0
  31. package/assets/dev-guide/fe/skills/react/fe-perf/SKILL.md +179 -0
  32. package/assets/dev-guide/fe/skills/react/fe-review/SKILL.md +141 -0
  33. package/assets/dev-guide/fe/skills/vue/fe-build/SKILL.md +148 -0
  34. package/assets/dev-guide/fe/skills/vue/fe-perf/SKILL.md +163 -0
  35. package/assets/dev-guide/fe/skills/vue/fe-review/SKILL.md +136 -0
  36. package/assets/dev-guide/fe/sources/a11y-dx/INDEX.md +41 -0
  37. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-memory.md +150 -0
  38. package/assets/dev-guide/fe/sources/a11y-dx/chrome-devtools-performance.md +99 -0
  39. package/assets/dev-guide/fe/sources/a11y-dx/lighthouse-audits.md +146 -0
  40. package/assets/dev-guide/fe/sources/a11y-dx/react-devtools-profiler.md +128 -0
  41. package/assets/dev-guide/fe/sources/a11y-dx/wcag22-new-criteria.md +174 -0
  42. package/assets/dev-guide/fe/sources/perf/01-core-web-vitals.md +58 -0
  43. package/assets/dev-guide/fe/sources/perf/02-inp.md +83 -0
  44. package/assets/dev-guide/fe/sources/perf/03-lcp-cls.md +130 -0
  45. package/assets/dev-guide/fe/sources/perf/04-speculation-rules.md +148 -0
  46. package/assets/dev-guide/fe/sources/perf/05-view-transitions.md +153 -0
  47. package/assets/dev-guide/fe/sources/perf/06-nextjs-caching.md +188 -0
  48. package/assets/dev-guide/fe/sources/perf/07-server-components.md +181 -0
  49. package/assets/dev-guide/fe/sources/perf/08-ppr.md +133 -0
  50. package/assets/dev-guide/fe/sources/perf/09-nextjs-image.md +200 -0
  51. package/assets/dev-guide/fe/sources/perf/10-optimize-lcp.md +201 -0
  52. package/assets/dev-guide/fe/sources/perf/INDEX.md +88 -0
  53. package/assets/dev-guide/fe/sources/react/INDEX.md +41 -0
  54. package/assets/dev-guide/fe/sources/react/keeping-components-pure.md +135 -0
  55. package/assets/dev-guide/fe/sources/react/no-effect-patterns.md +183 -0
  56. package/assets/dev-guide/fe/sources/react/react-compiler.md +182 -0
  57. package/assets/dev-guide/fe/sources/react/server-components.md +194 -0
  58. package/assets/dev-guide/fe/sources/react/server-functions.md +192 -0
  59. package/assets/dev-guide/fe/sources/react/suspense.md +218 -0
  60. package/assets/dev-guide/fe/sources/react/use-action-state.md +123 -0
  61. package/assets/dev-guide/fe/sources/react/use-form-status.md +158 -0
  62. package/assets/dev-guide/fe/sources/react/use-hook.md +153 -0
  63. package/assets/dev-guide/fe/sources/react/use-optimistic.md +194 -0
  64. package/assets/dev-guide/fe/sources/toss-ff/INDEX.md +58 -0
  65. package/assets/dev-guide/fe/sources/toss-ff/cohesion-code-directory.md +79 -0
  66. package/assets/dev-guide/fe/sources/toss-ff/cohesion-form-fields.md +110 -0
  67. package/assets/dev-guide/fe/sources/toss-ff/cohesion-magic-number.md +47 -0
  68. package/assets/dev-guide/fe/sources/toss-ff/coupling-item-edit-modal.md +124 -0
  69. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-bottom-sheet.md +57 -0
  70. package/assets/dev-guide/fe/sources/toss-ff/coupling-use-page-state.md +71 -0
  71. package/assets/dev-guide/fe/sources/toss-ff/overview-4-principles.md +77 -0
  72. package/assets/dev-guide/fe/sources/toss-ff/predictability-hidden-logic.md +59 -0
  73. package/assets/dev-guide/fe/sources/toss-ff/predictability-http.md +77 -0
  74. package/assets/dev-guide/fe/sources/toss-ff/predictability-use-user.md +110 -0
  75. package/assets/dev-guide/fe/sources/toss-ff/readability-comparison-order.md +52 -0
  76. package/assets/dev-guide/fe/sources/toss-ff/readability-condition-name.md +64 -0
  77. package/assets/dev-guide/fe/sources/toss-ff/readability-login-start-page.md +183 -0
  78. package/assets/dev-guide/fe/sources/toss-ff/readability-magic-number.md +53 -0
  79. package/assets/dev-guide/fe/sources/toss-ff/readability-submit-button.md +73 -0
  80. package/assets/dev-guide/fe/sources/toss-ff/readability-ternary-operator.md +38 -0
  81. package/assets/dev-guide/fe/sources/toss-ff/readability-use-page-state.md +77 -0
  82. package/assets/dev-guide/fe/sources/toss-ff/readability-user-policy.md +98 -0
  83. package/assets/dev-guide/fe/sources/vue/INDEX.md +17 -0
  84. package/assets/dev-guide/fe/sources/vue/composition-api.md +251 -0
  85. package/assets/dev-guide/fe/sources/vue/nuxt-data-fetching.md +232 -0
  86. package/assets/dev-guide/fe/sources/vue/pinia-state-management.md +134 -0
  87. package/assets/dev-guide/fe/sources/vue/reactivity-pitfalls.md +261 -0
  88. package/assets/dev-guide/fe/sources/vue/style-guide-priority-a.md +117 -0
  89. package/assets/dev-guide/fe/sources/vue/style-guide-priority-b.md +231 -0
  90. package/assets/dev-guide/fe/sources/vue/style-guide-priority-c.md +86 -0
  91. package/assets/dev-guide/fe/sources/vue/style-guide-priority-d.md +72 -0
  92. package/dist/cli.js +42 -0
  93. package/dist/core/dashboard-cli.d.ts +12 -0
  94. package/dist/core/dashboard-cli.js +226 -0
  95. package/dist/core/dev-guide-injector.d.ts +26 -0
  96. package/dist/core/dev-guide-injector.js +137 -0
  97. package/dist/core/init.js +53 -0
  98. package/dist/core/lifecycle-classifier.d.ts +23 -0
  99. package/dist/core/lifecycle-classifier.js +104 -0
  100. package/dist/core/observability-backfill.d.ts +31 -0
  101. package/dist/core/observability-backfill.js +178 -0
  102. package/dist/core/observability-store.d.ts +58 -0
  103. package/dist/core/observability-store.js +195 -0
  104. package/dist/core/session-store.js +4 -0
  105. package/dist/core/spawn.d.ts +17 -0
  106. package/dist/core/spawn.js +179 -2
  107. package/dist/core/statusline-cli.js +34 -1
  108. package/dist/engine/compound-extractor.js +39 -0
  109. package/dist/engine/compound-loop.js +6 -0
  110. package/dist/engine/compound-retire.d.ts +20 -0
  111. package/dist/engine/compound-retire.js +85 -0
  112. package/dist/hooks/context-guard.js +25 -1
  113. package/dist/hooks/post-tool-use.js +48 -0
  114. package/dist/hooks/solution-injector.js +93 -0
  115. package/dist/host/install-claude.d.ts +6 -2
  116. package/dist/host/install-claude.js +74 -2
  117. package/dist/host/install-codex.d.ts +4 -0
  118. package/dist/host/install-codex.js +71 -0
  119. package/dist/host/install-orchestrator.js +1 -0
  120. package/package.json +6 -6
  121. package/plugin.json +1 -1
  122. package/scripts/postinstall.js +134 -0
@@ -0,0 +1,272 @@
1
+ ---
2
+ name: be-perf-node
3
+ description: Node.js 서비스의 p95/p99 성능 문제를 진단하고 수정. DB N+1, Event Loop 차단, GC 압박, 동기 차단, lock contention, network roundtrip 카테고리별 절차로 접근한다.
4
+ ---
5
+
6
+ # be-perf (Node.js)
7
+
8
+ > **호출 시점**: "p99가 800ms야 잡아줘", "메모리가 계속 올라가", "DB 쿼리가 느려졌어", "CPU가 치솟아".
9
+ > **선행 로딩**: `principles/common.md` + `principles/node.md` 필수.
10
+
11
+ ## 0. 절대 금지
12
+
13
+ 1. 측정 없이 최적화 추측 금지 — "아마 이게 느릴 것 같아"는 근거 없음.
14
+ 2. p50만 보고 OK 선언 금지 — p95/p99 반드시 확인.
15
+ 3. 프로파일링 없이 `async/await` → callback 전환 금지 (성능 개선 미미, 가독성 손실 큼).
16
+
17
+ ## 1. 진단 절차
18
+
19
+ ### Step 1 — 현재 지표 수집
20
+
21
+ 먼저 수치를 확인하라. 추측으로 시작하지 마라.
22
+
23
+ ```bash
24
+ # APM 또는 Prometheus 메트릭
25
+ # http_request_duration_seconds{quantile="0.95"} — p95
26
+ # http_request_duration_seconds{quantile="0.99"} — p99
27
+
28
+ # 로드 테스트로 현재 기준선 측정
29
+ npx autocannon -c 100 -d 30 http://localhost:3000/api/orders
30
+ # Requests/sec, Latency 분포, Errors 확인
31
+ ```
32
+
33
+ ### Step 2 — 병목 카테고리 분류
34
+
35
+ | 증상 | 가능한 카테고리 |
36
+ |------|-----------------|
37
+ | DB 쿼리가 느림 (APM에서 확인) | N+1, 인덱스 누락, 슬로우 쿼리 |
38
+ | CPU 사용률 치솟음 | Event Loop 차단, GC 압박, 동기 연산 |
39
+ | 메모리 지속 증가 | 메모리 누수, GC 부족 |
40
+ | 특정 엔드포인트만 느림 | 해당 경로 분석 (외부 API, 직렬화) |
41
+ | 모든 요청 느림 | Event Loop 차단, 연결 풀 소진 |
42
+
43
+ ### Step 3 — 카테고리별 진단 실행
44
+
45
+ ## 2. 카테고리별 진단 및 픽스
46
+
47
+ ### 2.1 DB N+1
48
+
49
+ **탐지**: APM에서 단일 요청에 DB 쿼리 N+1개 발생. 로그에서 반복 쿼리 패턴.
50
+
51
+ ```typescript
52
+ // Prisma 탐지: queryRawUnsafe 이벤트 로깅
53
+ prisma.$on('query', (e) => {
54
+ if (process.env.NODE_ENV === 'development') {
55
+ logger.debug({ query: e.query, duration: e.duration }, 'DB query');
56
+ }
57
+ });
58
+ ```
59
+
60
+ **픽스**:
61
+ ```typescript
62
+ // WRONG: N+1
63
+ const orders = await prisma.order.findMany();
64
+ for (const order of orders) {
65
+ order.user = await prisma.user.findUnique({ where: { id: order.userId } });
66
+ }
67
+
68
+ // RIGHT: include로 JOIN
69
+ const orders = await prisma.order.findMany({
70
+ include: { user: { select: { id: true, name: true } } }, // 필요한 필드만
71
+ });
72
+
73
+ // 대용량: DataLoader 패턴
74
+ import DataLoader from 'dataloader';
75
+ const userLoader = new DataLoader<string, User>(async (ids) => {
76
+ const users = await prisma.user.findMany({ where: { id: { in: [...ids] } } });
77
+ return ids.map(id => users.find(u => u.id === id) ?? null);
78
+ });
79
+ ```
80
+
81
+ ### 2.2 Event Loop 차단
82
+
83
+ **탐지**: `--inspect` 플래그로 Node.js 프로파일러 실행.
84
+
85
+ ```bash
86
+ node --inspect --prof app.js # V8 프로파일 생성
87
+ node --prof-process isolate-*.log > profile.txt # 분석
88
+ ```
89
+
90
+ 또는 clinic.js (권장):
91
+ ```bash
92
+ npx clinic doctor -- node app.js
93
+ npx clinic flame -- node app.js # Flamegraph
94
+ ```
95
+
96
+ **픽스**:
97
+ ```typescript
98
+ // 동기 블로킹 → 비동기
99
+ // WRONG
100
+ import { readFileSync } from 'node:fs';
101
+ app.get('/config', (req, res) => {
102
+ const config = readFileSync('config.json', 'utf-8'); // 차단!
103
+ res.json(JSON.parse(config));
104
+ });
105
+
106
+ // RIGHT
107
+ import { readFile } from 'node:fs/promises';
108
+ app.get('/config', async (req, res) => {
109
+ const config = await readFile('config.json', 'utf-8');
110
+ res.json(JSON.parse(config));
111
+ });
112
+
113
+ // CPU 집약 작업 → worker_threads
114
+ import { Worker } from 'node:worker_threads';
115
+ function runInWorker(data: unknown): Promise<unknown> {
116
+ return new Promise((resolve, reject) => {
117
+ const worker = new Worker('./workers/compute.js', { workerData: data });
118
+ worker.on('message', resolve);
119
+ worker.on('error', reject);
120
+ });
121
+ }
122
+ ```
123
+
124
+ ### 2.3 GC 압박 (메모리)
125
+
126
+ **탐지**:
127
+ ```bash
128
+ # heapdump 분석
129
+ node --expose-gc app.js
130
+ # 또는
131
+ npx clinic heapprofiler -- node app.js
132
+ ```
133
+
134
+ ```typescript
135
+ // 메모리 사용량 모니터링
136
+ setInterval(() => {
137
+ const mem = process.memoryUsage();
138
+ logger.info({
139
+ heapUsed: Math.round(mem.heapUsed / 1024 / 1024) + 'MB',
140
+ heapTotal: Math.round(mem.heapTotal / 1024 / 1024) + 'MB',
141
+ rss: Math.round(mem.rss / 1024 / 1024) + 'MB',
142
+ }, 'Memory usage');
143
+ }, 30_000);
144
+ ```
145
+
146
+ **흔한 누수 패턴**:
147
+ ```typescript
148
+ // WRONG: EventEmitter 리스너 누수
149
+ app.on('request', handler); // 제거 없음
150
+
151
+ // RIGHT: 명시적 제거
152
+ const handler = () => { ... };
153
+ app.on('request', handler);
154
+ // ... 정리 시
155
+ app.off('request', handler);
156
+
157
+ // WRONG: 클로저가 큰 객체 참조
158
+ function processRequest(req: Request) {
159
+ const hugeBuffer = Buffer.alloc(1024 * 1024 * 100); // 100MB
160
+ return async () => {
161
+ // hugeBuffer가 클로저에 갇혀 GC 불가
162
+ return hugeBuffer.length;
163
+ };
164
+ }
165
+ ```
166
+
167
+ ### 2.4 외부 API 지연 (Network Roundtrip)
168
+
169
+ **탐지**: OpenTelemetry span에서 external call duration 확인.
170
+
171
+ **픽스**:
172
+ ```typescript
173
+ // 병렬화 — 독립적인 외부 호출은 동시 실행
174
+ const [user, inventory] = await Promise.all([
175
+ userApiClient.getUser(userId),
176
+ inventoryApiClient.getStock(productId),
177
+ ]);
178
+
179
+ // 타임아웃 설정 (기본값 없음에 의존 금지)
180
+ const response = await fetch(url, {
181
+ signal: AbortSignal.timeout(3000), // 3초 타임아웃
182
+ });
183
+
184
+ // 서킷 브레이커 (opossum)
185
+ import CircuitBreaker from 'opossum';
186
+ const breaker = new CircuitBreaker(externalApiCall, {
187
+ timeout: 3000,
188
+ errorThresholdPercentage: 50,
189
+ resetTimeout: 30000,
190
+ });
191
+ ```
192
+
193
+ ### 2.5 DB 연결 풀 소진
194
+
195
+ **탐지**: DB 응답 느림 + 연결 대기 큐 증가.
196
+
197
+ ```typescript
198
+ // Prisma 연결 풀 설정
199
+ const prisma = new PrismaClient({
200
+ datasources: {
201
+ db: {
202
+ url: `${DATABASE_URL}?connection_limit=10&pool_timeout=10`,
203
+ },
204
+ },
205
+ });
206
+
207
+ // pg (node-postgres) 연결 풀
208
+ import { Pool } from 'pg';
209
+ const pool = new Pool({
210
+ max: 10, // 최대 연결 수
211
+ idleTimeoutMillis: 30000,
212
+ connectionTimeoutMillis: 2000,
213
+ });
214
+
215
+ // 연결 풀 모니터링
216
+ setInterval(() => {
217
+ logger.info({
218
+ total: pool.totalCount,
219
+ idle: pool.idleCount,
220
+ waiting: pool.waitingCount,
221
+ }, 'Connection pool stats');
222
+ }, 10_000);
223
+ ```
224
+
225
+ ### 2.6 직렬화 비용 (대용량 JSON)
226
+
227
+ ```typescript
228
+ // WRONG: 대용량 객체 통째로 JSON.stringify
229
+ res.json(await db.findManyWithAllFields());
230
+
231
+ // RIGHT: 필요한 필드만 선택 (projection)
232
+ const orders = await prisma.order.findMany({
233
+ select: { id: true, status: true, createdAt: true }, // 필요한 것만
234
+ take: 20, // 페이지네이션
235
+ });
236
+
237
+ // 대용량 응답: 스트리밍
238
+ import { pipeline } from 'node:stream/promises';
239
+ import { Readable } from 'node:stream';
240
+ app.get('/export', async (req, res) => {
241
+ res.setHeader('Content-Type', 'application/x-ndjson');
242
+ const cursor = db.findManyCursor(); // DB 커서
243
+ await pipeline(cursor, res);
244
+ });
245
+ ```
246
+
247
+ ## 3. 출력 형식
248
+
249
+ ```
250
+ ## 성능 진단 결과
251
+
252
+ ### 측정 기준선
253
+ - p50: Xms / p95: Xms / p99: Xms
254
+ - 목표 SLO: p95 < Xms (docs/slo.md)
255
+
256
+ ### 발견된 병목
257
+ 1. [카테고리] file:line — 설명 (예상 개선: X%)
258
+ 2. ...
259
+
260
+ ### 적용한 픽스
261
+ - 변경 파일: <목록>
262
+ - 재측정 결과: p95 Xms → Xms
263
+
264
+ ### 추가 권고 (이번 PR 범위 외)
265
+ - ...
266
+ ```
267
+
268
+ ## 4. 관련 문서
269
+
270
+ - 원칙: [`principles/common.md`](../../../principles/common.md) F섹션 (Performance Baseline)
271
+ - 리뷰: [`skills/node/be-review/SKILL.md`](../be-review/SKILL.md)
272
+ - 코퍼스: `sources/node-runtime/`, `sources/postgres/`
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: be-review-node
3
+ description: Node.js/TypeScript PR을 사내 BE 원칙 기준으로 리뷰. [SEVERITY] file:line — 이슈 형식으로 출력하고, 머지 차단/비차단을 명확히 구분한다.
4
+ ---
5
+
6
+ # be-review (Node.js)
7
+
8
+ > **호출 시점**: "이 PR 리뷰해줘", "이 코드 리뷰해줘", "OWASP 관점에서 검토해줘".
9
+ > **선행 로딩**: `principles/common.md` + `principles/node.md` 필수.
10
+
11
+ ## 0. 절대 금지
12
+
13
+ 1. 이슈 없이 "좋아 보입니다" 완료 선언 금지 — 모든 체크리스트 항목 확인.
14
+ 2. 주관적 스타일 의견을 [HIGH]로 분류 금지.
15
+ 3. 자동 수정 가능한 포매팅 이슈를 리뷰에 포함 금지 (eslint --fix 로 처리).
16
+
17
+ ## 1. 리뷰 출력 형식
18
+
19
+ ```
20
+ ## 리뷰 요약
21
+ - 변경: N files +X -Y
22
+ - HIGH N, MED N, LOW N / 머지 [차단|비차단]
23
+
24
+ [HIGH] src/orders/order.controller.ts:42 — SQL 문자열 concat, injection 위험
25
+ [HIGH] src/orders/order.service.ts:88 — 빈 catch 블록, 에러 묵살
26
+ [MED] src/orders/order.service.ts:120 — optional couponCode를 required 취급 (명세 위반)
27
+ [LOW] src/orders/types.ts:15 — 매직 넘버 300, 상수 추출 권장
28
+ ```
29
+
30
+ ### SEVERITY 기준
31
+
32
+ | SEVERITY | 정의 | 머지 |
33
+ |----------|------|------|
34
+ | **HIGH** | 보안 취약점, 데이터 손실 가능, 명세 위반, 에러 묵살, 프로세스 불안정 | 차단 |
35
+ | **MED** | 성능 저하(N+1), 관찰가능성 누락, 타입 안전성 위반, 에러 구조 불일치 | 권고 (팀 합의 시 통과) |
36
+ | **LOW** | 가독성, 매직 넘버, 주석, 함수 길이 | 비차단 |
37
+
38
+ ## 2. 체크리스트
39
+
40
+ ### 2.1 보안 (HIGH 기준)
41
+
42
+ ```
43
+ [ ] SQL/NoSQL injection — 문자열 concat 쿼리 없음
44
+ [ ] 인가 검증 — 모든 리소스 접근에 소유권 확인 (OWASP API1)
45
+ [ ] 하드코딩 시크릿 없음 — API key, password, token
46
+ [ ] 입력 검증 — 모든 external input에 Zod/joi 검증
47
+ [ ] 응답에 민감 필드 노출 없음 — password, hash, internal ID (OWASP API3)
48
+ [ ] CORS 설정 — `*` 미사용 (origin allowlist)
49
+ ```
50
+
51
+ ### 2.2 에러 모델 (HIGH/MED)
52
+
53
+ ```
54
+ [ ] 빈 catch 블록 없음 — 최소 logger.error + re-throw
55
+ [ ] HTTP 200 + { success: false } 패턴 없음 — 4xx/5xx 적절히 사용
56
+ [ ] 에러 응답 구조 일관성 — { error: { code, message, requestId } }
57
+ [ ] 4xx와 5xx 경계 올바름 — 클라이언트 오류 vs 서버 오류 혼동 없음
58
+ [ ] unhandledRejection 핸들러 존재
59
+ ```
60
+
61
+ ### 2.3 Node.js 특화 (MED/HIGH)
62
+
63
+ ```
64
+ [ ] async/await 일관성 — callback 혼용 없음
65
+ [ ] Event Loop 차단 없음 — 동기 I/O (readFileSync 등) 핸들러 내 미사용
66
+ [ ] optional 필드를 required로 취급하지 않음 (명세 일치)
67
+ [ ] TypeScript strict 통과 — any 타입 남용 없음
68
+ [ ] 런타임 검증 — 외부 데이터에 as T 단언 미사용
69
+ [ ] process.exit() 직접 호출 없음 (graceful shutdown 우회)
70
+ ```
71
+
72
+ ### 2.4 성능 (MED)
73
+
74
+ ```
75
+ [ ] N+1 쿼리 없음 — 루프 내 DB 쿼리 패턴
76
+ [ ] 트랜잭션 내 외부 API 호출 없음
77
+ [ ] Promise.all — 독립 async 작업 병렬화
78
+ [ ] 인덱스 없는 WHERE 조건 없음 (신규 쿼리)
79
+ ```
80
+
81
+ ### 2.5 관찰가능성 (MED)
82
+
83
+ ```
84
+ [ ] 구조화 로그 (JSON) — console.log 미사용
85
+ [ ] 에러 로그에 requestId 포함
86
+ [ ] 중요 비즈니스 이벤트 로그 존재 (주문 생성, 결제 완료 등)
87
+ [ ] 민감 정보 로그 미포함 (password, 카드번호, PII)
88
+ ```
89
+
90
+ ### 2.6 코드 품질 (LOW)
91
+
92
+ ```
93
+ [ ] 함수 50줄 이하
94
+ [ ] 중첩 깊이 4 이하 (early return 활용)
95
+ [ ] 매직 넘버 상수화
96
+ [ ] 같은 파일 3+회 수정 시 전체 재설계 검토
97
+ ```
98
+
99
+ ## 3. 이슈 카탈로그 (즉시 참조)
100
+
101
+ | 패턴 | SEVERITY | 설명 |
102
+ |------|----------|------|
103
+ | `db.query("... WHERE id = " + id)` | HIGH | SQL injection |
104
+ | `catch (err) {}` | HIGH | 에러 묵살 |
105
+ | `res.status(200).json({ success: false })` | HIGH | 에러 응답 오용 |
106
+ | `const user = cache.get() as User` | MED | 런타임 검증 없는 단언 |
107
+ | `for (const x of list) { await db.findBy... }` | MED | N+1 쿼리 |
108
+ | `await externalApi()` inside transaction | MED | 트랜잭션 내 외부 호출 |
109
+ | `console.log(req.body)` | MED | 구조화 로그 미사용 + PII 노출 위험 |
110
+ | `if (req.params.userId !== req.user.id)` 누락 | HIGH | OWASP API1 (소유권 검증) |
111
+ | `res.json(await db.findUser(id))` (전체 모델) | MED | OWASP API3 (필드 노출) |
112
+ | `fs.readFileSync(...)` in handler | MED | Event loop 차단 |
113
+ | 함수 > 50줄 | LOW | 책임별 분리 |
114
+
115
+ ## 4. 관련 문서
116
+
117
+ - 원칙: [`principles/common.md`](../../../principles/common.md), [`principles/node.md`](../../../principles/node.md)
118
+ - 보안 체크리스트: [`skills/node/be-security/SKILL.md`](../be-security/SKILL.md)