backend-claude-code 1.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 (51) hide show
  1. package/.claude/settings.json +42 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +32 -0
  5. package/.mcp.json +19 -0
  6. package/CLAUDE.md +126 -0
  7. package/README.md +142 -0
  8. package/agents/code-reviewer.md +84 -0
  9. package/agents/database-reviewer.md +91 -0
  10. package/agents/java-build-resolver.md +127 -0
  11. package/agents/java-performance-reviewer.md +262 -0
  12. package/agents/planner.md +99 -0
  13. package/agents/security-reviewer.md +119 -0
  14. package/agents/tdd-guide.md +189 -0
  15. package/bin/cli.js +144 -0
  16. package/commands/db-migrate.md +134 -0
  17. package/commands/dev-build.md +72 -0
  18. package/commands/dev-coverage.md +73 -0
  19. package/commands/dev-fix.md +75 -0
  20. package/commands/dev-plan.md +501 -0
  21. package/commands/dev-review.md +144 -0
  22. package/commands/dev-run.md +385 -0
  23. package/commands/dev-test.md +89 -0
  24. package/commands/dev-verify.md +95 -0
  25. package/commands/dev.md +45 -0
  26. package/commands/git-commit.md +112 -0
  27. package/commands/git-issue.md +74 -0
  28. package/commands/git-pr.md +184 -0
  29. package/commands/git-push.md +28 -0
  30. package/package.json +24 -0
  31. package/rules/architecture.md +33 -0
  32. package/rules/coding-style.md +113 -0
  33. package/rules/controller-patterns.md +63 -0
  34. package/rules/dto-patterns.md +76 -0
  35. package/rules/entity-patterns.md +70 -0
  36. package/rules/error-handling.md +56 -0
  37. package/rules/hooks.md +73 -0
  38. package/rules/repository-patterns.md +75 -0
  39. package/rules/security.md +101 -0
  40. package/rules/service-patterns.md +70 -0
  41. package/rules/testing.md +174 -0
  42. package/skills/api-design/SKILL.md +523 -0
  43. package/skills/architecture-decision-records/SKILL.md +179 -0
  44. package/skills/database-migrations/SKILL.md +429 -0
  45. package/skills/hexagonal-architecture/SKILL.md +276 -0
  46. package/skills/java-coding-standards/SKILL.md +236 -0
  47. package/skills/jpa-patterns/SKILL.md +218 -0
  48. package/skills/postgres-patterns/SKILL.md +147 -0
  49. package/skills/springboot-patterns/SKILL.md +255 -0
  50. package/skills/springboot-security/SKILL.md +241 -0
  51. package/skills/springboot-tdd/SKILL.md +236 -0
