red64-cli 0.3.0 → 0.6.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.
- package/README.md +194 -338
- package/dist/cli/parseArgs.d.ts.map +1 -1
- package/dist/cli/parseArgs.js +5 -13
- package/dist/cli/parseArgs.js.map +1 -1
- package/dist/components/init/types.d.ts +0 -2
- package/dist/components/init/types.d.ts.map +1 -1
- package/dist/components/screens/HelpScreen.d.ts.map +1 -1
- package/dist/components/screens/HelpScreen.js +0 -2
- package/dist/components/screens/HelpScreen.js.map +1 -1
- package/dist/components/screens/InitScreen.d.ts.map +1 -1
- package/dist/components/screens/InitScreen.js +5 -8
- package/dist/components/screens/InitScreen.js.map +1 -1
- package/dist/components/screens/StartScreen.d.ts.map +1 -1
- package/dist/components/screens/StartScreen.js +29 -8
- package/dist/components/screens/StartScreen.js.map +1 -1
- package/dist/components/screens/StatusScreen.d.ts.map +1 -1
- package/dist/components/screens/StatusScreen.js +16 -1
- package/dist/components/screens/StatusScreen.js.map +1 -1
- package/dist/services/AgentInvoker.d.ts.map +1 -1
- package/dist/services/AgentInvoker.js +76 -37
- package/dist/services/AgentInvoker.js.map +1 -1
- package/dist/services/ClaudeErrorDetector.d.ts +1 -1
- package/dist/services/ClaudeErrorDetector.d.ts.map +1 -1
- package/dist/services/ClaudeErrorDetector.js +1 -0
- package/dist/services/ClaudeErrorDetector.js.map +1 -1
- package/dist/services/ClaudeHealthCheck.d.ts +7 -0
- package/dist/services/ClaudeHealthCheck.d.ts.map +1 -1
- package/dist/services/ClaudeHealthCheck.js +76 -12
- package/dist/services/ClaudeHealthCheck.js.map +1 -1
- package/dist/services/ConfigService.d.ts +1 -0
- package/dist/services/ConfigService.d.ts.map +1 -1
- package/dist/services/ConfigService.js.map +1 -1
- package/dist/services/DockerRunner.js +1 -1
- package/dist/services/DockerRunner.js.map +1 -1
- package/dist/services/PhaseExecutor.d.ts.map +1 -1
- package/dist/services/PhaseExecutor.js +2 -1
- package/dist/services/PhaseExecutor.js.map +1 -1
- package/dist/services/TaskRunner.d.ts.map +1 -1
- package/dist/services/TaskRunner.js +2 -1
- package/dist/services/TaskRunner.js.map +1 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/index.js.map +1 -1
- package/dist/types/index.d.ts +4 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/framework/stacks/c/code-quality.md +326 -0
- package/framework/stacks/c/coding-style.md +347 -0
- package/framework/stacks/c/conventions.md +513 -0
- package/framework/stacks/c/error-handling.md +350 -0
- package/framework/stacks/c/feedback.md +158 -0
- package/framework/stacks/c/memory-safety.md +408 -0
- package/framework/stacks/c/tech.md +122 -0
- package/framework/stacks/c/testing.md +472 -0
- package/framework/stacks/cpp/code-quality.md +282 -0
- package/framework/stacks/cpp/coding-style.md +363 -0
- package/framework/stacks/cpp/conventions.md +420 -0
- package/framework/stacks/cpp/error-handling.md +264 -0
- package/framework/stacks/cpp/feedback.md +104 -0
- package/framework/stacks/cpp/memory-safety.md +351 -0
- package/framework/stacks/cpp/tech.md +160 -0
- package/framework/stacks/cpp/testing.md +323 -0
- package/framework/stacks/java/code-quality.md +357 -0
- package/framework/stacks/java/coding-style.md +400 -0
- package/framework/stacks/java/conventions.md +437 -0
- package/framework/stacks/java/error-handling.md +408 -0
- package/framework/stacks/java/feedback.md +180 -0
- package/framework/stacks/java/tech.md +126 -0
- package/framework/stacks/java/testing.md +485 -0
- package/framework/stacks/javascript/async-patterns.md +216 -0
- package/framework/stacks/javascript/code-quality.md +182 -0
- package/framework/stacks/javascript/coding-style.md +293 -0
- package/framework/stacks/javascript/conventions.md +268 -0
- package/framework/stacks/javascript/error-handling.md +216 -0
- package/framework/stacks/javascript/feedback.md +80 -0
- package/framework/stacks/javascript/tech.md +114 -0
- package/framework/stacks/javascript/testing.md +209 -0
- package/framework/stacks/loco/code-quality.md +156 -0
- package/framework/stacks/loco/coding-style.md +247 -0
- package/framework/stacks/loco/error-handling.md +225 -0
- package/framework/stacks/loco/feedback.md +35 -0
- package/framework/stacks/loco/loco.md +342 -0
- package/framework/stacks/loco/structure.md +193 -0
- package/framework/stacks/loco/tech.md +129 -0
- package/framework/stacks/loco/testing.md +211 -0
- package/framework/stacks/rust/code-quality.md +370 -0
- package/framework/stacks/rust/coding-style.md +475 -0
- package/framework/stacks/rust/conventions.md +430 -0
- package/framework/stacks/rust/error-handling.md +399 -0
- package/framework/stacks/rust/feedback.md +152 -0
- package/framework/stacks/rust/memory-safety.md +398 -0
- package/framework/stacks/rust/tech.md +121 -0
- package/framework/stacks/rust/testing.md +528 -0
- package/package.json +14 -2
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
# Error Handling Patterns
|
|
2
|
+
|
|
3
|
+
Structured error handling for Java 21+ applications with Spring Boot, custom exception hierarchies, Problem Details (RFC 9457), and sealed result types.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Philosophy
|
|
8
|
+
|
|
9
|
+
- **Fail fast**: Validate inputs early, throw immediately on invalid state
|
|
10
|
+
- **Unchecked over checked**: Prefer `RuntimeException` subclasses; checked exceptions create coupling
|
|
11
|
+
- **Centralized handling**: `@ControllerAdvice` at the API boundary, not scattered try/catch
|
|
12
|
+
- **Structured responses**: Problem Details (RFC 9457) for consistent, machine-readable error payloads
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Custom Exception Hierarchy
|
|
17
|
+
|
|
18
|
+
### Base Exceptions
|
|
19
|
+
|
|
20
|
+
```java
|
|
21
|
+
// src/main/java/com/example/common/exception/AppException.java
|
|
22
|
+
public abstract class AppException extends RuntimeException {
|
|
23
|
+
|
|
24
|
+
private final String errorCode;
|
|
25
|
+
private final int statusCode;
|
|
26
|
+
private final Map<String, Object> details;
|
|
27
|
+
|
|
28
|
+
protected AppException(String message, String errorCode, int statusCode) {
|
|
29
|
+
this(message, errorCode, statusCode, Map.of());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected AppException(String message, String errorCode, int statusCode, Map<String, Object> details) {
|
|
33
|
+
super(message);
|
|
34
|
+
this.errorCode = errorCode;
|
|
35
|
+
this.statusCode = statusCode;
|
|
36
|
+
this.details = details;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
public String getErrorCode() { return errorCode; }
|
|
40
|
+
public int getStatusCode() { return statusCode; }
|
|
41
|
+
public Map<String, Object> getDetails() { return details; }
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Concrete Exceptions
|
|
46
|
+
|
|
47
|
+
```java
|
|
48
|
+
public class NotFoundException extends AppException {
|
|
49
|
+
public NotFoundException(String resource, Object identifier) {
|
|
50
|
+
super(
|
|
51
|
+
"%s not found: %s".formatted(resource, identifier),
|
|
52
|
+
"NOT_FOUND",
|
|
53
|
+
404,
|
|
54
|
+
Map.of("resource", resource, "identifier", identifier.toString())
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
public class ConflictException extends AppException {
|
|
60
|
+
public ConflictException(String message) {
|
|
61
|
+
super(message, "CONFLICT", 409);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public class ValidationException extends AppException {
|
|
66
|
+
public ValidationException(String message, Map<String, String> fieldErrors) {
|
|
67
|
+
super(message, "VALIDATION_ERROR", 422, Map.of("fieldErrors", fieldErrors));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public ValidationException(String message) {
|
|
71
|
+
this(message, Map.of());
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public class ForbiddenException extends AppException {
|
|
76
|
+
public ForbiddenException(String message) {
|
|
77
|
+
super(message, "FORBIDDEN", 403);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public class ExternalServiceException extends AppException {
|
|
82
|
+
public ExternalServiceException(String service, String message, Throwable cause) {
|
|
83
|
+
super("External service error (%s): %s".formatted(service, message), "EXTERNAL_SERVICE_ERROR", 502,
|
|
84
|
+
Map.of("service", service));
|
|
85
|
+
initCause(cause);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Usage in Services
|
|
91
|
+
|
|
92
|
+
```java
|
|
93
|
+
@Service
|
|
94
|
+
public class UserService {
|
|
95
|
+
|
|
96
|
+
private final UserRepository userRepository;
|
|
97
|
+
|
|
98
|
+
public User getUser(Long id) {
|
|
99
|
+
return userRepository.findById(id)
|
|
100
|
+
.orElseThrow(() -> new NotFoundException("User", id));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public User createUser(CreateUserRequest request) {
|
|
104
|
+
userRepository.findByEmail(request.email()).ifPresent(existing -> {
|
|
105
|
+
throw new ConflictException("Email already registered: " + request.email());
|
|
106
|
+
});
|
|
107
|
+
var user = new User(request.email(), request.name());
|
|
108
|
+
return userRepository.save(user);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Spring @ControllerAdvice (Problem Details)
|
|
116
|
+
|
|
117
|
+
### Global Exception Handler
|
|
118
|
+
|
|
119
|
+
Spring Boot 3.x supports RFC 9457 Problem Details natively via `ProblemDetail`:
|
|
120
|
+
|
|
121
|
+
```java
|
|
122
|
+
@RestControllerAdvice
|
|
123
|
+
public class GlobalExceptionHandler {
|
|
124
|
+
|
|
125
|
+
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
|
126
|
+
|
|
127
|
+
@ExceptionHandler(AppException.class)
|
|
128
|
+
public ProblemDetail handleAppException(AppException ex, HttpServletRequest request) {
|
|
129
|
+
log.warn("app_error: code={}, message={}, path={}",
|
|
130
|
+
ex.getErrorCode(), ex.getMessage(), request.getRequestURI());
|
|
131
|
+
|
|
132
|
+
var problem = ProblemDetail.forStatusAndDetail(
|
|
133
|
+
HttpStatusCode.valueOf(ex.getStatusCode()),
|
|
134
|
+
ex.getMessage()
|
|
135
|
+
);
|
|
136
|
+
problem.setTitle(ex.getErrorCode());
|
|
137
|
+
problem.setProperty("errorCode", ex.getErrorCode());
|
|
138
|
+
if (!ex.getDetails().isEmpty()) {
|
|
139
|
+
problem.setProperty("details", ex.getDetails());
|
|
140
|
+
}
|
|
141
|
+
return problem;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@ExceptionHandler(MethodArgumentNotValidException.class)
|
|
145
|
+
public ProblemDetail handleValidation(MethodArgumentNotValidException ex) {
|
|
146
|
+
var fieldErrors = ex.getBindingResult().getFieldErrors().stream()
|
|
147
|
+
.collect(Collectors.toMap(
|
|
148
|
+
FieldError::getField,
|
|
149
|
+
fe -> fe.getDefaultMessage() != null ? fe.getDefaultMessage() : "invalid",
|
|
150
|
+
(a, b) -> a
|
|
151
|
+
));
|
|
152
|
+
|
|
153
|
+
var problem = ProblemDetail.forStatusAndDetail(
|
|
154
|
+
HttpStatus.UNPROCESSABLE_ENTITY,
|
|
155
|
+
"Validation failed"
|
|
156
|
+
);
|
|
157
|
+
problem.setTitle("VALIDATION_ERROR");
|
|
158
|
+
problem.setProperty("fieldErrors", fieldErrors);
|
|
159
|
+
return problem;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
@ExceptionHandler(Exception.class)
|
|
163
|
+
public ProblemDetail handleUnexpected(Exception ex, HttpServletRequest request) {
|
|
164
|
+
log.error("unhandled_error: path={}, method={}", request.getRequestURI(), request.getMethod(), ex);
|
|
165
|
+
|
|
166
|
+
var problem = ProblemDetail.forStatusAndDetail(
|
|
167
|
+
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
168
|
+
"An unexpected error occurred"
|
|
169
|
+
);
|
|
170
|
+
problem.setTitle("INTERNAL_ERROR");
|
|
171
|
+
return problem;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Enable Problem Details in Spring Boot
|
|
177
|
+
|
|
178
|
+
```yaml
|
|
179
|
+
# application.yml
|
|
180
|
+
spring:
|
|
181
|
+
mvc:
|
|
182
|
+
problemdetails:
|
|
183
|
+
enabled: true
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Example Response (RFC 9457)
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{
|
|
190
|
+
"type": "about:blank",
|
|
191
|
+
"title": "NOT_FOUND",
|
|
192
|
+
"status": 404,
|
|
193
|
+
"detail": "User not found: 42",
|
|
194
|
+
"instance": "/api/v1/users/42",
|
|
195
|
+
"errorCode": "NOT_FOUND",
|
|
196
|
+
"details": {
|
|
197
|
+
"resource": "User",
|
|
198
|
+
"identifier": "42"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Checked vs Unchecked Exceptions
|
|
206
|
+
|
|
207
|
+
### Decision Table
|
|
208
|
+
|
|
209
|
+
| Scenario | Exception Type | Rationale |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| Business logic violations | Unchecked (`AppException`) | Caller should not be forced to handle |
|
|
212
|
+
| Resource not found | Unchecked (`NotFoundException`) | Common flow, handled centrally |
|
|
213
|
+
| External service failure | Unchecked (`ExternalServiceException`) | Wrap and rethrow |
|
|
214
|
+
| File I/O (must handle) | Checked (wrap in unchecked) | Convert at boundary |
|
|
215
|
+
| Programmatic errors (bugs) | Unchecked (`IllegalStateException`) | Should never be caught |
|
|
216
|
+
|
|
217
|
+
### Wrapping Checked Exceptions
|
|
218
|
+
|
|
219
|
+
```java
|
|
220
|
+
// GOOD: Wrap checked exception at the boundary
|
|
221
|
+
public byte[] readConfig(Path path) {
|
|
222
|
+
try {
|
|
223
|
+
return Files.readAllBytes(path);
|
|
224
|
+
} catch (IOException e) {
|
|
225
|
+
throw new ConfigurationException("Failed to read config: " + path, e);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// BAD: Propagating checked exceptions through the entire call chain
|
|
230
|
+
public byte[] readConfig(Path path) throws IOException { // forces all callers to handle
|
|
231
|
+
return Files.readAllBytes(path);
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Result Pattern with Sealed Interfaces
|
|
238
|
+
|
|
239
|
+
For expected business outcomes where exceptions feel too heavy:
|
|
240
|
+
|
|
241
|
+
```java
|
|
242
|
+
public sealed interface Result<T>
|
|
243
|
+
permits Result.Success, Result.Failure {
|
|
244
|
+
|
|
245
|
+
record Success<T>(T value) implements Result<T> {}
|
|
246
|
+
record Failure<T>(String error, String code) implements Result<T> {}
|
|
247
|
+
|
|
248
|
+
default T orElseThrow() {
|
|
249
|
+
return switch (this) {
|
|
250
|
+
case Success<T> s -> s.value();
|
|
251
|
+
case Failure<T> f -> throw new AppException(f.error(), f.code(), 400) {};
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
default <R> Result<R> map(Function<T, R> mapper) {
|
|
256
|
+
return switch (this) {
|
|
257
|
+
case Success<T> s -> new Success<>(mapper.apply(s.value()));
|
|
258
|
+
case Failure<T> f -> new Failure<>(f.error(), f.code());
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Usage
|
|
265
|
+
|
|
266
|
+
```java
|
|
267
|
+
public Result<User> createUser(CreateUserRequest request) {
|
|
268
|
+
if (userRepository.existsByEmail(request.email())) {
|
|
269
|
+
return new Result.Failure<>("Email already registered", "DUPLICATE_EMAIL");
|
|
270
|
+
}
|
|
271
|
+
var user = new User(request.email(), request.name());
|
|
272
|
+
return new Result.Success<>(userRepository.save(user));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Caller
|
|
276
|
+
Result<User> result = userService.createUser(request);
|
|
277
|
+
return switch (result) {
|
|
278
|
+
case Result.Success<User> s -> ResponseEntity.created(uri).body(UserResponse.from(s.value()));
|
|
279
|
+
case Result.Failure<User> f -> ResponseEntity.status(409).body(ProblemDetail.forStatusAndDetail(
|
|
280
|
+
HttpStatus.CONFLICT, f.error()));
|
|
281
|
+
};
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### When to Use Each
|
|
285
|
+
|
|
286
|
+
| Pattern | Use Case |
|
|
287
|
+
|---|---|
|
|
288
|
+
| Exceptions | Infrastructure errors, auth failures, unexpected failures |
|
|
289
|
+
| Result | Expected business outcomes (duplicate email, insufficient funds, validation) |
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Exception Chaining
|
|
294
|
+
|
|
295
|
+
Always preserve the original cause:
|
|
296
|
+
|
|
297
|
+
```java
|
|
298
|
+
// GOOD: Chain with cause
|
|
299
|
+
try {
|
|
300
|
+
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
301
|
+
} catch (IOException | InterruptedException e) {
|
|
302
|
+
throw new ExternalServiceException("payment-api", e.getMessage(), e);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// BAD: Losing the original cause
|
|
306
|
+
try {
|
|
307
|
+
var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
|
|
308
|
+
} catch (IOException e) {
|
|
309
|
+
throw new ExternalServiceException("payment-api", "request failed", null); // cause lost!
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Retry with Resilience4j
|
|
316
|
+
|
|
317
|
+
```java
|
|
318
|
+
@Service
|
|
319
|
+
public class PaymentClient {
|
|
320
|
+
|
|
321
|
+
private final RetryRegistry retryRegistry;
|
|
322
|
+
|
|
323
|
+
@Retry(name = "paymentService", fallbackMethod = "paymentFallback")
|
|
324
|
+
public PaymentResult charge(long amountCents, String token) {
|
|
325
|
+
// HTTP call to payment provider
|
|
326
|
+
return restClient.post()
|
|
327
|
+
.uri("/charges")
|
|
328
|
+
.body(new ChargeRequest(amountCents, token))
|
|
329
|
+
.retrieve()
|
|
330
|
+
.body(PaymentResult.class);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private PaymentResult paymentFallback(long amountCents, String token, Exception ex) {
|
|
334
|
+
log.error("payment_failed_after_retries: amount={}, error={}", amountCents, ex.getMessage());
|
|
335
|
+
throw new ExternalServiceException("payment", "Payment service unavailable", ex);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
```yaml
|
|
341
|
+
# application.yml
|
|
342
|
+
resilience4j:
|
|
343
|
+
retry:
|
|
344
|
+
instances:
|
|
345
|
+
paymentService:
|
|
346
|
+
maxAttempts: 3
|
|
347
|
+
waitDuration: 1s
|
|
348
|
+
exponentialBackoffMultiplier: 2
|
|
349
|
+
retryExceptions:
|
|
350
|
+
- java.io.IOException
|
|
351
|
+
- org.springframework.web.client.ResourceAccessException
|
|
352
|
+
ignoreExceptions:
|
|
353
|
+
- com.example.common.exception.ValidationException
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## What Never to Catch
|
|
359
|
+
|
|
360
|
+
```java
|
|
361
|
+
// BAD: Never catch Throwable -- includes OutOfMemoryError, StackOverflowError
|
|
362
|
+
try {
|
|
363
|
+
process();
|
|
364
|
+
} catch (Throwable t) {
|
|
365
|
+
log.error("Something happened", t);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// BAD: Never catch and silently swallow
|
|
369
|
+
try {
|
|
370
|
+
riskyOperation();
|
|
371
|
+
} catch (Exception e) {
|
|
372
|
+
// swallowed -- silent failure, impossible to debug
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// BAD: Catching InterruptedException without restoring interrupt status
|
|
376
|
+
try {
|
|
377
|
+
Thread.sleep(1000);
|
|
378
|
+
} catch (InterruptedException e) {
|
|
379
|
+
// Thread interrupt flag is cleared -- must restore it
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// GOOD: Restore interrupt status
|
|
383
|
+
try {
|
|
384
|
+
Thread.sleep(1000);
|
|
385
|
+
} catch (InterruptedException e) {
|
|
386
|
+
Thread.currentThread().interrupt();
|
|
387
|
+
throw new ServiceException("Operation interrupted", e);
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Anti-Patterns
|
|
394
|
+
|
|
395
|
+
| Anti-Pattern | Problem | Correct Approach |
|
|
396
|
+
|---|---|---|
|
|
397
|
+
| Catching `Throwable` | Catches `Error` (OOM, StackOverflow) | Catch specific exceptions |
|
|
398
|
+
| Empty catch blocks | Silent failures, impossible to debug | Log and rethrow or handle properly |
|
|
399
|
+
| Checked exceptions everywhere | Forces boilerplate on every caller | Wrap in `RuntimeException` subclass |
|
|
400
|
+
| Returning error codes/strings | No type safety, easy to ignore | Use `Result` type or throw |
|
|
401
|
+
| Stack traces in API responses | Security risk, bad UX | `ProblemDetail` with safe messages |
|
|
402
|
+
| `Exception` as method signature | Caller has no idea what to expect | Declare specific exceptions or use unchecked |
|
|
403
|
+
| `try/catch` around every method | Cluttered, hides control flow | Centralized `@ControllerAdvice` |
|
|
404
|
+
| `e.printStackTrace()` | Goes to stderr, not structured logging | Use SLF4J logger |
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
_Errors are data. Classify them in a hierarchy, handle them centrally, and present them as Problem Details. Never swallow exceptions silently._
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Feedback Configuration
|
|
2
|
+
|
|
3
|
+
Project-specific commands for automated feedback during Java implementation with Gradle.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Test Commands
|
|
8
|
+
|
|
9
|
+
Commands to run tests during implementation. The agent will use these to verify code changes.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
# Primary test command (REQUIRED)
|
|
13
|
+
test: ./gradlew test
|
|
14
|
+
|
|
15
|
+
# Test with verbose output
|
|
16
|
+
test_verbose: ./gradlew test --info
|
|
17
|
+
|
|
18
|
+
# Run specific test class (use {class} as placeholder)
|
|
19
|
+
test_class: ./gradlew test --tests "{class}"
|
|
20
|
+
|
|
21
|
+
# Run tests matching pattern
|
|
22
|
+
test_pattern: ./gradlew test --tests "*{pattern}*"
|
|
23
|
+
|
|
24
|
+
# Run only unit tests (by directory convention)
|
|
25
|
+
test_unit: ./gradlew test --tests "com.example.unit.*"
|
|
26
|
+
|
|
27
|
+
# Run only integration tests
|
|
28
|
+
test_integration: ./gradlew test --tests "com.example.integration.*"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Build Commands
|
|
34
|
+
|
|
35
|
+
Commands for compiling and packaging.
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
# Full build (compile + test + check)
|
|
39
|
+
build: ./gradlew build
|
|
40
|
+
|
|
41
|
+
# Compile only (no tests)
|
|
42
|
+
compile: ./gradlew compileJava
|
|
43
|
+
|
|
44
|
+
# Clean build
|
|
45
|
+
clean_build: ./gradlew clean build
|
|
46
|
+
|
|
47
|
+
# Build without tests (use sparingly)
|
|
48
|
+
build_skip_tests: ./gradlew build -x test
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Linting and Quality Commands
|
|
54
|
+
|
|
55
|
+
Commands for code quality checks.
|
|
56
|
+
|
|
57
|
+
```yaml
|
|
58
|
+
# Run all verification tasks (checkstyle + spotbugs + tests)
|
|
59
|
+
check: ./gradlew check
|
|
60
|
+
|
|
61
|
+
# Checkstyle (style enforcement)
|
|
62
|
+
checkstyle: ./gradlew checkstyleMain checkstyleTest
|
|
63
|
+
|
|
64
|
+
# SpotBugs (bug detection)
|
|
65
|
+
spotbugs: ./gradlew spotbugsMain
|
|
66
|
+
|
|
67
|
+
# Spotless format check
|
|
68
|
+
format_check: ./gradlew spotlessCheck
|
|
69
|
+
|
|
70
|
+
# Spotless auto-format
|
|
71
|
+
format_fix: ./gradlew spotlessApply
|
|
72
|
+
|
|
73
|
+
# SonarQube analysis
|
|
74
|
+
sonar: ./gradlew sonar
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Coverage Commands
|
|
80
|
+
|
|
81
|
+
Commands for test coverage reporting.
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
# Generate coverage report
|
|
85
|
+
coverage: ./gradlew test jacocoTestReport
|
|
86
|
+
|
|
87
|
+
# Enforce coverage thresholds
|
|
88
|
+
coverage_verify: ./gradlew jacocoTestCoverageVerification
|
|
89
|
+
|
|
90
|
+
# Report location
|
|
91
|
+
coverage_report: build/reports/jacoco/test/html/index.html
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Database Commands
|
|
97
|
+
|
|
98
|
+
Commands for database migrations.
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
# Apply pending migrations
|
|
102
|
+
migrate: ./gradlew flywayMigrate
|
|
103
|
+
|
|
104
|
+
# Show migration status
|
|
105
|
+
migrate_info: ./gradlew flywayInfo
|
|
106
|
+
|
|
107
|
+
# Clean database (DANGER: drops all objects)
|
|
108
|
+
migrate_clean: ./gradlew flywayClean
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Development Server
|
|
114
|
+
|
|
115
|
+
Commands for starting the development server.
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
# Start dev server (Spring Boot)
|
|
119
|
+
dev_server: ./gradlew bootRun
|
|
120
|
+
|
|
121
|
+
# Dev server with specific profile
|
|
122
|
+
dev_server_profile: SPRING_PROFILES_ACTIVE=dev ./gradlew bootRun
|
|
123
|
+
|
|
124
|
+
# Dev server port
|
|
125
|
+
dev_port: 8080
|
|
126
|
+
|
|
127
|
+
# Dev server base URL
|
|
128
|
+
dev_url: http://localhost:8080
|
|
129
|
+
|
|
130
|
+
# API documentation URL
|
|
131
|
+
api_docs_url: http://localhost:8080/swagger-ui.html
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## UI Verification
|
|
137
|
+
|
|
138
|
+
Settings for agent-browser UI verification.
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
# Enable UI verification (typically false for API-only projects)
|
|
142
|
+
ui_verification_enabled: false
|
|
143
|
+
|
|
144
|
+
# Default wait time after navigation (milliseconds)
|
|
145
|
+
navigation_wait: 2000
|
|
146
|
+
|
|
147
|
+
# Screenshot directory
|
|
148
|
+
screenshot_dir: /tmp/ui-captures
|
|
149
|
+
|
|
150
|
+
# API documentation URL (for API projects)
|
|
151
|
+
api_docs_url: http://localhost:8080/swagger-ui.html
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Quick Reference
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Full CI pipeline (what CI runs)
|
|
160
|
+
./gradlew clean build check jacocoTestReport
|
|
161
|
+
|
|
162
|
+
# Quick local development cycle
|
|
163
|
+
./gradlew spotlessApply && ./gradlew check
|
|
164
|
+
|
|
165
|
+
# Fast feedback (compile + single test)
|
|
166
|
+
./gradlew compileJava && ./gradlew test --tests "UserServiceTest"
|
|
167
|
+
|
|
168
|
+
# Format, lint, test -- one command
|
|
169
|
+
./gradlew spotlessApply check
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Notes
|
|
175
|
+
|
|
176
|
+
- Uses Gradle wrapper (`./gradlew`) for reproducible builds -- never use system Gradle
|
|
177
|
+
- Spotless replaces manual formatting discussions -- run `spotlessApply` before committing
|
|
178
|
+
- `./gradlew check` runs checkstyle, spotbugs, tests, and JaCoCo verification in one command
|
|
179
|
+
- For Spring Boot apps, `bootRun` uses virtual threads when `spring.threads.virtual.enabled=true`
|
|
180
|
+
- Testcontainers requires Docker to be running for integration tests
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Technology Stack
|
|
2
|
+
|
|
3
|
+
## Architecture
|
|
4
|
+
|
|
5
|
+
Modern Java application with virtual-thread-first design. Spring Boot 3.x or Quarkus as web framework, PostgreSQL for persistence, Redis for caching, Docker for deployment.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core Technologies
|
|
10
|
+
|
|
11
|
+
- **Language**: Java 21+ (LTS)
|
|
12
|
+
- **Build Tool**: Gradle (Kotlin DSL) or Maven
|
|
13
|
+
- **Web Framework**: Spring Boot 3.x (enterprise) or Quarkus (cloud-native) or Micronaut
|
|
14
|
+
- **Database**: PostgreSQL with Spring Data JPA / Hibernate
|
|
15
|
+
- **Cache/Queue**: Redis with Spring Data Redis or Lettuce
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Key Libraries
|
|
20
|
+
|
|
21
|
+
### Web & API
|
|
22
|
+
- **Spring Boot 3.x**: Production-ready framework with auto-configuration
|
|
23
|
+
- **Spring WebMVC**: Servlet-based web framework (virtual threads recommended)
|
|
24
|
+
- **Spring WebFlux**: Reactive web framework (when reactive streams required)
|
|
25
|
+
- **Jackson**: JSON serialization/deserialization
|
|
26
|
+
- **SpringDoc OpenAPI**: API documentation (Swagger UI)
|
|
27
|
+
|
|
28
|
+
### Database & Storage
|
|
29
|
+
- **Spring Data JPA**: Repository abstraction over Hibernate
|
|
30
|
+
- **Hibernate 6.x**: ORM with Jakarta Persistence API
|
|
31
|
+
- **Flyway**: Database migrations (preferred) or Liquibase
|
|
32
|
+
- **jOOQ**: Type-safe SQL DSL (alternative to JPA for complex queries)
|
|
33
|
+
- **HikariCP**: Connection pooling (Spring Boot default)
|
|
34
|
+
|
|
35
|
+
### Background Tasks
|
|
36
|
+
- **Spring Scheduler**: `@Scheduled` for cron-like jobs
|
|
37
|
+
- **Spring Integration**: Enterprise integration patterns
|
|
38
|
+
- **Spring Batch**: Large-scale batch processing
|
|
39
|
+
|
|
40
|
+
### Deployment
|
|
41
|
+
- **Docker**: Containerized deployment (multi-stage builds)
|
|
42
|
+
- **Docker Compose**: Multi-service local development
|
|
43
|
+
- **GraalVM Native Image**: Optional ahead-of-time compilation (Quarkus/Micronaut)
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Development Standards
|
|
48
|
+
|
|
49
|
+
### Code Quality
|
|
50
|
+
- **Spotless** or **google-java-format**: Code formatting
|
|
51
|
+
- **Checkstyle**: Style enforcement (Google or Sun conventions)
|
|
52
|
+
- **SpotBugs**: Static analysis for bug patterns
|
|
53
|
+
- **Error Prone**: Compile-time error detection (Google)
|
|
54
|
+
- **SonarQube**: Continuous code quality inspection
|
|
55
|
+
|
|
56
|
+
### Security
|
|
57
|
+
- **Spring Security 6**: Authentication and authorization
|
|
58
|
+
- **OWASP Dependency-Check**: Dependency vulnerability scanning
|
|
59
|
+
- **Spring Vault**: Secrets management (when needed)
|
|
60
|
+
|
|
61
|
+
### Testing
|
|
62
|
+
- **JUnit 5**: Test framework with `@Test`, `@ParameterizedTest`, `@Nested`
|
|
63
|
+
- **Mockito**: Mocking framework
|
|
64
|
+
- **AssertJ**: Fluent assertion library
|
|
65
|
+
- **Testcontainers**: Docker-based integration testing
|
|
66
|
+
- **Spring Boot Test**: `@SpringBootTest`, `@WebMvcTest`, `@DataJpaTest`
|
|
67
|
+
- **ArchUnit**: Architecture rule enforcement
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Development Environment
|
|
72
|
+
|
|
73
|
+
### Required Tools
|
|
74
|
+
- Java 21+ (see `.java-version` or `.sdkmanrc`)
|
|
75
|
+
- Gradle 8.x or Maven 3.9+
|
|
76
|
+
- PostgreSQL 16+
|
|
77
|
+
- Redis 7+
|
|
78
|
+
- Docker & Docker Compose
|
|
79
|
+
|
|
80
|
+
### Common Commands
|
|
81
|
+
```bash
|
|
82
|
+
# Environment setup
|
|
83
|
+
./gradlew build # Build project
|
|
84
|
+
./gradlew dependencies # Show dependency tree
|
|
85
|
+
|
|
86
|
+
# Dev server
|
|
87
|
+
./gradlew bootRun # Spring Boot
|
|
88
|
+
./mvnw spring-boot:run # Maven alternative
|
|
89
|
+
|
|
90
|
+
# Tests
|
|
91
|
+
./gradlew test # All tests
|
|
92
|
+
./gradlew test --tests "*.unit.*" # Unit tests only
|
|
93
|
+
./gradlew test jacocoTestReport # With coverage
|
|
94
|
+
|
|
95
|
+
# Code quality
|
|
96
|
+
./gradlew checkstyleMain # Checkstyle
|
|
97
|
+
./gradlew spotlessCheck # Format check
|
|
98
|
+
./gradlew spotlessApply # Auto-format
|
|
99
|
+
./gradlew spotbugsMain # SpotBugs analysis
|
|
100
|
+
|
|
101
|
+
# Database
|
|
102
|
+
./gradlew flywayMigrate # Apply migrations
|
|
103
|
+
./gradlew flywayInfo # Migration status
|
|
104
|
+
|
|
105
|
+
# Docker
|
|
106
|
+
docker compose up -d # Start services
|
|
107
|
+
docker compose logs -f app # Follow app logs
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Key Technical Decisions
|
|
113
|
+
|
|
114
|
+
| Decision | Rationale |
|
|
115
|
+
|----------|-----------|
|
|
116
|
+
| **Java 21 over 17** | Virtual threads, record patterns, pattern matching for switch, sequenced collections |
|
|
117
|
+
| **Gradle Kotlin DSL over Groovy** | Type-safe build scripts, IDE autocompletion, refactoring support |
|
|
118
|
+
| **Spring Boot 3.x over 2.x** | Jakarta EE 10, ProblemDetail support, virtual threads, GraalVM native |
|
|
119
|
+
| **Flyway over Liquibase** | SQL-based migrations, simpler mental model, widely adopted |
|
|
120
|
+
| **AssertJ over Hamcrest** | Fluent API, better error messages, IDE discoverability |
|
|
121
|
+
| **Testcontainers over H2** | Production-parity testing, real database behavior, Docker-based |
|
|
122
|
+
| **Virtual threads over reactive** | Simpler programming model, familiar blocking code, JVM-managed scalability |
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
_Document standards and patterns, not every dependency. See individual steering files for detailed conventions._
|