@@ -0,0 +1,255 @@
1
+ ---
2
+ name: springboot-patterns
3
+ description: Spring Boot architecture patterns, REST API design, layered services, data access, caching, async processing, and logging. Use for Java Spring Boot backend work.
4
+ origin: ECC
5
+ ---
6
+
7
+ # Spring Boot Development Patterns
8
+
9
+ Spring Boot 아키텍처 및 API 패턴 — 확장 가능한 프로덕션 등급 서비스.
10
+
11
+ ## When to Activate
12
+
13
+ - Spring MVC 또는 WebFlux로 REST API 구축
14
+ - controller → service → repository 레이어 구조화
15
+ - Spring Data JPA, 캐싱, 비동기 처리 설정
16
+ - 검증, 예외 처리, 페이지네이션 추가
17
+ - dev/staging/production 프로파일 설정
18
+ - Spring Events 또는 Kafka를 사용한 이벤트 기반 패턴 구현
19
+
20
+ ## REST API Structure
21
+
22
+ ```java
23
+ @RestController
24
+ @RequestMapping("/api/orders")
25
+ @Validated
26
+ class OrderController {
27
+ private final OrderService orderService;
28
+
29
+ OrderController(OrderService orderService) {
30
+ this.orderService = orderService;
31
+ }
32
+
33
+ @GetMapping
34
+ ResponseEntity<Page<OrderResponse>> list(
35
+ @RequestParam(defaultValue = "0") int page,
36
+ @RequestParam(defaultValue = "20") int size) {
37
+ return ResponseEntity.ok(
38
+ orderService.list(PageRequest.of(page, size)));
39
+ }
40
+
41
+ @PostMapping
42
+ ResponseEntity<OrderResponse> create(@Valid @RequestBody CreateOrderRequest request) {
43
+ return ResponseEntity.status(HttpStatus.CREATED)
44
+ .body(orderService.create(request));
45
+ }
46
+
47
+ @GetMapping("/{id}")
48
+ ResponseEntity<OrderResponse> findById(@PathVariable Long id) {
49
+ return ResponseEntity.ok(orderService.findById(id));
50
+ }
51
+
52
+ @DeleteMapping("/{id}")
53
+ ResponseEntity<Void> delete(@PathVariable Long id) {
54
+ orderService.delete(id);
55
+ return ResponseEntity.noContent().build();
56
+ }
57
+ }
58
+ ```
59
+
60
+ ## Repository Pattern (Spring Data JPA)
61
+
62
+ ```java
63
+ public interface OrderRepository extends JpaRepository<OrderEntity, Long> {
64
+ @Query("select o from OrderEntity o where o.status = :status order by o.createdAt desc")
65
+ Page<OrderEntity> findByStatus(@Param("status") OrderStatus status, Pageable pageable);
66
+
67
+ Optional<OrderEntity> findByOrderNumber(String orderNumber);
68
+ }
69
+ ```
70
+
71
+ ## Service Layer with Transactions
72
+
73
+ ```java
74
+ @Service
75
+ public class OrderService {
76
+ private final OrderRepository repo;
77
+
78
+ public OrderService(OrderRepository repo) {
79
+ this.repo = repo;
80
+ }
81
+
82
+ @Transactional
83
+ public OrderResponse create(CreateOrderRequest request) {
84
+ OrderEntity entity = OrderEntity.from(request);
85
+ OrderEntity saved = repo.save(entity);
86
+ return OrderResponse.from(saved);
87
+ }
88
+
89
+ @Transactional(readOnly = true)
90
+ public OrderResponse findById(Long id) {
91
+ return repo.findById(id)
92
+ .map(OrderResponse::from)
93
+ .orElseThrow(() -> new OrderNotFoundException(id));
94
+ }
95
+
96
+ @Transactional(readOnly = true)
97
+ public Page<OrderResponse> list(Pageable pageable) {
98
+ return repo.findAll(pageable).map(OrderResponse::from);
99
+ }
100
+
101
+ @Transactional
102
+ public void delete(Long id) {
103
+ if (!repo.existsById(id)) {
104
+ throw new OrderNotFoundException(id);
105
+ }
106
+ repo.deleteById(id);
107
+ }
108
+ }
109
+ ```
110
+
111
+ ## DTOs and Validation
112
+
113
+ ```java
114
+ public record CreateOrderRequest(
115
+ @NotBlank @Size(max = 100) String customerName,
116
+ @NotNull @Positive BigDecimal amount,
117
+ @NotNull @FutureOrPresent LocalDate deliveryDate) {}
118
+
119
+ public record OrderResponse(Long id, String customerName, BigDecimal amount, OrderStatus status) {
120
+ static OrderResponse from(OrderEntity entity) {
121
+ return new OrderResponse(
122
+ entity.getId(), entity.getCustomerName(),
123
+ entity.getAmount(), entity.getStatus());
124
+ }
125
+ }
126
+ ```
127
+
128
+ ## Exception Handling
129
+
130
+ ```java
131
+ @RestControllerAdvice
132
+ class GlobalExceptionHandler {
133
+ private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
134
+
135
+ @ExceptionHandler(EntityNotFoundException.class)
136
+ ResponseEntity<ApiResponse<Void>> handleNotFound(EntityNotFoundException ex) {
137
+ return ResponseEntity.status(HttpStatus.NOT_FOUND)
138
+ .body(ApiResponse.error(ex.getMessage()));
139
+ }
140
+
141
+ @ExceptionHandler(MethodArgumentNotValidException.class)
142
+ ResponseEntity<ApiResponse<Void>> handleValidation(MethodArgumentNotValidException ex) {
143
+ String message = ex.getBindingResult().getFieldErrors().stream()
144
+ .map(e -> e.getField() + ": " + e.getDefaultMessage())
145
+ .collect(Collectors.joining(", "));
146
+ return ResponseEntity.badRequest().body(ApiResponse.error(message));
147
+ }
148
+
149
+ @ExceptionHandler(Exception.class)
150
+ ResponseEntity<ApiResponse<Void>> handleGeneric(Exception ex) {
151
+ log.error("Unexpected error", ex);
152
+ return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
153
+ .body(ApiResponse.error("Internal server error"));
154
+ }
155
+ }
156
+ ```
157
+
158
+ ## Caching
159
+
160
+ `@EnableCaching` 설정 클래스에 추가 필요.
161
+
162
+ ```java
163
+ @Service
164
+ public class OrderCacheService {
165
+ private final OrderRepository repo;
166
+
167
+ public OrderCacheService(OrderRepository repo) { this.repo = repo; }
168
+
169
+ @Cacheable(value = "order", key = "#id")
170
+ public OrderResponse getById(Long id) {
171
+ return repo.findById(id)
172
+ .map(OrderResponse::from)
173
+ .orElseThrow(() -> new OrderNotFoundException(id));
174
+ }
175
+
176
+ @CacheEvict(value = "order", key = "#id")
177
+ public void evict(Long id) {}
178
+ }
179
+ ```
180
+
181
+ ## Async Processing
182
+
183
+ `@EnableAsync` 설정 클래스에 추가 필요.
184
+
185
+ ```java
186
+ @Service
187
+ public class NotificationService {
188
+ @Async
189
+ public CompletableFuture<Void> sendAsync(Notification notification) {
190
+ // 이메일/SMS 발송
191
+ return CompletableFuture.completedFuture(null);
192
+ }
193
+ }
194
+ ```
195
+
196
+ ## Logging (SLF4J)
197
+
198
+ ```java
199
+ @Service
200
+ public class OrderService {
201
+ private static final Logger log = LoggerFactory.getLogger(OrderService.class);
202
+
203
+ public OrderResponse create(CreateOrderRequest request) {
204
+ log.info("create_order customerName={}", request.customerName());
205
+ try {
206
+ // 로직
207
+ log.info("order_created id={}", saved.getId());
208
+ return OrderResponse.from(saved);
209
+ } catch (Exception ex) {
210
+ log.error("create_order_failed customerName={}", request.customerName(), ex);
211
+ throw ex;
212
+ }
213
+ }
214
+ }
215
+ ```
216
+
217
+ ## Request Logging Filter
218
+
219
+ ```java
220
+ @Component
221
+ public class RequestLoggingFilter extends OncePerRequestFilter {
222
+ private static final Logger log = LoggerFactory.getLogger(RequestLoggingFilter.class);
223
+
224
+ @Override
225
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
226
+ FilterChain filterChain) throws ServletException, IOException {
227
+ long start = System.currentTimeMillis();
228
+ try {
229
+ filterChain.doFilter(request, response);
230
+ } finally {
231
+ long duration = System.currentTimeMillis() - start;
232
+ log.info("req method={} uri={} status={} durationMs={}",
233
+ request.getMethod(), request.getRequestURI(),
234
+ response.getStatus(), duration);
235
+ }
236
+ }
237
+ }
238
+ ```
239
+
240
+ ## Pagination and Sorting
241
+
242
+ ```java
243
+ PageRequest page = PageRequest.of(pageNumber, pageSize, Sort.by("createdAt").descending());
244
+ Page<OrderEntity> results = orderRepository.findAll(page);
245
+ ```
246
+
247
+ ## Production Defaults
248
+
249
+ - 생성자 주입 선호, 필드 주입 지양
250
+ - Spring Boot 3+: `spring.mvc.problemdetails.enabled=true` (RFC 7807)
251
+ - HikariCP 풀 크기 설정, 타임아웃 구성
252
+ - 조회에 `@Transactional(readOnly = true)` 사용
253
+ - `@NonNull`과 `Optional`로 null 안전성 강제
254
+
255
+ **Remember**: 컨트롤러는 얇게, 서비스는 집중되게, 레포지토리는 단순하게, 오류는 중앙에서 처리.
@@ -0,0 +1,241 @@
1
+ ---
2
+ name: springboot-security
3
+ description: Spring Security best practices for authn/authz, validation, CSRF, secrets, headers, rate limiting, and dependency security in Java Spring Boot services.
4
+ origin: ECC
5
+ ---
6
+
7
+ # Spring Boot Security Review
8
+
9
+ 인증, 입력 처리, 엔드포인트 생성, 시크릿 처리 시 사용.
10
+
11
+ ## When to Activate
12
+
13
+ - 인증 추가 (JWT, OAuth2, 세션 기반)
14
+ - 인가 구현 (@PreAuthorize, 역할 기반 접근)
15
+ - 사용자 입력 검증 (Bean Validation, 커스텀 검증기)
16
+ - CORS, CSRF, 보안 헤더 설정
17
+ - 시크릿 관리 (Vault, 환경 변수)
18
+ - 속도 제한 또는 무차별 대입 보호 추가
19
+ - 의존성 CVE 스캔
20
+
21
+ ## Authentication
22
+
23
+ - 상태 없는 JWT 또는 취소 목록이 있는 불투명 토큰 선호
24
+ - 세션에는 `httpOnly`, `Secure`, `SameSite=Strict` 쿠키 사용
25
+ - `OncePerRequestFilter` 또는 리소스 서버로 토큰 검증
26
+
27
+ ```java
28
+ @Component
29
+ public class JwtAuthFilter extends OncePerRequestFilter {
30
+ private final JwtService jwtService;
31
+
32
+ public JwtAuthFilter(JwtService jwtService) {
33
+ this.jwtService = jwtService;
34
+ }
35
+
36
+ @Override
37
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
38
+ FilterChain chain) throws ServletException, IOException {
39
+ String header = request.getHeader(HttpHeaders.AUTHORIZATION);
40
+ if (header != null && header.startsWith("Bearer ")) {
41
+ String token = header.substring(7);
42
+ Authentication auth = jwtService.authenticate(token);
43
+ SecurityContextHolder.getContext().setAuthentication(auth);
44
+ }
45
+ chain.doFilter(request, response);
46
+ }
47
+ }
48
+ ```
49
+
50
+ ## Authorization
51
+
52
+ - 메서드 보안 활성화: `@EnableMethodSecurity`
53
+ - `@PreAuthorize("hasRole('ADMIN')")` 또는 `@PreAuthorize("@authz.canEdit(#id)")` 사용
54
+ - 기본 거부; 필요한 범위만 노출
55
+
56
+ ```java
57
+ @RestController
58
+ @RequestMapping("/api/admin")
59
+ public class AdminController {
60
+
61
+ @PreAuthorize("hasRole('ADMIN')")
62
+ @GetMapping("/users")
63
+ public Page<UserDto> listUsers(Pageable pageable) {
64
+ return userService.findAll(pageable);
65
+ }
66
+
67
+ @PreAuthorize("@authz.isOwner(#id, authentication)")
68
+ @DeleteMapping("/users/{id}")
69
+ public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
70
+ userService.delete(id);
71
+ return ResponseEntity.noContent().build();
72
+ }
73
+ }
74
+ ```
75
+
76
+ ## Input Validation
77
+
78
+ ```java
79
+ // BAD: 검증 없음
80
+ @PostMapping("/users")
81
+ public User createUser(@RequestBody UserDto dto) {
82
+ return userService.create(dto);
83
+ }
84
+
85
+ // GOOD: 검증된 DTO
86
+ public record CreateUserDto(
87
+ @NotBlank @Size(max = 100) String name,
88
+ @NotBlank @Email String email,
89
+ @NotNull @Min(0) @Max(150) Integer age
90
+ ) {}
91
+
92
+ @PostMapping("/users")
93
+ public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserDto dto) {
94
+ return ResponseEntity.status(HttpStatus.CREATED)
95
+ .body(userService.create(dto));
96
+ }
97
+ ```
98
+
99
+ ## SQL Injection Prevention
100
+
101
+ ```java
102
+ // BAD: 문자열 연결
103
+ @Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
104
+
105
+ // GOOD: 파라미터화된 네이티브 쿼리
106
+ @Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)
107
+ List<User> findByName(@Param("name") String name);
108
+
109
+ // GOOD: Spring Data 파생 쿼리 (자동 파라미터화)
110
+ List<User> findByEmailAndActiveTrue(String email);
111
+ ```
112
+
113
+ ## Password Encoding
114
+
115
+ ```java
116
+ @Bean
117
+ public PasswordEncoder passwordEncoder() {
118
+ return new BCryptPasswordEncoder(12); // cost factor 12
119
+ }
120
+
121
+ public User register(CreateUserDto dto) {
122
+ String hashedPassword = passwordEncoder.encode(dto.password());
123
+ return userRepository.save(new User(dto.email(), hashedPassword));
124
+ }
125
+ ```
126
+
127
+ ## CSRF Protection
128
+
129
+ ```java
130
+ // 순수 JWT API — stateless 인증
131
+ http
132
+ .csrf(csrf -> csrf.disable())
133
+ .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
134
+
135
+ // 세션 기반 브라우저 앱 — CSRF 활성화 유지
136
+ http.csrf(Customizer.withDefaults());
137
+ ```
138
+
139
+ ## Secrets Management
140
+
141
+ ```yaml
142
+ # BAD: application.yml에 하드코딩
143
+ spring:
144
+ datasource:
145
+ password: mySecretPassword123
146
+
147
+ # GOOD: 환경 변수 플레이스홀더
148
+ spring:
149
+ datasource:
150
+ password: ${DB_PASSWORD}
151
+
152
+ # GOOD: Spring Cloud Vault
153
+ spring:
154
+ cloud:
155
+ vault:
156
+ uri: https://vault.example.com
157
+ token: ${VAULT_TOKEN}
158
+ ```
159
+
160
+ ## Security Headers
161
+
162
+ ```java
163
+ http
164
+ .headers(headers -> headers
165
+ .contentSecurityPolicy(csp -> csp
166
+ .policyDirectives("default-src 'self'; script-src 'self'"))
167
+ .frameOptions(HeadersConfigurer.FrameOptionsConfig::deny)
168
+ .xssProtection(Customizer.withDefaults())
169
+ .referrerPolicy(rp -> rp
170
+ .policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)));
171
+ ```
172
+
173
+ ## CORS Configuration
174
+
175
+ ```java
176
+ @Bean
177
+ public CorsConfigurationSource corsConfigurationSource() {
178
+ CorsConfiguration config = new CorsConfiguration();
179
+ config.setAllowedOrigins(List.of("https://app.example.com")); // 프로덕션에서 * 금지
180
+ config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
181
+ config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
182
+ config.setAllowCredentials(true);
183
+ config.setMaxAge(3600L);
184
+
185
+ UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
186
+ source.registerCorsConfiguration("/api/**", config);
187
+ return source;
188
+ }
189
+
190
+ http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
191
+ ```
192
+
193
+ ## Rate Limiting (Bucket4j)
194
+
195
+ ```java
196
+ @Component
197
+ public class RateLimitFilter extends OncePerRequestFilter {
198
+ private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
199
+
200
+ private Bucket createBucket() {
201
+ return Bucket.builder()
202
+ .addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
203
+ .build();
204
+ }
205
+
206
+ @Override
207
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
208
+ FilterChain chain) throws ServletException, IOException {
209
+ String clientIp = request.getRemoteAddr(); // ForwardedHeaderFilter 설정 시 정확한 IP 반환
210
+ Bucket bucket = buckets.computeIfAbsent(clientIp, k -> createBucket());
211
+
212
+ if (bucket.tryConsume(1)) {
213
+ chain.doFilter(request, response);
214
+ } else {
215
+ response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
216
+ response.getWriter().write("{\"error\": \"Rate limit exceeded\"}");
217
+ }
218
+ }
219
+ }
220
+ ```
221
+
222
+ ## Dependency Security
223
+
224
+ - CI에서 OWASP Dependency Check / Snyk 실행
225
+ - Spring Boot 및 Spring Security 지원 버전 유지
226
+ - 알려진 CVE 발견 시 빌드 실패
227
+
228
+ ## Release Checklist
229
+
230
+ - [ ] 인증 토큰 검증 및 만료 확인
231
+ - [ ] 모든 민감한 경로에 인가 가드
232
+ - [ ] 모든 입력 검증 및 새니타이즈
233
+ - [ ] SQL 문자열 연결 없음
234
+ - [ ] 앱 타입에 맞는 CSRF 설정
235
+ - [ ] 시크릿 외부화, 커밋 없음
236
+ - [ ] 보안 헤더 설정
237
+ - [ ] API에 속도 제한
238
+ - [ ] 의존성 스캔 완료
239
+ - [ ] 로그에 민감 데이터 없음
240
+
241
+ **Remember**: 기본 거부, 입력 검증, 최소 권한, 설정으로 보안 우선.